mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 09:26:54 +00:00
Compare commits
66 Commits
v2.5.1
...
api-update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4812106ff0 | ||
|
|
d6bfd45782 | ||
|
|
237179f590 | ||
|
|
ea8cb4ddd1 | ||
|
|
9fbd890db8 | ||
|
|
db217f5533 | ||
|
|
cf3bb412d5 | ||
|
|
84ff217d7f | ||
|
|
4f520b2e5a | ||
|
|
4c2a7e8c90 | ||
|
|
9320d4395a | ||
|
|
451eebae7e | ||
|
|
9271f103ea | ||
|
|
a6d309ab5a | ||
|
|
1b447e3c4f | ||
|
|
c3b16b43ce | ||
|
|
ce06e8158a | ||
|
|
29b0064329 | ||
|
|
27be769228 | ||
|
|
106d3d61f9 | ||
|
|
5607e6d711 | ||
|
|
dea8fc05b1 | ||
|
|
8e037988d5 | ||
|
|
c56c3c9a8f | ||
|
|
70ef4a0f8f | ||
|
|
7456933843 | ||
|
|
df2e178394 | ||
|
|
62c8fb0e1c | ||
|
|
878f67391b | ||
|
|
f020af7062 | ||
|
|
c56dcc62f3 | ||
|
|
d42ab26417 | ||
|
|
24cd260531 | ||
|
|
440c9ffd9b | ||
|
|
9c88951646 | ||
|
|
6a983a5eae | ||
|
|
c17af196fb | ||
|
|
60e7cc2369 | ||
|
|
3ccfe6633b | ||
|
|
812974ebb6 | ||
|
|
cccfecc325 | ||
|
|
0fc0782e55 | ||
|
|
120c527b34 | ||
|
|
4f7f3b6212 | ||
|
|
8070382229 | ||
|
|
513fffed4b | ||
|
|
232705ca37 | ||
|
|
7242d8fed1 | ||
|
|
0cc701448b | ||
|
|
06054db655 | ||
|
|
6be1eea20a | ||
|
|
04517e113f | ||
|
|
028837e1c9 | ||
|
|
0784c077f5 | ||
|
|
231ae24065 | ||
|
|
ef64d9e99c | ||
|
|
72ceede26a | ||
|
|
5fbd3a6d68 | ||
|
|
df2a92cac2 | ||
|
|
d4ac129b05 | ||
|
|
04dfa82371 | ||
|
|
ef357e90af | ||
|
|
048072bdd7 | ||
|
|
18d07d6e2d | ||
|
|
6429085650 | ||
|
|
fd85697636 |
12
.eslintrc
12
.eslintrc
@@ -3,11 +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/ban-types": 0,
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
"@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
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
* @jacoobes
|
||||
src/* @jacoobes
|
||||
|
||||
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,33 +0,0 @@
|
||||
---
|
||||
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
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Sern Community Support
|
||||
url: https://discord.gg/9w8jzsR48U
|
||||
about: Please ask and answer questions here.
|
||||
43
.github/ISSUE_TEMPLATE/feature_request.md
vendored
43
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,43 +0,0 @@
|
||||
---
|
||||
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.)
|
||||
@@ -1,26 +0,0 @@
|
||||
# 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
|
||||
|
||||
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -24,14 +24,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
47
.github/workflows/continuous-integration.yml
vendored
47
.github/workflows/continuous-integration.yml
vendored
@@ -1,47 +0,0 @@
|
||||
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@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
|
||||
with:
|
||||
node-version: 17
|
||||
|
||||
- name: Install pnpm
|
||||
run: npm i -g pnpm
|
||||
|
||||
# Prettier must be in `package.json`
|
||||
- name: Install Node.js dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: Run Prettier
|
||||
run: pnpm run pretty
|
||||
|
||||
- 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
20
.github/workflows/dependency-review.yml
vendored
@@ -1,20 +0,0 @@
|
||||
# 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
|
||||
8
.github/workflows/npm-publish.yml
vendored
8
.github/workflows/npm-publish.yml
vendored
@@ -8,8 +8,8 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm ci
|
||||
@@ -19,8 +19,8 @@ jobs:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
14
.github/workflows/release-please.yml
vendored
14
.github/workflows/release-please.yml
vendored
@@ -1,14 +0,0 @@
|
||||
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
3
.gitignore
vendored
@@ -82,8 +82,5 @@ dist
|
||||
# VisualStudio Config file
|
||||
.vs
|
||||
|
||||
# VSCode settings and cache
|
||||
.vscode
|
||||
|
||||
# IntelliJ IDEA Config file
|
||||
.idea/
|
||||
|
||||
10
.npmignore
10
.npmignore
@@ -1,4 +1,5 @@
|
||||
src/
|
||||
tsconfig.json
|
||||
docs/
|
||||
.gitignore
|
||||
|
||||
@@ -8,6 +9,7 @@ logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
@@ -104,10 +106,4 @@ CODE_OF_CONDUCT.md
|
||||
|
||||
babel.config.js
|
||||
|
||||
tsup.config.js
|
||||
|
||||
tsconfig-base.json
|
||||
|
||||
tsconfig.cjs.json
|
||||
|
||||
tsconfig.esm.json
|
||||
tests/
|
||||
@@ -1,3 +1,2 @@
|
||||
.github/
|
||||
*.md
|
||||
dist/
|
||||
*.md
|
||||
12
.prettierrc
12
.prettierrc
@@ -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"
|
||||
}
|
||||
|
||||
193
CHANGELOG.md
193
CHANGELOG.md
@@ -1,193 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
## [2.5.1](https://github.com/sern-handler/handler/compare/v2.5.0...v2.5.1) (2023-02-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Adding my bot to readme ([#210](https://github.com/sern-handler/handler/issues/210)) ([96f4281](https://github.com/sern-handler/handler/commit/96f42811218e4898a47e75a8138ccd452ae2c5c2))
|
||||
* Adding the WIP to my bot ([86fa531](https://github.com/sern-handler/handler/commit/86fa531eb620d2ac649bad6decb29d5c55a25445))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* autocomplete ([1860b89](https://github.com/sern-handler/handler/commit/1860b898f3a231840e2a8b781e007ef9d6f587a4))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 2.5.1 ([c78936a](https://github.com/sern-handler/handler/commit/c78936a22574da4af71826f5b5f72f354a4eb06a))
|
||||
|
||||
## [2.5.0](https://github.com/sern-handler/handler/compare/v2.1.1...v2.5.0) (2023-01-30)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* simpler plugins ([#193](https://github.com/sern-handler/handler/issues/193))
|
||||
|
||||
### Features
|
||||
|
||||
* simpler plugins ([#193](https://github.com/sern-handler/handler/issues/193)) ([33f1446](https://github.com/sern-handler/handler/commit/33f14467ec413e003a82503c8a77cb42a6194281))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 2.5.0 ([b4b195d](https://github.com/sern-handler/handler/commit/b4b195dc9586736760d0b78caa8589f3d6131f8a))
|
||||
|
||||
## [2.1.1](https://github.com/sern-handler/handler/compare/v2.1.0...v2.1.1) (2022-12-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* modals remapping ([a13df6f](https://github.com/sern-handler/handler/commit/a13df6fb424d256476284da49024dbe56e82baab))
|
||||
|
||||
## [2.1.0](https://github.com/sern-handler/handler/compare/v2.0.0...v2.1.0) (2022-12-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* grammar ([c30aac4](https://github.com/sern-handler/handler/commit/c30aac476cdc2094de34f9f67b5805204cc5e4dd))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* multi parameter events ([e986535](https://github.com/sern-handler/handler/commit/e98653593566ef4635493e0c997bd107a7a3a2a2))
|
||||
|
||||
## [2.0.0](https://github.com/sern-handler/handler/compare/v1.2.1...v2.0.0) (2022-12-28)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* (2.0 global services) ([#156](https://github.com/sern-handler/handler/issues/156))
|
||||
|
||||
### Features
|
||||
|
||||
* (2.0 global services) ([#156](https://github.com/sern-handler/handler/issues/156)) ([1455622](https://github.com/sern-handler/handler/commit/14556223fd6f79b797fb2aee03e795d4f4e94a8b))
|
||||
|
||||
## [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))
|
||||
@@ -1,5 +1,5 @@
|
||||
# Code of Conduct
|
||||
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.
|
||||
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.
|
||||
|
||||
# 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 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
98
README.md
98
README.md
@@ -1,21 +1,13 @@
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/sern-handler/.github/main/banner.png" width="900px" />
|
||||
</div>
|
||||
# SernHandler
|
||||
|
||||
<h1 align="center">Handlers. Redefined.</h1>
|
||||
<h4 align="center">A customizable, batteries-included, powerful discord.js framework to streamline bot development.</h4>
|
||||
<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>
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
<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.dev"><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>
|
||||
A customizable, batteries-included, powerful discord.js framework to automate and streamline your bot development.
|
||||
|
||||
|
||||
## 📜 Installation
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install @sern/handler
|
||||
@@ -28,31 +20,15 @@ yarn add @sern/handler
|
||||
```sh
|
||||
pnpm add @sern/handler
|
||||
```
|
||||
## Why?
|
||||
- Most handlers don't support discord.js 14.7+
|
||||
- Customizable commands
|
||||
- Plug and play or customize to your liking
|
||||
- Embraces reactive programming for consistent and reliable backend
|
||||
- Customizable logger, error handling, and more
|
||||
- Active development and growing [community](https://sern.dev/discord)
|
||||
## 👀 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
|
||||
## Basic Usage
|
||||
|
||||
#### ` index.js (CommonJS)`
|
||||
|
||||
```js
|
||||
// Import the discord.js Client and GatewayIntentBits
|
||||
const { Client, GatewayIntentBits } = require('discord.js');
|
||||
|
||||
// Import Sern namespace
|
||||
const { Sern } = require('@sern/handler');
|
||||
const { Sern } = require('sern-handler');
|
||||
const { defaultPrefix, token } = require('./config.json');
|
||||
|
||||
const client = new Client({
|
||||
intents: [
|
||||
@@ -61,61 +37,47 @@ const client = new Client({
|
||||
GatewayIntentBits.GuildMessages
|
||||
]
|
||||
});
|
||||
export const useContainer = Sern.makeDependencies({
|
||||
build: root => root
|
||||
.add({ '@sern/client': single(client) })
|
||||
.upsert({ '@sern/logger': single(new DefaultLogging()) })
|
||||
});
|
||||
|
||||
//View docs for all options
|
||||
Sern.init({
|
||||
defaultPrefix: '!', // removing defaultPrefix will shut down text commands
|
||||
commands: 'src/commands',
|
||||
// events: 'src/events' (optional),
|
||||
containerConfig : {
|
||||
get: useContainer
|
||||
}
|
||||
client,
|
||||
defaultPrefix,
|
||||
commands : 'src/commands',
|
||||
});
|
||||
|
||||
client.login("YOUR_BOT_TOKEN_HERE");
|
||||
client.login(token);
|
||||
```
|
||||
|
||||
#### ` ping.js (CommonJS)`
|
||||
|
||||
```js
|
||||
const { CommandType, commandModule } = require('@sern/handler');
|
||||
const { Sern, CommandType } = require('@sern/handler');
|
||||
|
||||
exports.default = commandModule({
|
||||
name: 'ping',
|
||||
description: 'A ping pong command',
|
||||
type: CommandType.Slash,
|
||||
execute(ctx) {
|
||||
ctx.reply('pong!');
|
||||
}
|
||||
});
|
||||
exports.default = {
|
||||
description: 'A ping pong command',
|
||||
type: CommandType.Slash,
|
||||
execute(ctx) {
|
||||
ctx.reply('pong!');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 🤖 Bots Using sern
|
||||
- [Community Bot](https://github.com/sern-handler/sern-community), the community bot for our [discord server](https://sern.dev/discord).
|
||||
- [Vinci](https://github.com/SrIzan10/vinci), the bot for Mara Turing.
|
||||
- [Bask](https://github.com/baskbotml/bask), Listen your favorite artists on Discord.
|
||||
- [ava](https://github.com/SrIzan10/ava), A discord bot that plays KNGI and Gensokyo Radio.
|
||||
- [Siamese (WIP)](https://github.com/Benzo-Fury/Siamese-Bot), Using AI to unleash the power in your server.
|
||||
- [Protector (WIP)](https://github.com/needhamgary/Protector), Just a simple bot to help enhance a private minecraft server.
|
||||
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 and Guide](https://sern.dev)
|
||||
- [Support Server](https://sern.dev/discord)
|
||||
- [Official Documentation](https://sern-handler.js.org)
|
||||
- [Support Server](https://discord.com/invite/mmyCTnYtbF)
|
||||
|
||||
## 👋 Contribute
|
||||
## Contribute
|
||||
|
||||
- Read our contribution [guidelines](https://github.com/sern-handler/handler/blob/main/.github/CONTRIBUTING.md) carefully
|
||||
- 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
|
||||
|
||||
You can check our [roadmap](https://github.com/sern-handler/roadmap) to see what's going to be added or patched in the future.
|
||||
|
||||
7558
package-lock.json
generated
Normal file
7558
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
101
package.json
101
package.json
@@ -1,55 +1,50 @@
|
||||
{
|
||||
"name": "@sern/handler",
|
||||
"packageManager": "pnpm@7.27.0",
|
||||
"version": "2.5.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",
|
||||
"pretty": "prettier --write ."
|
||||
},
|
||||
"keywords": [
|
||||
"sern-handler",
|
||||
"sern",
|
||||
"handler",
|
||||
"sern handler",
|
||||
"wrapper",
|
||||
"discord.js",
|
||||
"framework"
|
||||
],
|
||||
"author": "SernDevs",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iti": "^0.6.0",
|
||||
"rxjs": "^7.5.6",
|
||||
"ts-pattern": "^4.0.6",
|
||||
"ts-results-es": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "5.51.0",
|
||||
"@typescript-eslint/parser": "5.48.0",
|
||||
"discord.js": ">= ^14.7.x",
|
||||
"eslint": "8.30.0",
|
||||
"prettier": "2.8.4",
|
||||
"tsup": "^6.1.3",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sern-handler/handler.git"
|
||||
},
|
||||
"homepage": "https://sern.dev"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1788
pnpm-lock.yaml
generated
1788
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"major": {
|
||||
"dependencyDashboardApproval": true,
|
||||
"reviewers": ["EvolutionX-10", "jacoobes", "Murtatrxx"]
|
||||
},
|
||||
"minor": {
|
||||
"reviewers": ["jacoobes", "Murtatrxx"]
|
||||
},
|
||||
"schedule": ["every weekend"],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"automerge": false
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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' }));
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { Logging } from './logging';
|
||||
import util from 'util';
|
||||
export interface ErrorHandling {
|
||||
/**
|
||||
* Number of times the process should throw an error until crashing and exiting
|
||||
*/
|
||||
keepAlive: number;
|
||||
|
||||
/**
|
||||
* Utility function to crash
|
||||
* @param error
|
||||
*/
|
||||
crash(error: Error): never;
|
||||
|
||||
/**
|
||||
* A function that is called on every crash. Updates keepAlive
|
||||
* @param error
|
||||
*/
|
||||
updateAlive(error: Error): void;
|
||||
}
|
||||
|
||||
export class DefaultErrorHandling implements ErrorHandling {
|
||||
keepAlive = 5;
|
||||
crash(error: Error): never {
|
||||
throw error;
|
||||
}
|
||||
updateAlive(e: Error) {
|
||||
this.keepAlive--;
|
||||
}
|
||||
}
|
||||
|
||||
export function handleError<C>(crashHandler: ErrorHandling, logging?: Logging) {
|
||||
return (pload: unknown, caught: Observable<C>) => {
|
||||
// This is done to fit the ErrorHandling contract
|
||||
const err = pload instanceof Error ? pload : Error(util.format(pload));
|
||||
if (crashHandler.keepAlive == 0) {
|
||||
crashHandler.crash(err);
|
||||
}
|
||||
//formatted payload
|
||||
logging?.error({ message: util.format(pload) });
|
||||
crashHandler.updateAlive(err);
|
||||
return caught;
|
||||
};
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export { ErrorHandling, DefaultErrorHandling } from './errorHandling';
|
||||
export { Logging, DefaultLogging } from './logging';
|
||||
export { ModuleManager, DefaultModuleManager } from './moduleManager';
|
||||
@@ -1,27 +0,0 @@
|
||||
import type { LogPayload } from '../../types/handler';
|
||||
|
||||
export interface Logging<T = unknown> {
|
||||
error(payload: LogPayload<T>): void;
|
||||
warning(payload: LogPayload<T>): void;
|
||||
info(payload: LogPayload<T>): void;
|
||||
debug(payload: LogPayload<T>): void;
|
||||
}
|
||||
|
||||
export class DefaultLogging implements Logging {
|
||||
private date = () => new Date();
|
||||
debug(payload: LogPayload): void {
|
||||
console.debug(`DEBUG: ${this.date().toISOString()} -> ${payload.message}`);
|
||||
}
|
||||
|
||||
error(payload: LogPayload): void {
|
||||
console.error(`ERROR: ${this.date().toISOString()} -> ${payload.message}`);
|
||||
}
|
||||
|
||||
info(payload: LogPayload): void {
|
||||
console.info(`INFO: ${this.date().toISOString()} -> ${payload.message}`);
|
||||
}
|
||||
|
||||
warning(payload: LogPayload): void {
|
||||
console.warn(`WARN: ${this.date().toISOString()} -> ${payload.message}`);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { CommandModuleDefs } from '../../types/module';
|
||||
import type { CommandType, ModuleStore } from '../structures';
|
||||
import type { Processed } from '../../types/handler';
|
||||
|
||||
export interface ModuleManager {
|
||||
get<T extends CommandType>(
|
||||
strat: (ms: ModuleStore) => Processed<CommandModuleDefs[T]> | undefined,
|
||||
): CommandModuleDefs[T] | undefined;
|
||||
set(strat: (ms: ModuleStore) => void): void;
|
||||
}
|
||||
|
||||
export class DefaultModuleManager implements ModuleManager {
|
||||
constructor(private moduleStore: ModuleStore) {}
|
||||
get<T extends CommandType>(
|
||||
strat: (ms: ModuleStore) => Processed<CommandModuleDefs[T]> | undefined,
|
||||
) {
|
||||
return strat(this.moduleStore);
|
||||
}
|
||||
|
||||
set(strat: (ms: ModuleStore) => void): void {
|
||||
strat(this.moduleStore);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { single, transient, many } from './lifetimeFunctions';
|
||||
export { useContainerRaw } from './provider';
|
||||
@@ -1,57 +0,0 @@
|
||||
import { _const } from '../utilities/functions';
|
||||
|
||||
type NotFunction =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| bigint
|
||||
| readonly any[]
|
||||
| { apply?: never; [k: string]: any }
|
||||
| { call?: never; [k: string]: any };
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param cb
|
||||
*/
|
||||
export function single<T extends NotFunction>(cb: T): () => T;
|
||||
/**
|
||||
* New signature
|
||||
* @param cb
|
||||
*/
|
||||
export function single<T extends () => unknown>(cb: T): T;
|
||||
/**
|
||||
* Please note that on intellij, the deprecation is for all signatures, which is unintended behavior (and
|
||||
* very annoying).
|
||||
* For future versions, ensure that single is being passed as a **callback!!**
|
||||
* @param cb
|
||||
*/
|
||||
export function single<T>(cb: T) {
|
||||
if (typeof cb === 'function') return cb;
|
||||
return () => cb;
|
||||
}
|
||||
/**
|
||||
* @deprecated
|
||||
* @param cb
|
||||
* Deprecated signature
|
||||
*/
|
||||
export function transient<T extends NotFunction>(cb: T): () => () => T;
|
||||
export function transient<T extends () => () => unknown>(cb: T): T;
|
||||
/**
|
||||
* Following iti's singleton and transient implementation,
|
||||
* use transient if you want a new dependency every time your container getter is called
|
||||
* @param cb
|
||||
*/
|
||||
export function transient<T>(cb: (() => () => T) | T) {
|
||||
if (typeof cb !== 'function') return () => () => cb;
|
||||
return cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @param value
|
||||
* Please use the transient function instead
|
||||
*/
|
||||
// prettier-ignore
|
||||
export const many = <T>(value: T) => () => _const(value);
|
||||
@@ -1,73 +0,0 @@
|
||||
import type { Container } from 'iti';
|
||||
import { SernError } from '../structures/errors';
|
||||
import type { Dependencies, DependencyConfiguration, MapDeps } from '../../types/handler';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
import { DefaultErrorHandling, DefaultLogging, DefaultModuleManager } from '../contracts';
|
||||
import { ModuleStore } from '../structures/moduleStore';
|
||||
import { Result } from 'ts-results-es';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { createContainer } from 'iti';
|
||||
|
||||
export const containerSubject = new BehaviorSubject(defaultContainer());
|
||||
|
||||
/**
|
||||
* Given the user's conf, check for any excluded dependency keys.
|
||||
* Then, call conf.build to get the rest of the users' dependencies.
|
||||
* Finally, update the containerSubject with the new container state
|
||||
* @param conf
|
||||
*/
|
||||
export function composeRoot<T extends Dependencies>(conf: DependencyConfiguration<T>) {
|
||||
//Get the current container. This should have no client or possible logger yet.
|
||||
const currentContainer = containerSubject.getValue();
|
||||
const excludeLogger = conf.exclude?.has('@sern/logger');
|
||||
if (!excludeLogger) {
|
||||
currentContainer.add({
|
||||
'@sern/logger': () => new DefaultLogging(),
|
||||
});
|
||||
}
|
||||
//Build the container based on the callback provided by the user
|
||||
const container = conf.build(currentContainer);
|
||||
//Check if the built container contains @sern/client or throw
|
||||
// a runtime exception
|
||||
Result.wrap(() => container.get('@sern/client')).expect(SernError.MissingRequired);
|
||||
|
||||
if (!excludeLogger) {
|
||||
container.get('@sern/logger')?.info({ message: 'All dependencies loaded successfully.' });
|
||||
}
|
||||
//I'm sorry little one
|
||||
containerSubject.next(container as any);
|
||||
}
|
||||
|
||||
export function useContainer<T extends Dependencies>() {
|
||||
const container = containerSubject.getValue() as Container<T, {}>;
|
||||
return <V extends (keyof T)[]>(...keys: [...V]) =>
|
||||
keys.map(key => Result.wrap(() => container.get(key)).unwrapOr(undefined)) as MapDeps<T, V>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying data structure holding all dependencies.
|
||||
* Please be careful as this only gets the client's current state.
|
||||
* Exposes some methods from iti
|
||||
*/
|
||||
export function useContainerRaw<T extends Dependencies>() {
|
||||
return containerSubject.getValue() as Container<T, {}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides all the defaults for sern to function properly.
|
||||
* The only user provided dependency needs to be @sern/client
|
||||
*/
|
||||
function defaultContainer() {
|
||||
return createContainer()
|
||||
.add({ '@sern/errors': () => new DefaultErrorHandling() })
|
||||
.add({ '@sern/store': () => new ModuleStore() })
|
||||
.add(ctx => {
|
||||
return {
|
||||
'@sern/modules': () => new DefaultModuleManager(ctx['@sern/store']),
|
||||
};
|
||||
})
|
||||
.add({ '@sern/emitter': () => new SernEmitter() }) as Container<
|
||||
Omit<Dependencies, '@sern/client' | '@sern/logger'>,
|
||||
{}
|
||||
>;
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import type { Processed } from '../../../types/handler';
|
||||
import type { AutocompleteInteraction } from 'discord.js';
|
||||
import { SernError } from '../../structures';
|
||||
import treeSearch from '../../utilities/treeSearch';
|
||||
import type { BothCommand, CommandModule, Module, SlashCommand } from '../../../types/module';
|
||||
import { EventEmitter } from 'events';
|
||||
import * as assert from 'assert';
|
||||
import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs';
|
||||
import { callPlugin } from '../operators';
|
||||
import { createResultResolver } from '../observableHandling';
|
||||
|
||||
export function dispatchCommand(module: Processed<CommandModule>, createArgs: () => unknown[]) {
|
||||
const args = createArgs();
|
||||
return {
|
||||
module,
|
||||
args,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an observable from { source }
|
||||
* @param module
|
||||
* @param source
|
||||
*/
|
||||
export function eventDispatcher(module: Processed<Module>, source: unknown) {
|
||||
assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`);
|
||||
/**
|
||||
* Sometimes fromEvent emits a single parameter, which is not an Array. This
|
||||
* operator function flattens events into an array
|
||||
* @param src
|
||||
*/
|
||||
const arrayify = pipe(
|
||||
map(event => (Array.isArray(event) ? (event as unknown[]) : [event])),
|
||||
map(args => ({ module, args })),
|
||||
);
|
||||
const createResult = createResultResolver<
|
||||
Processed<Module>,
|
||||
{ module: Processed<Module>; args: unknown[] },
|
||||
unknown[]
|
||||
>({
|
||||
createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)),
|
||||
onSuccess: ({ args }) => args,
|
||||
});
|
||||
const execute: OperatorFunction<unknown[], unknown> = pipe(
|
||||
concatMap(async args => module.execute(...args)),
|
||||
);
|
||||
return fromEvent(source, module.name).pipe(arrayify, concatMap(createResult), execute);
|
||||
}
|
||||
|
||||
export function dispatchAutocomplete(
|
||||
module: Processed<BothCommand | SlashCommand>,
|
||||
interaction: AutocompleteInteraction,
|
||||
) {
|
||||
const option = treeSearch(interaction, module.options);
|
||||
if (option !== undefined) {
|
||||
return {
|
||||
module: option.command as Processed<Module>, //autocomplete is not a true "module" warning cast!
|
||||
args: [interaction],
|
||||
};
|
||||
}
|
||||
throw Error(
|
||||
SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`,
|
||||
);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export * from './dispatchers';
|
||||
export * from './provideArgs';
|
||||
@@ -1,23 +0,0 @@
|
||||
import type { ChatInputCommandInteraction, Interaction, Message } from 'discord.js';
|
||||
import { Context } from '../../structures';
|
||||
import type { Args, SlashOptions } from '../../../types/handler';
|
||||
|
||||
/**
|
||||
* function overloads to create an arguments list for Context
|
||||
* @param wrap
|
||||
* @param messageArgs
|
||||
*/
|
||||
export function contextArgs(
|
||||
wrap: Message,
|
||||
messageArgs?: string[],
|
||||
): () => [Context, ['text', string[]]];
|
||||
export function contextArgs(wrap: Interaction): () => [Context, ['slash', SlashOptions]];
|
||||
export function contextArgs(wrap: Interaction | Message, messageArgs?: string[]) {
|
||||
const ctx = Context.wrap(wrap as ChatInputCommandInteraction | Message);
|
||||
const args = ctx.isMessage() ? ['text', messageArgs!] : ['slash', ctx.interaction.options];
|
||||
return () => [ctx, args] as [Context, Args];
|
||||
}
|
||||
|
||||
export function interactionArg<T extends Interaction>(interaction: T) {
|
||||
return () => [interaction] as [T];
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import { Subject, type Observable } from 'rxjs';
|
||||
import type { EventEmitter } from 'events';
|
||||
import type SernEmitter from '../sernEmitter';
|
||||
import type { ErrorHandling, Logging, ModuleManager } from '../contracts';
|
||||
|
||||
/**
|
||||
* why did I make this, definitely going to be changed in the future
|
||||
*/
|
||||
export abstract class EventsHandler<T> {
|
||||
protected payloadSubject = new Subject<T>();
|
||||
protected abstract discordEvent: Observable<unknown>;
|
||||
protected client: EventEmitter;
|
||||
protected emitter: SernEmitter;
|
||||
protected crashHandler: ErrorHandling;
|
||||
protected logger?: Logging;
|
||||
protected modules: ModuleManager;
|
||||
protected constructor({ containerConfig }: Wrapper) {
|
||||
const [client, emitter, crash, modules, logger] = containerConfig.get(
|
||||
'@sern/client',
|
||||
'@sern/emitter',
|
||||
'@sern/errors',
|
||||
'@sern/modules',
|
||||
'@sern/logger',
|
||||
);
|
||||
this.logger = logger as Logging | undefined;
|
||||
this.modules = modules as ModuleManager;
|
||||
this.client = client as EventEmitter;
|
||||
this.emitter = emitter as SernEmitter;
|
||||
this.crashHandler = crash as ErrorHandling;
|
||||
}
|
||||
protected abstract init(): void;
|
||||
protected abstract setState(state: T): void;
|
||||
}
|
||||
229
src/handler/events/interactionCreate.ts
Normal file
229
src/handler/events/interactionCreate.ts
Normal file
@@ -0,0 +1,229 @@
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import type { Interaction } from 'discord.js';
|
||||
import { catchError, concatMap, finalize, fromEvent, map, Observable } from 'rxjs';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import { EventsHandler } from './eventsHandler';
|
||||
import { CommandType, SernError, type ModuleStore } from '../structures';
|
||||
import { match, P } from 'ts-pattern';
|
||||
import { contextArgs, interactionArg, dispatchAutocomplete, dispatchCommand } from './dispatchers';
|
||||
import { executeModule, makeModuleExecutor } from './observableHandling';
|
||||
import type { CommandModule } from '../../types/module';
|
||||
import { handleError } from '../contracts/errorHandling';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
import type { Processed } from '../../types/handler';
|
||||
import { useContainerRaw } from '../dependencies';
|
||||
|
||||
export default class InteractionHandler extends EventsHandler<{
|
||||
event: Interaction;
|
||||
module: Processed<CommandModule>;
|
||||
}> {
|
||||
protected override discordEvent: Observable<Interaction>;
|
||||
constructor(wrapper: Wrapper) {
|
||||
super(wrapper);
|
||||
this.discordEvent = <Observable<Interaction>>fromEvent(this.client, 'interactionCreate');
|
||||
this.init();
|
||||
|
||||
this.payloadSubject
|
||||
.pipe(
|
||||
map(this.createDispatcher),
|
||||
makeModuleExecutor(module => {
|
||||
this.emitter.emit(
|
||||
'module.activate',
|
||||
SernEmitter.failure(module, SernError.PluginFailure),
|
||||
);
|
||||
}),
|
||||
concatMap(payload => executeModule(this.emitter, payload)),
|
||||
catchError(handleError(this.crashHandler, this.logger)),
|
||||
finalize(() => {
|
||||
this.logger?.info({
|
||||
message: 'interactionCreate stream closed or reached end of lifetime',
|
||||
});
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => {
|
||||
this.logger?.info({ message: 'Cleaning container and crashing' });
|
||||
});
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
override init() {
|
||||
const get = (cb: (ms: ModuleStore) => Processed<CommandModule> | undefined) => {
|
||||
return this.modules.get(cb);
|
||||
};
|
||||
/**
|
||||
* Module retrieval:
|
||||
* ModuleStores are mapped by Discord API values and modules mapped
|
||||
* by customId or command name.
|
||||
*/
|
||||
this.discordEvent.subscribe({
|
||||
next: event => {
|
||||
if (event.isMessageComponent()) {
|
||||
const module = get(ms =>
|
||||
ms.InteractionHandlers[event.componentType].get(event.customId),
|
||||
);
|
||||
this.setState({ event, module });
|
||||
} else if (event.isCommand() || event.isAutocomplete()) {
|
||||
const module = get(
|
||||
ms =>
|
||||
/**
|
||||
* try to fetch from ApplicationCommands, if nothing, try BothCommands
|
||||
* map. If nothing again,this means a slash command
|
||||
* exists on the API but not sern
|
||||
*/
|
||||
ms.ApplicationCommands[event.commandType].get(event.commandName) ??
|
||||
ms.BothCommands.get(event.commandName),
|
||||
);
|
||||
this.setState({ event, module });
|
||||
} else if (event.isModalSubmit()) {
|
||||
const module = get(ms => ms.ModalSubmit.get(event.customId));
|
||||
this.setState({ event, module });
|
||||
} else {
|
||||
throw Error('This interaction is not supported yet');
|
||||
}
|
||||
},
|
||||
error: reason => {
|
||||
this.emitter.emit('error', SernEmitter.failure(undefined, reason));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected setState(state: { event: Interaction; module: CommandModule | undefined }): void {
|
||||
if (state.module === undefined) {
|
||||
this.emitter.emit(
|
||||
'warning',
|
||||
SernEmitter.warning('Found no module for this interaction'),
|
||||
);
|
||||
} else {
|
||||
//if statement above checks already, safe cast
|
||||
this.payloadSubject.next(
|
||||
state as { event: Interaction; module: Processed<CommandModule> },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected createDispatcher({
|
||||
module,
|
||||
event,
|
||||
}: {
|
||||
event: Interaction;
|
||||
module: Processed<CommandModule>;
|
||||
}) {
|
||||
return (
|
||||
match(module)
|
||||
.with({ type: CommandType.Text }, () =>
|
||||
this.crashHandler.crash(Error(SernError.MismatchEvent)),
|
||||
)
|
||||
//P.union = either CommandType.Slash or CommandType.Both
|
||||
.with({ type: P.union(CommandType.Slash, CommandType.Both) }, module => {
|
||||
if (event.isAutocomplete()) {
|
||||
/**
|
||||
* Autocomplete is a special case that
|
||||
* must be handled separately, since it's
|
||||
* too different from regular command modules
|
||||
*/
|
||||
return dispatchAutocomplete(module, event);
|
||||
} else {
|
||||
return dispatchCommand(module, contextArgs(event));
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Every other command module takes a one argument parameter, its corresponding interaction
|
||||
* this makes this usage safe
|
||||
*/
|
||||
.otherwise(mod => dispatchCommand(mod, interactionArg(event)))
|
||||
);
|
||||
}
|
||||
}
|
||||
71
src/handler/events/messageEvent.ts
Normal file
71
src/handler/events/messageEvent.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
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);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,84 +0,0 @@
|
||||
import { EventsHandler } from './eventsHandler';
|
||||
import { catchError, concatMap, EMPTY, finalize, fromEvent, map, Observable, of } from 'rxjs';
|
||||
import { type Wrapper, type ModuleStore, SernError } from '../structures';
|
||||
import type { Message } from 'discord.js';
|
||||
import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling';
|
||||
import { fmt } from '../utilities/messageHelpers';
|
||||
import type { CommandModule, Module, TextCommand } from '../../types/module';
|
||||
import { handleError } from '../contracts/errorHandling';
|
||||
import { contextArgs, dispatchCommand } from './dispatchers';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
import type { Processed } from '../../types/handler';
|
||||
import { useContainerRaw } from '../dependencies';
|
||||
|
||||
export default class MessageHandler extends EventsHandler<{
|
||||
module: Processed<Module>;
|
||||
args: unknown[];
|
||||
}> {
|
||||
protected discordEvent: Observable<Message>;
|
||||
|
||||
public constructor(protected wrapper: Wrapper) {
|
||||
super(wrapper);
|
||||
this.discordEvent = <Observable<Message>>fromEvent(this.client, 'messageCreate');
|
||||
this.init();
|
||||
this.payloadSubject
|
||||
.pipe(
|
||||
makeModuleExecutor(module => {
|
||||
this.emitter.emit(
|
||||
'module.activate',
|
||||
SernEmitter.failure(module, SernError.PluginFailure),
|
||||
);
|
||||
}),
|
||||
concatMap(payload => executeModule(this.emitter, payload)),
|
||||
catchError(handleError(this.crashHandler, this.logger)),
|
||||
finalize(() => {
|
||||
this.logger?.info({
|
||||
message: 'messageCreate stream closed or reached end of lifetime',
|
||||
});
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => {
|
||||
this.logger?.info({ message: 'Cleaning container and crashing' });
|
||||
});
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
protected init(): void {
|
||||
if (this.wrapper.defaultPrefix === undefined) return; //for now, just ignore if prefix doesn't exist
|
||||
const { defaultPrefix } = this.wrapper;
|
||||
const get = (cb: (ms: ModuleStore) => Processed<CommandModule> | undefined) => {
|
||||
return this.modules.get(cb);
|
||||
};
|
||||
this.discordEvent
|
||||
.pipe(
|
||||
ignoreNonBot(this.wrapper.defaultPrefix),
|
||||
//This concatMap checks if module is undefined, and if it is, do not continue.
|
||||
// Synonymous to filterMap, but I haven't thought of a generic implementation for filterMap yet
|
||||
concatMap(message => {
|
||||
const [prefix, ...rest] = fmt(message, defaultPrefix);
|
||||
const module = get(
|
||||
ms => ms.TextCommands.get(prefix) ?? ms.BothCommands.get(prefix),
|
||||
);
|
||||
if (module === undefined) {
|
||||
return EMPTY;
|
||||
}
|
||||
const payload = {
|
||||
args: contextArgs(message, rest),
|
||||
module,
|
||||
};
|
||||
return of(payload);
|
||||
}),
|
||||
map(({ args, module }) => dispatchCommand(module as Processed<TextCommand>, args)),
|
||||
)
|
||||
.subscribe({
|
||||
next: value => this.setState(value),
|
||||
error: reason => this.emitter.emit('error', SernEmitter.failure(reason)),
|
||||
});
|
||||
}
|
||||
|
||||
protected setState(state: { module: Processed<Module>; args: unknown[] }) {
|
||||
this.payloadSubject.next(state);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,32 @@
|
||||
import type { Awaitable, Message } from 'discord.js';
|
||||
import { concatMap, EMPTY, from, Observable, of, pipe, tap, throwError } from 'rxjs';
|
||||
import type { SernError } from '../structures';
|
||||
import { Result } from 'ts-results-es';
|
||||
import type { AnyModule, CommandModule, EventModule, Module } from '../../types/module';
|
||||
import { _const as i } from '../utilities/functions';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
import { callPlugin, everyPluginOk, filterMapTo } from './operators';
|
||||
import type { Processed } from '../../types/handler';
|
||||
import type { ControlPlugin, VoidResult } from '../../types/plugin';
|
||||
import type { Message } from 'discord.js';
|
||||
import { Observable, 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(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignores messages from any person / bot except itself
|
||||
* @param prefix
|
||||
*/
|
||||
export function ignoreNonBot<T extends Message>(prefix: string) {
|
||||
return (src: Observable<T>) =>
|
||||
new Observable<T>(subscriber => {
|
||||
export function ignoreNonBot(prefix: string) {
|
||||
return (src: Observable<Message>) =>
|
||||
new Observable<Message>(subscriber => {
|
||||
return src.subscribe({
|
||||
next(m) {
|
||||
const messageFromHumanAndHasPrefix =
|
||||
@@ -27,19 +38,19 @@ export function ignoreNonBot<T extends Message>(prefix: string) {
|
||||
subscriber.next(m);
|
||||
}
|
||||
},
|
||||
error: e => subscriber.error(e),
|
||||
complete: () => subscriber.complete(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current value in Result stream is an error, calls callback.
|
||||
* This also extracts the Ok value from Result
|
||||
* @param cb
|
||||
* @returns Observable<{ module: T; absPath: string }>
|
||||
*/
|
||||
export function errTap<T extends AnyModule>(cb: (err: SernError) => void) {
|
||||
return (src: Observable<Result<{ module: T; absPath: string }, SernError>>) =>
|
||||
new Observable<{ module: T; absPath: string }>(subscriber => {
|
||||
export function errTap<T extends Module>(cb: (err: SernError) => void) {
|
||||
return (src: Observable<Result<{ mod: T; absPath: string }, SernError>>) =>
|
||||
new Observable<{ mod: T; absPath: string }>(subscriber => {
|
||||
return src.subscribe({
|
||||
next(value) {
|
||||
if (value.err) {
|
||||
@@ -48,108 +59,8 @@ export function errTap<T extends AnyModule>(cb: (err: SernError) => void) {
|
||||
subscriber.next(value.val);
|
||||
}
|
||||
},
|
||||
error: e => subscriber.error(e),
|
||||
complete: () => subscriber.complete(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the task in a Result as a try / catch.
|
||||
* if the task is ok, an event is emitted and the stream becomes empty
|
||||
* if the task is an error, throw an error down the stream which will be handled by catchError
|
||||
* @param emitter reference to SernEmitter that will emit a successful execution of module
|
||||
* @param module the module that will be executed with task
|
||||
* @param task the deferred execution which will be called
|
||||
*/
|
||||
export function executeModule(
|
||||
emitter: SernEmitter,
|
||||
{
|
||||
module,
|
||||
task,
|
||||
}: {
|
||||
module: Processed<Module>;
|
||||
task: () => Awaitable<unknown>;
|
||||
},
|
||||
) {
|
||||
return of(module).pipe(
|
||||
//converting the task into a promise so rxjs can resolve the Awaitable properly
|
||||
concatMap(() => Result.wrapAsync(async () => task())),
|
||||
concatMap(result => {
|
||||
if (result.ok) {
|
||||
emitter.emit('module.activate', SernEmitter.success(module));
|
||||
return EMPTY;
|
||||
} else {
|
||||
return throwError(i(SernEmitter.failure(module, result.val)));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* A higher order function that
|
||||
* - creates a stream of {@link VoidResult} { config.createStream }
|
||||
* - any failures results to { config.onFailure } being called
|
||||
* - if all results are ok, the stream is converted to { config.onSuccess }
|
||||
* emit config.onSuccess Observable
|
||||
* @param config
|
||||
* @returns receiver function for flattening a stream of data
|
||||
*/
|
||||
export function createResultResolver<
|
||||
T extends { execute: (...args: any[]) => any; onEvent : ControlPlugin[] },
|
||||
Args extends { module: T; [key: string]: unknown },
|
||||
Output,
|
||||
>(config: {
|
||||
onFailure?: (module: T) => unknown;
|
||||
onSuccess: (args: Args) => Output;
|
||||
createStream: (args: Args) => Observable<VoidResult>;
|
||||
}) {
|
||||
return (args: Args) => {
|
||||
const task$ = config.createStream(args);
|
||||
return task$.pipe(
|
||||
tap(result => {
|
||||
if (result.err) {
|
||||
config.onFailure?.(args.module);
|
||||
}
|
||||
}),
|
||||
everyPluginOk(),
|
||||
filterMapTo(() => config.onSuccess(args)),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a module's init plugins and checks for Err. If so, call { onFailure } and
|
||||
* ignore the module
|
||||
*/
|
||||
export function scanModule<
|
||||
T extends Processed<CommandModule | EventModule>,
|
||||
Args extends { module: T; absPath: string },
|
||||
>(config: { onFailure?: (module: T) => unknown; onSuccess: (module: Args) => T }) {
|
||||
return pipe(
|
||||
concatMap(
|
||||
createResultResolver({
|
||||
createStream: args => from(args.module.plugins).pipe(callPlugin(args)),
|
||||
...config,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an executable task ( execute the command ) if all control plugins are successful
|
||||
* @param onFailure emits a failure response to the SernEmitter
|
||||
*/
|
||||
export function makeModuleExecutor<
|
||||
M extends Processed<Module>,
|
||||
Args extends { module: M; args: unknown[] },
|
||||
>(onFailure: (m: M) => unknown) {
|
||||
const onSuccess = ({ args, module }: Args) => ({ task: () => module.execute(...args), module });
|
||||
return pipe(
|
||||
concatMap(
|
||||
createResultResolver({
|
||||
onFailure,
|
||||
createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)),
|
||||
onSuccess,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* This file holds sern's rxjs operators used for processing data.
|
||||
* Each function should be modular and testable, not bound to discord / sern
|
||||
* and independent of each other
|
||||
*/
|
||||
|
||||
import { concatMap, defaultIfEmpty, EMPTY, every, map, of, OperatorFunction, pipe } from 'rxjs';
|
||||
import type { AnyModule } from '../../../types/module';
|
||||
import { nameOrFilename } from '../../utilities/functions';
|
||||
import type { PluginResult, VoidResult } from '../../../types/plugin';
|
||||
import { guayin } from '../../plugins';
|
||||
import { controller } from '../../sern';
|
||||
|
||||
/**
|
||||
* if {src} is true, mapTo V, else ignore
|
||||
* @param item
|
||||
*/
|
||||
export function filterMapTo<V>(item: () => V): OperatorFunction<boolean, V> {
|
||||
return pipe(concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls any plugin with {args}.
|
||||
* @param args if an array, its spread and plugin called.
|
||||
*/
|
||||
export function callPlugin(args: unknown): OperatorFunction<
|
||||
{
|
||||
execute: (...args: unknown[]) => PluginResult;
|
||||
},
|
||||
VoidResult
|
||||
> {
|
||||
return pipe(
|
||||
concatMap(async plugin => {
|
||||
const isNewPlugin = Reflect.has(plugin, guayin);
|
||||
if (isNewPlugin) {
|
||||
if (Array.isArray(args)) {
|
||||
return plugin.execute(...args);
|
||||
}
|
||||
return plugin.execute(args);
|
||||
} else {
|
||||
return plugin.execute(args, controller);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* operator function that fill the defaults for a module
|
||||
*/
|
||||
export function defineAllFields<T extends AnyModule>() {
|
||||
const fillFields = ({ absPath, module }: { absPath: string; module: T }) => ({
|
||||
absPath,
|
||||
module: {
|
||||
name: nameOrFilename(module.name, absPath),
|
||||
description: module.description ?? '...',
|
||||
...module,
|
||||
},
|
||||
});
|
||||
return pipe(map(fillFields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the stream of results is all ok.
|
||||
*/
|
||||
export function everyPluginOk(): OperatorFunction<VoidResult, boolean> {
|
||||
return pipe(
|
||||
every(result => result.ok),
|
||||
defaultIfEmpty(true),
|
||||
);
|
||||
}
|
||||
125
src/handler/events/readyEvent.ts
Normal file
125
src/handler/events/readyEvent.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
import { EventsHandler } from './eventsHandler';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import { concatMap, fromEvent, type Observable, take } from 'rxjs';
|
||||
import * as Files from '../utilities/readFile';
|
||||
import { errTap, scanModule } from './observableHandling';
|
||||
import { CommandType, SernError, type ModuleStore } from '../structures';
|
||||
import { match } from 'ts-pattern';
|
||||
import { Result } from 'ts-results-es';
|
||||
import { ApplicationCommandType, ComponentType } from 'discord.js';
|
||||
import type { CommandModule } from '../../types/module';
|
||||
import type { Processed } from '../../types/handler';
|
||||
import type { ModuleManager } from '../contracts';
|
||||
import { _const, err, ok } from '../utilities/functions';
|
||||
import { defineAllFields } from './operators';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
|
||||
export default class ReadyHandler extends EventsHandler<{
|
||||
module: Processed<CommandModule>;
|
||||
absPath: string;
|
||||
}> {
|
||||
protected discordEvent!: Observable<{ module: CommandModule; absPath: string }>;
|
||||
constructor(wrapper: Wrapper) {
|
||||
super(wrapper);
|
||||
const ready$ = fromEvent(this.client, 'ready').pipe(take(1));
|
||||
this.discordEvent = ready$.pipe(
|
||||
concatMap(() =>
|
||||
Files.buildData<CommandModule>(wrapper.commands).pipe(
|
||||
errTap(reason => {
|
||||
this.emitter.emit(
|
||||
'module.register',
|
||||
SernEmitter.failure(undefined, reason),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
this.init();
|
||||
this.payloadSubject
|
||||
.pipe(
|
||||
scanModule({
|
||||
onFailure: module => {
|
||||
this.emitter.emit(
|
||||
'module.register',
|
||||
SernEmitter.failure(module, SernError.PluginFailure),
|
||||
);
|
||||
},
|
||||
onSuccess: ({ module }) => {
|
||||
this.emitter.emit('module.register', SernEmitter.success(module));
|
||||
return module;
|
||||
},
|
||||
}),
|
||||
)
|
||||
.subscribe(module => {
|
||||
const res = registerModule(this.modules, module as Processed<CommandModule>);
|
||||
if (res.err) {
|
||||
this.crashHandler.crash(Error(SernError.InvalidModuleType));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.discordEvent.pipe(defineAllFields()).subscribe({
|
||||
next: value => this.setState(value),
|
||||
complete: () => this.payloadSubject.unsubscribe(),
|
||||
});
|
||||
}
|
||||
protected setState(state: { absPath: string; module: Processed<CommandModule> }): void {
|
||||
this.payloadSubject.next(state);
|
||||
}
|
||||
}
|
||||
|
||||
function registerModule<T extends Processed<CommandModule>>(
|
||||
manager: ModuleManager,
|
||||
mod: T,
|
||||
): Result<void, void> {
|
||||
const name = mod.name;
|
||||
const insert = (cb: (ms: ModuleStore) => void) => {
|
||||
const set = Result.wrap(_const(manager.set(cb)));
|
||||
return set.ok ? ok() : err();
|
||||
};
|
||||
return match(mod as Processed<CommandModule>)
|
||||
.with({ type: CommandType.Text }, mod => {
|
||||
mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod)));
|
||||
return insert(ms => ms.TextCommands.set(name, mod));
|
||||
})
|
||||
.with({ type: CommandType.Slash }, mod =>
|
||||
insert(ms => ms.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.Both }, mod => {
|
||||
mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod)));
|
||||
return insert(ms => ms.BothCommands.set(name, mod));
|
||||
})
|
||||
.with({ type: CommandType.CtxUser }, mod =>
|
||||
insert(ms => ms.ApplicationCommands[ApplicationCommandType.User].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.CtxMsg }, mod =>
|
||||
insert(ms => ms.ApplicationCommands[ApplicationCommandType.Message].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.Button }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.Button].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.StringSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.StringSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.MentionableSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.MentionableSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.ChannelSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.ChannelSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.UserSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.UserSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.RoleSelect }, mod =>
|
||||
insert(ms => ms.InteractionHandlers[ComponentType.RoleSelect].set(name, mod)),
|
||||
)
|
||||
.with({ type: CommandType.Modal }, mod => insert(ms => ms.ModalSubmit.set(name, mod)))
|
||||
.otherwise(err);
|
||||
}
|
||||
@@ -1,72 +1,93 @@
|
||||
import { catchError, finalize, map, tap } from 'rxjs';
|
||||
import { buildData } from '../utilities/readFile';
|
||||
import type { Dependencies, Processed } from '../../types/handler';
|
||||
import { errTap, scanModule } from './observableHandling';
|
||||
import type { CommandModule, EventModule } from '../../types/module';
|
||||
import type { EventEmitter } from 'events';
|
||||
import SernEmitter from '../sernEmitter';
|
||||
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 { EventModule } from '../structures/module';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import { basename } from 'path';
|
||||
import { match } from 'ts-pattern';
|
||||
import type { ErrorHandling, Logging } from '../contracts';
|
||||
import { SernError, EventType, type Wrapper } from '../structures';
|
||||
import { eventDispatcher } from './dispatchers';
|
||||
import { handleError } from '../contracts/errorHandling';
|
||||
import { defineAllFields } from './operators';
|
||||
import { useContainerRaw } from '../dependencies';
|
||||
import { isDiscordEvent, isSernEvent } from '../utilities/predicates';
|
||||
import { errTap } from './observableHandling';
|
||||
|
||||
export function processEvents({ containerConfig, events }: Wrapper) {
|
||||
const [client, errorHandling, sernEmitter, logger] = containerConfig.get(
|
||||
'@sern/client',
|
||||
'@sern/errors',
|
||||
'@sern/emitter',
|
||||
'@sern/logger',
|
||||
) as [EventEmitter, ErrorHandling, SernEmitter, Logging?];
|
||||
const lazy = (k: string) => containerConfig.get(k as keyof Dependencies)[0];
|
||||
const eventStream$ = eventObservable$(events!, sernEmitter);
|
||||
/**
|
||||
* Utility function to process command plugins for all Modules
|
||||
* @param wrapper
|
||||
* @param payload
|
||||
*/
|
||||
export function processCommandPlugins<T extends DefinedCommandModule>(
|
||||
wrapper: Wrapper,
|
||||
payload: { mod: T; absPath: string },
|
||||
) {
|
||||
return payload.mod.plugins.map(plug => ({
|
||||
...plug,
|
||||
name: plug?.name ?? 'Unnamed Plugin',
|
||||
description: plug?.description ?? '...',
|
||||
execute: plug.execute(wrapper, payload, controller),
|
||||
}));
|
||||
}
|
||||
|
||||
const eventCreation$ = eventStream$.pipe(
|
||||
defineAllFields(),
|
||||
scanModule({
|
||||
onFailure: module => sernEmitter.emit('module.register', SernEmitter.success(module)),
|
||||
onSuccess: ({ module }) => {
|
||||
sernEmitter.emit(
|
||||
'module.register',
|
||||
SernEmitter.failure(module, SernError.PluginFailure),
|
||||
export function processEvents(
|
||||
wrapper: Wrapper,
|
||||
events:
|
||||
| string
|
||||
| { mod: EventModule; absPath: string }[]
|
||||
| (() => { mod: EventModule; absPath: string }[]),
|
||||
) {
|
||||
const eventStream$ = eventObservable$(wrapper, events);
|
||||
const normalize$ = eventStream$.pipe(
|
||||
map(({ mod, absPath }) => {
|
||||
return <DefinedEventModule>{
|
||||
name: mod?.name ?? Files.fmtFileName(basename(absPath)),
|
||||
description: mod?.description ?? '...',
|
||||
...mod,
|
||||
};
|
||||
}),
|
||||
);
|
||||
normalize$.subscribe(e => {
|
||||
const emitter = isSernEvent(e)
|
||||
? wrapper?.sernEmitter
|
||||
: isDiscordEvent(e)
|
||||
? wrapper.client
|
||||
: ExternalEventEmitters.get(e.emitter);
|
||||
if (emitter === undefined) {
|
||||
throw new Error(`Cannot find event emitter as it is undefined`);
|
||||
}
|
||||
//Would add sern event emitter for events loaded, attached onto sern emitter, but could lead to unwanted behavior!
|
||||
fromEvent(emitter, e.name, e.execute as SpreadParams<typeof e.execute>).subscribe();
|
||||
});
|
||||
}
|
||||
|
||||
function eventObservable$(
|
||||
{ sernEmitter }: Wrapper,
|
||||
events:
|
||||
| string
|
||||
| { mod: EventModule; absPath: string }[]
|
||||
| (() => { mod: EventModule; absPath: string }[]),
|
||||
) {
|
||||
return match(events)
|
||||
.when(Array.isArray, (arr: { mod: EventModule; absPath: string }[]) => {
|
||||
return from(arr);
|
||||
})
|
||||
.when(
|
||||
e => typeof e === 'string',
|
||||
(eventsDir: string) => {
|
||||
return buildData<EventModule>(eventsDir).pipe(
|
||||
errTap(reason =>
|
||||
sernEmitter?.emit('module.register', {
|
||||
type: 'failure',
|
||||
module: undefined,
|
||||
reason,
|
||||
}),
|
||||
),
|
||||
);
|
||||
return module;
|
||||
},
|
||||
}),
|
||||
);
|
||||
const intoDispatcher = (e: Processed<EventModule | CommandModule>) =>
|
||||
match(e)
|
||||
.with({ type: EventType.Sern }, m => eventDispatcher(m, sernEmitter))
|
||||
.with({ type: EventType.Discord }, m => eventDispatcher(m, client))
|
||||
.with({ type: EventType.External }, m => eventDispatcher(m, lazy(m.emitter)))
|
||||
.otherwise(() => errorHandling.crash(Error(SernError.InvalidModuleType)));
|
||||
|
||||
eventCreation$
|
||||
.pipe(
|
||||
map(intoDispatcher),
|
||||
/**
|
||||
* Where all events are turned on
|
||||
*/
|
||||
tap(dispatcher => dispatcher.subscribe()),
|
||||
catchError(handleError(errorHandling, logger)),
|
||||
finalize(() => {
|
||||
logger?.info({ message: 'an event module reached end of lifetime' });
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => {
|
||||
logger?.info({ message: 'Cleaning container and crashing' });
|
||||
});
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
function eventObservable$(events: string, emitter: SernEmitter) {
|
||||
return buildData<EventModule>(events).pipe(
|
||||
errTap(reason => {
|
||||
emitter.emit('module.register', SernEmitter.failure(undefined, reason));
|
||||
}),
|
||||
);
|
||||
.when(
|
||||
e => typeof e === 'function',
|
||||
(evs: () => { mod: EventModule; absPath: string }[]) => {
|
||||
return from(evs());
|
||||
},
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import type { CommandType } from '../structures/enums';
|
||||
import type { PluginType } from '../structures/enums';
|
||||
import type { ClientEvents } from 'discord.js';
|
||||
import type {
|
||||
BothCommand,
|
||||
ButtonCommand,
|
||||
ChannelSelectCommand,
|
||||
ContextMenuUser,
|
||||
DiscordEventCommand,
|
||||
ExternalEventCommand,
|
||||
MentionableSelectCommand,
|
||||
ModalSubmitCommand,
|
||||
RoleSelectCommand,
|
||||
SernEventCommand,
|
||||
SlashCommand,
|
||||
StringSelectCommand,
|
||||
TextCommand,
|
||||
UserSelectCommand,
|
||||
ContextMenuMsg,
|
||||
Module,
|
||||
} from '../../types/module';
|
||||
import type { Args, Payload, Processed, SlashOptions } from '../../types/handler';
|
||||
import type Context from '../structures/context';
|
||||
import type { MessageContextMenuCommandInteraction } from 'discord.js';
|
||||
import type {
|
||||
ButtonInteraction,
|
||||
RoleSelectMenuInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
UserContextMenuCommandInteraction,
|
||||
} from 'discord.js';
|
||||
import type {
|
||||
ChannelSelectMenuInteraction,
|
||||
MentionableSelectMenuInteraction,
|
||||
ModalSubmitInteraction,
|
||||
UserSelectMenuInteraction,
|
||||
} from 'discord.js';
|
||||
import { EventType } from '../structures/enums';
|
||||
|
||||
type CommandArgsMatrix = {
|
||||
[CommandType.Text]: {
|
||||
[PluginType.Control]: [Context, ['text', string[]]];
|
||||
[PluginType.Init]: [InitArgs<Processed<TextCommand>>];
|
||||
};
|
||||
[CommandType.Slash]: {
|
||||
[PluginType.Control]: [Context, ['slash', /* library coupled */ SlashOptions]];
|
||||
[PluginType.Init]: [InitArgs<Processed<SlashCommand>>];
|
||||
};
|
||||
[CommandType.Both]: {
|
||||
[PluginType.Control]: [Context, Args];
|
||||
[PluginType.Init]: [InitArgs<Processed<BothCommand>>];
|
||||
};
|
||||
[CommandType.CtxMsg]: {
|
||||
[PluginType.Control]: [/* library coupled */ MessageContextMenuCommandInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<ContextMenuMsg>>];
|
||||
};
|
||||
[CommandType.CtxUser]: {
|
||||
[PluginType.Control]: [/* library coupled */ UserContextMenuCommandInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<ContextMenuUser>>];
|
||||
};
|
||||
[CommandType.Button]: {
|
||||
[PluginType.Control]: [/* library coupled */ ButtonInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<ButtonCommand>>];
|
||||
};
|
||||
[CommandType.StringSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ StringSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<StringSelectCommand>>];
|
||||
};
|
||||
[CommandType.RoleSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ RoleSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<RoleSelectCommand>>];
|
||||
};
|
||||
[CommandType.ChannelSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ ChannelSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<ChannelSelectCommand>>];
|
||||
};
|
||||
[CommandType.MentionableSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ MentionableSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<MentionableSelectCommand>>];
|
||||
};
|
||||
[CommandType.UserSelect]: {
|
||||
[PluginType.Control]: [/* library coupled */ UserSelectMenuInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<UserSelectCommand>>];
|
||||
};
|
||||
[CommandType.Modal]: {
|
||||
[PluginType.Control]: [/* library coupled */ ModalSubmitInteraction];
|
||||
[PluginType.Init]: [InitArgs<Processed<ModalSubmitCommand>>];
|
||||
};
|
||||
};
|
||||
|
||||
type EventArgsMatrix = {
|
||||
[EventType.Discord]: {
|
||||
[PluginType.Control]: /* library coupled */ ClientEvents[keyof ClientEvents];
|
||||
[PluginType.Init]: [InitArgs<Processed<DiscordEventCommand>>];
|
||||
};
|
||||
[EventType.Sern]: {
|
||||
[PluginType.Control]: [Payload];
|
||||
[PluginType.Init]: [InitArgs<Processed<SernEventCommand>>];
|
||||
};
|
||||
[EventType.External]: {
|
||||
[PluginType.Control]: unknown[];
|
||||
[PluginType.Init]: [InitArgs<Processed<ExternalEventCommand>>];
|
||||
};
|
||||
};
|
||||
|
||||
export interface InitArgs<T extends Processed<Module>> {
|
||||
module: T;
|
||||
absPath: string;
|
||||
}
|
||||
|
||||
export type CommandArgs<
|
||||
I extends CommandType = CommandType,
|
||||
J extends PluginType = PluginType,
|
||||
> = CommandArgsMatrix[I][J];
|
||||
export type EventArgs<
|
||||
I extends EventType = EventType,
|
||||
J extends PluginType = PluginType,
|
||||
> = EventArgsMatrix[I][J];
|
||||
@@ -1,51 +0,0 @@
|
||||
import { CommandType, EventType, PluginType } from '../structures/enums';
|
||||
import type { Plugin, PluginResult } from '../../types/plugin';
|
||||
import type { CommandArgs, EventArgs } from './args';
|
||||
import type { ClientEvents } from 'discord.js';
|
||||
export const guayin = Symbol('twice<3');
|
||||
export function makePlugin<V extends unknown[]>(
|
||||
type: PluginType,
|
||||
execute: (...args: any[]) => any,
|
||||
): Plugin<V> {
|
||||
return {
|
||||
type,
|
||||
execute,
|
||||
[guayin]: undefined,
|
||||
} as Plugin<V>;
|
||||
}
|
||||
|
||||
export function EventInitPlugin<I extends EventType>(
|
||||
execute: (...args: EventArgs<I, PluginType.Init>) => PluginResult,
|
||||
) {
|
||||
return makePlugin(PluginType.Init, execute);
|
||||
}
|
||||
|
||||
export function CommandInitPlugin<I extends CommandType>(
|
||||
execute: (...args: CommandArgs<I, PluginType.Init>) => PluginResult,
|
||||
) {
|
||||
return makePlugin(PluginType.Init, execute);
|
||||
}
|
||||
|
||||
export function CommandControlPlugin<I extends CommandType>(
|
||||
execute: (...args: CommandArgs<I, PluginType.Control>) => PluginResult,
|
||||
) {
|
||||
return makePlugin(PluginType.Control, execute);
|
||||
}
|
||||
|
||||
export function EventControlPlugin<I extends EventType>(
|
||||
execute: (...args: EventArgs<I, PluginType.Control>) => PluginResult,
|
||||
) {
|
||||
return makePlugin(PluginType.Control, execute);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Experimental
|
||||
* A specialized function for creating control plugins with discord.js ClientEvents.
|
||||
* Will probably be moved one day!
|
||||
*/
|
||||
export function DiscordEventControlPlugin<T extends keyof ClientEvents>(
|
||||
name: T,
|
||||
execute: (...args: ClientEvents[T]) => PluginResult,
|
||||
) {
|
||||
return makePlugin(PluginType.Control, execute);
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { EventArgs, InitArgs, CommandArgs } from './args';
|
||||
export * from './createPlugin';
|
||||
186
src/handler/plugins/plugin.ts
Normal file
186
src/handler/plugins/plugin.ts
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* Plugins can be inserted on all commands and are emitted
|
||||
*
|
||||
* 1. On ready event, where all commands are loaded.
|
||||
* 2. On corresponding observable (when command triggers)
|
||||
*
|
||||
* The goal of plugins is to organize commands and
|
||||
* provide extensions to repetitive patterns
|
||||
* examples include refreshing modules,
|
||||
* categorizing commands, cooldowns, 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 { BaseModule, CommandModuleDefs, EventModuleDefs } from '../structures/module';
|
||||
import type { EventEmitter } from 'events';
|
||||
import type {
|
||||
DiscordEventCommand,
|
||||
ExternalEventCommand,
|
||||
SernEventCommand,
|
||||
} from '../structures/events';
|
||||
import type SernEmitter from '../sernEmitter';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
|
||||
export interface Controller {
|
||||
next: () => Ok<void>;
|
||||
stop: () => Err<void>;
|
||||
}
|
||||
|
||||
type BasePlugin = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: PluginType;
|
||||
}
|
||||
>;
|
||||
|
||||
export type CommandPlugin<T extends keyof CommandModuleDefs = keyof CommandModuleDefs> = {
|
||||
[K in T]: Override<
|
||||
BasePlugin,
|
||||
{
|
||||
type: PluginType.Command;
|
||||
execute: (
|
||||
wrapper: Wrapper,
|
||||
payload: {
|
||||
mod: DefinitelyDefined<CommandModuleDefs[T], 'name' | 'description'>;
|
||||
absPath: string;
|
||||
},
|
||||
controller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
}[T];
|
||||
|
||||
export type DiscordEmitterPlugin = Override<
|
||||
BasePlugin,
|
||||
{
|
||||
type: PluginType.Command;
|
||||
execute: (
|
||||
wrapper: Client,
|
||||
module: DefinitelyDefined<DiscordEventCommand, 'name' | 'description'>,
|
||||
controller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
export type ExternalEmitterPlugin<T extends EventEmitter = EventEmitter> = Override<
|
||||
BasePlugin,
|
||||
{
|
||||
type: PluginType.Command;
|
||||
execute: (
|
||||
wrapper: T,
|
||||
module: DefinitelyDefined<ExternalEventCommand, 'name' | 'description'>,
|
||||
controller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type SernEmitterPlugin = Override<
|
||||
BasePlugin,
|
||||
{
|
||||
type: PluginType.Command;
|
||||
execute: (
|
||||
wrapper: SernEmitter,
|
||||
module: DefinitelyDefined<SernEventCommand, 'name' | 'description'>,
|
||||
controller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type AutocompletePlugin = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: PluginType.Event;
|
||||
execute: (
|
||||
autocmp: AutocompleteInteraction,
|
||||
controlller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type EventPlugin<T extends keyof CommandModuleDefs = keyof CommandModuleDefs> = {
|
||||
[K in T]: Override<
|
||||
BasePlugin,
|
||||
{
|
||||
type: PluginType.Event;
|
||||
execute: (
|
||||
event: Parameters<CommandModuleDefs[K]['execute']>,
|
||||
controller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
}[T];
|
||||
|
||||
export type SernEventPlugin<T extends keyof SernEventsMapping = keyof SernEventsMapping> = Override<
|
||||
BasePlugin,
|
||||
{
|
||||
name?: T;
|
||||
type: PluginType.Event;
|
||||
execute: (
|
||||
args: SernEventsMapping[T],
|
||||
controller: Controller,
|
||||
) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ExternalEventPlugin = Override<
|
||||
BasePlugin,
|
||||
{
|
||||
type: PluginType.Event;
|
||||
execute: (args: unknown[], controller: Controller) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type DiscordEventPlugin<T extends keyof ClientEvents = keyof ClientEvents> = Override<
|
||||
BasePlugin,
|
||||
{
|
||||
name?: T;
|
||||
type: PluginType.Event;
|
||||
execute: (args: ClientEvents[T], controller: Controller) => Awaitable<Result<void, void>>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type CommandModuleNoPlugins = {
|
||||
[T in CommandType]: Omit<CommandModuleDefs[T], 'plugins' | 'onEvent'>;
|
||||
};
|
||||
export type EventModulesNoPlugins = {
|
||||
[T in EventType]: Omit<EventModuleDefs[T], 'plugins' | 'onEvent'>;
|
||||
};
|
||||
/**
|
||||
* Event Module Event Plugins
|
||||
*/
|
||||
export type EventModuleEventPluginDefs = {
|
||||
[EventType.Discord]: DiscordEventPlugin;
|
||||
[EventType.Sern]: SernEventPlugin;
|
||||
[EventType.External]: ExternalEventPlugin;
|
||||
};
|
||||
|
||||
/**
|
||||
* Event Module Command Plugins
|
||||
*/
|
||||
export type EventModuleCommandPluginDefs = {
|
||||
[EventType.Discord]: DiscordEmitterPlugin;
|
||||
[EventType.Sern]: SernEmitterPlugin;
|
||||
[EventType.External]: ExternalEmitterPlugin;
|
||||
};
|
||||
|
||||
export type EventModulePlugin<T extends EventType> =
|
||||
| EventModuleEventPluginDefs[T]
|
||||
| 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
|
||||
*/
|
||||
export type InputCommandModule = {
|
||||
[T in CommandType]: CommandModuleNoPlugins[T] & { plugins?: CommandModulePlugin<T>[] };
|
||||
}[CommandType];
|
||||
|
||||
export type InputEventModule = {
|
||||
[T in EventType]: EventModulesNoPlugins[T] & { plugins?: EventModulePlugin<T>[] };
|
||||
}[EventType];
|
||||
@@ -1,131 +1,101 @@
|
||||
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 { ExternalEventEmitters } from './utilities/readFile';
|
||||
import type { EventEmitter } from 'events';
|
||||
import { processEvents } from './events/userDefinedEventsHandling';
|
||||
import { CommandType, EventType, PluginType } from './structures/enums';
|
||||
import type { AnyEventPlugin, ControlPlugin, InitPlugin, Plugin } from '../types/plugin';
|
||||
import InteractionHandler from './events/interactionHandler';
|
||||
import ReadyHandler from './events/readyHandler';
|
||||
import MessageHandler from './events/messageHandler';
|
||||
import type { CommandModule, EventModule } from './structures/module';
|
||||
import { EventType, PluginType } from './structures/enums';
|
||||
import type {
|
||||
CommandModule,
|
||||
CommandModuleDefs,
|
||||
EventModule,
|
||||
EventModuleDefs,
|
||||
InputCommand,
|
||||
InputEvent,
|
||||
} from '../types/module';
|
||||
import type { Dependencies, DependencyConfiguration } from '../types/handler';
|
||||
import { composeRoot, useContainer } from './dependencies/provider';
|
||||
import type { Logging } from './contracts';
|
||||
import { err, ok, partition } from './utilities/functions';
|
||||
import type { Awaitable, ClientEvents } from 'discord.js';
|
||||
CommandPlugin,
|
||||
EventModuleCommandPluginDefs,
|
||||
EventModuleEventPluginDefs,
|
||||
EventPlugin,
|
||||
InputCommandModule,
|
||||
InputEventModule,
|
||||
} from './plugins/plugin';
|
||||
import { SernError } from './structures/errors';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param wrapper Options to pass into sern.
|
||||
* Function to start the handler up
|
||||
* @example
|
||||
* ```ts title="src/index.ts"
|
||||
* Sern.init({
|
||||
* defaultPrefix: '!',
|
||||
* commands: 'dist/commands',
|
||||
* events: 'dist/events',
|
||||
* containerConfig : {
|
||||
* get: useContainer
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @param wrapper options to pass into sern.
|
||||
* Function to start the handler up.
|
||||
*/
|
||||
export function init(wrapper: Wrapper) {
|
||||
const logger = wrapper.containerConfig.get('@sern/logger')[0] as Logging | undefined;
|
||||
const startTime = performance.now();
|
||||
const { events } = wrapper;
|
||||
if (events !== undefined) {
|
||||
processEvents(wrapper);
|
||||
processEvents(wrapper, events);
|
||||
}
|
||||
new ReadyHandler(wrapper);
|
||||
new MessageHandler(wrapper);
|
||||
new InteractionHandler(wrapper);
|
||||
const endTime = performance.now();
|
||||
logger?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` });
|
||||
onReady(wrapper);
|
||||
onMessageCreate(wrapper);
|
||||
onInteractionCreate(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* The object passed into every plugin to control a command's behavior
|
||||
*
|
||||
* @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.
|
||||
* As there are infinite possibilities to adding external event emitters,
|
||||
* Most types aren't provided and are as narrow as possibly can.
|
||||
* @example
|
||||
* ```
|
||||
* Sern.addExternal(new Level())
|
||||
* ```
|
||||
* ```
|
||||
* // 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)) {
|
||||
throw Error(`${emitter.constructor.name} already exists!`);
|
||||
}
|
||||
ExternalEventEmitters.set(emitter.constructor.name, emitter);
|
||||
}
|
||||
|
||||
export const controller = {
|
||||
next: ok,
|
||||
stop: err,
|
||||
next: () => Ok.EMPTY,
|
||||
stop: () => Err.EMPTY,
|
||||
};
|
||||
|
||||
/**
|
||||
* The wrapper function to define command modules for sern
|
||||
* @param mod
|
||||
*/
|
||||
export function commandModule(mod: InputCommand): CommandModule {
|
||||
const [onEvent, plugins] = partition(
|
||||
mod.plugins ?? [],
|
||||
el => (el as Plugin).type === PluginType.Control,
|
||||
);
|
||||
export function commandModule(mod: InputCommandModule): CommandModule {
|
||||
const onEvent: EventPlugin[] = [];
|
||||
const plugins: CommandPlugin[] = [];
|
||||
for (const pl of mod.plugins ?? []) {
|
||||
if (pl.type === PluginType.Event) {
|
||||
onEvent.push(pl);
|
||||
} else {
|
||||
plugins.push(pl as CommandPlugin);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...mod,
|
||||
onEvent,
|
||||
plugins,
|
||||
} as CommandModule;
|
||||
}
|
||||
/**
|
||||
* The wrapper function to define event modules for sern
|
||||
* @param mod
|
||||
*/
|
||||
export function eventModule(mod: InputEvent): EventModule {
|
||||
const [onEvent, plugins] = partition(
|
||||
mod.plugins ?? [],
|
||||
el => (el as Plugin).type === PluginType.Control,
|
||||
);
|
||||
export function eventModule(mod: InputEventModule): EventModule {
|
||||
const onEvent: EventModuleEventPluginDefs[EventType][] = [];
|
||||
const plugins: EventModuleCommandPluginDefs[EventType][] = [];
|
||||
const hasPlugins = mod.plugins && mod.plugins.length > 0;
|
||||
if (hasPlugins) {
|
||||
throw Error(
|
||||
SernError.NotSupportedYet + `: Plugins on event listeners are not supported yet`,
|
||||
);
|
||||
}
|
||||
return {
|
||||
...mod,
|
||||
onEvent,
|
||||
plugins,
|
||||
} as EventModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create event modules from discord.js client events,
|
||||
* This is an {@link eventModule} for discord events,
|
||||
* where typings can be very bad.
|
||||
* @param mod
|
||||
*/
|
||||
export function discordEvent<T extends keyof ClientEvents>(mod: {
|
||||
name: T;
|
||||
plugins?: AnyEventPlugin[];
|
||||
execute: (...args: ClientEvents[T]) => Awaitable<unknown>;
|
||||
}) {
|
||||
return eventModule({ type: EventType.Discord, ...mod });
|
||||
}
|
||||
/**
|
||||
* @param conf a configuration for creating your project dependencies
|
||||
*/
|
||||
export function makeDependencies<T extends Dependencies>(conf: DependencyConfiguration<T>) {
|
||||
//Until there are more optional dependencies, just check if the logger exists
|
||||
composeRoot(conf);
|
||||
return useContainer<T>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Experimental
|
||||
* Will be refactored / changed in future
|
||||
*/
|
||||
export abstract class CommandExecutable<Type extends CommandType> {
|
||||
abstract type: Type;
|
||||
plugins: InitPlugin[] = [];
|
||||
onEvent: ControlPlugin[] = [];
|
||||
abstract execute: CommandModuleDefs[Type]['execute'];
|
||||
}
|
||||
/**@Experimental
|
||||
* Will be refactored in future
|
||||
*/
|
||||
export abstract class EventExecutable<Type extends EventType> {
|
||||
abstract type: Type;
|
||||
plugins: InitPlugin[] = [];
|
||||
onEvent: ControlPlugin[] = [];
|
||||
abstract execute: EventModuleDefs[Type]['execute'];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import type { Payload, SernEventsMapping } from '../types/handler';
|
||||
import { PayloadType } from './structures';
|
||||
import type { Module } from '../types/module';
|
||||
import type { SernEventsMapping } from '../types/handler';
|
||||
|
||||
class SernEmitter extends EventEmitter {
|
||||
/**
|
||||
@@ -29,7 +27,7 @@ class SernEmitter extends EventEmitter {
|
||||
/**
|
||||
* Listening to sern events with on. This event stays on until a crash or a normal exit
|
||||
* @param eventName
|
||||
* @param args the arguments for emitting the eventName
|
||||
* @param args the arguments for emitting the { eventName }
|
||||
*/
|
||||
public override emit<T extends keyof SernEventsMapping>(
|
||||
eventName: T,
|
||||
@@ -37,49 +35,6 @@ class SernEmitter extends EventEmitter {
|
||||
): boolean {
|
||||
return super.emit(eventName, ...args);
|
||||
}
|
||||
private static payload<T extends Payload>(
|
||||
type: PayloadType,
|
||||
module?: Module,
|
||||
reason?: unknown,
|
||||
) {
|
||||
return { type, module, reason } as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a compliant SernEmitter failure payload
|
||||
* @param module
|
||||
* @param reason
|
||||
*/
|
||||
static failure(module?: Module, reason?: unknown) {
|
||||
//The generic cast Payload & { type : PayloadType.* } coerces the type to be a failure payload
|
||||
// same goes to the other methods below
|
||||
return SernEmitter.payload<Payload & { type: PayloadType.Failure }>(
|
||||
PayloadType.Failure,
|
||||
module,
|
||||
reason,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Creates a compliant SernEmitter module success payload
|
||||
* @param module
|
||||
*/
|
||||
static success(module: Module) {
|
||||
return SernEmitter.payload<Payload & { type: PayloadType.Success }>(
|
||||
PayloadType.Success,
|
||||
module,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Creates a compliant SernEmitter module warning payload
|
||||
* @param reason
|
||||
*/
|
||||
static warning(reason: unknown) {
|
||||
return SernEmitter.payload<Payload & { type: PayloadType.Warning }>(
|
||||
PayloadType.Warning,
|
||||
undefined,
|
||||
reason,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SernEmitter;
|
||||
|
||||
@@ -1,106 +1,161 @@
|
||||
import type { APIGuildMember } from 'discord-api-types/v10';
|
||||
import type {
|
||||
ChatInputCommandInteraction,
|
||||
Client,
|
||||
Guild,
|
||||
GuildMember,
|
||||
InteractionReplyOptions,
|
||||
Message,
|
||||
ReplyMessageOptions,
|
||||
Snowflake,
|
||||
MessageReplyOptions,
|
||||
TextBasedChannel,
|
||||
User,
|
||||
} from 'discord.js';
|
||||
import { Result as Either, Ok as Left, Err as Right } from 'ts-results-es';
|
||||
import type { ReplyOptions } from '../../types/handler';
|
||||
import { None, Option, Some } from 'ts-results';
|
||||
import type { Nullish } from '../../types/handler';
|
||||
import { ExternallyUsed } from '../utilities/externallyUsed';
|
||||
import { SernError } from './errors';
|
||||
|
||||
function safeUnwrap<T>(res: Either<T, T>) {
|
||||
return res.val;
|
||||
function firstSome<T>(...args: Option<T>[]): Nullish<T> {
|
||||
for (const op of args) {
|
||||
if (op.some) return op.val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//Could I refactor with Either monad?
|
||||
/**
|
||||
* Provides values shared between
|
||||
* Message and ChatInputCommandInteraction
|
||||
*/
|
||||
export default class Context {
|
||||
private constructor(private ctx: Either<Message, ChatInputCommandInteraction>) {}
|
||||
private constructor(
|
||||
private oMsg: Option<Message> = None,
|
||||
private oInterac: Option<ChatInputCommandInteraction> = None,
|
||||
) {
|
||||
this.oMsg = oMsg;
|
||||
this.oInterac = oInterac;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getting the Message object. Crashes if module type is
|
||||
* CommandType.Slash or the event fired in a Both command was
|
||||
* ChatInputCommandInteraction
|
||||
*/
|
||||
@ExternallyUsed
|
||||
public get message() {
|
||||
return this.ctx.expect(SernError.MismatchEvent);
|
||||
return this.oMsg.expect(SernError.MismatchEvent);
|
||||
}
|
||||
/**
|
||||
* Getting the ChatInputCommandInteraction object. Crashes if module type is
|
||||
* CommandType.Text or the event fired in a Both command was
|
||||
* Message
|
||||
*/
|
||||
@ExternallyUsed
|
||||
public get interaction() {
|
||||
return this.ctx.expectErr(SernError.MismatchEvent);
|
||||
return this.oInterac.expect(SernError.MismatchEvent);
|
||||
}
|
||||
|
||||
@ExternallyUsed
|
||||
public get id(): Snowflake {
|
||||
return this.ctx.val.id;
|
||||
return firstSome(
|
||||
this.oInterac.map(i => i.id),
|
||||
this.oMsg.map(m => m.id),
|
||||
)!;
|
||||
}
|
||||
|
||||
public get channel() {
|
||||
return this.ctx.val.channel;
|
||||
@ExternallyUsed
|
||||
public get channel(): Nullish<TextBasedChannel> {
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.channel),
|
||||
this.oInterac.map(i => i.channel),
|
||||
);
|
||||
}
|
||||
/**
|
||||
* If context is holding a message, message.author
|
||||
* else, interaction.user
|
||||
*/
|
||||
|
||||
@ExternallyUsed
|
||||
public get user(): User {
|
||||
return safeUnwrap(this.ctx.map(m => m.author).mapErr(i => i.user));
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.author),
|
||||
this.oInterac.map(i => i.user),
|
||||
)!;
|
||||
}
|
||||
|
||||
@ExternallyUsed
|
||||
public get createdTimestamp(): number {
|
||||
return this.ctx.val.createdTimestamp;
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.createdTimestamp),
|
||||
this.oInterac.map(i => i.createdTimestamp),
|
||||
)!;
|
||||
}
|
||||
|
||||
public get guild() {
|
||||
return this.ctx.val.guild;
|
||||
@ExternallyUsed
|
||||
public get guild(): Guild {
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.guild),
|
||||
this.oInterac.map(i => i.guild),
|
||||
)!;
|
||||
}
|
||||
|
||||
public get guildId() {
|
||||
return this.ctx.val.guildId;
|
||||
@ExternallyUsed
|
||||
public get guildId(): Snowflake {
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.guildId),
|
||||
this.oInterac.map(i => i.guildId),
|
||||
)!;
|
||||
}
|
||||
|
||||
/*
|
||||
* interactions can return APIGuildMember if the guild it is emitted from is not cached
|
||||
*/
|
||||
public get member() {
|
||||
return this.ctx.val.member;
|
||||
@ExternallyUsed
|
||||
public get member(): Nullish<GuildMember | APIGuildMember> {
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.member),
|
||||
this.oInterac.map(i => i.member),
|
||||
);
|
||||
}
|
||||
|
||||
@ExternallyUsed
|
||||
public get client(): Client {
|
||||
return this.ctx.val.client;
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.client),
|
||||
this.oInterac.map(i => i.client),
|
||||
)!;
|
||||
}
|
||||
|
||||
@ExternallyUsed
|
||||
public get inGuild(): boolean {
|
||||
return this.ctx.val.inGuild();
|
||||
}
|
||||
public isMessage() {
|
||||
return this.ctx.map(() => true).unwrapOr(false);
|
||||
}
|
||||
|
||||
public isSlash() {
|
||||
return !this.isMessage();
|
||||
return firstSome(
|
||||
this.oMsg.map(m => m.inGuild()),
|
||||
this.oInterac.map(i => i.inGuild()),
|
||||
)!;
|
||||
}
|
||||
|
||||
static wrap(wrappable: ChatInputCommandInteraction | Message): Context {
|
||||
if ('token' in wrappable) {
|
||||
return new Context(Right(wrappable));
|
||||
return new Context(None, Some(wrappable));
|
||||
}
|
||||
return new Context(Left(wrappable));
|
||||
return new Context(Some(wrappable), None);
|
||||
}
|
||||
|
||||
public reply(content: ReplyOptions) {
|
||||
return safeUnwrap(
|
||||
this.ctx
|
||||
.map(m => m.reply(content as string | MessageReplyOptions))
|
||||
.mapErr(i =>
|
||||
i.reply(content as string | InteractionReplyOptions).then(() => i.fetchReply()),
|
||||
),
|
||||
);
|
||||
@ExternallyUsed
|
||||
public isEmpty() {
|
||||
return this.oMsg.none && this.oInterac.none;
|
||||
}
|
||||
//Make queueable
|
||||
@ExternallyUsed
|
||||
public reply(
|
||||
content: string | Omit<InteractionReplyOptions, 'fetchReply'> | ReplyMessageOptions,
|
||||
) {
|
||||
return firstSome(
|
||||
this.oInterac.map(i => {
|
||||
return i
|
||||
.reply(content as string | InteractionReplyOptions)
|
||||
.then(() => i.fetchReply());
|
||||
}),
|
||||
this.oMsg.map(m => {
|
||||
return m.reply(content as string | ReplyMessageOptions);
|
||||
}),
|
||||
)!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,139 +1,26 @@
|
||||
/**
|
||||
* A bitfield that discriminates command modules
|
||||
* @enum { number }
|
||||
* @example
|
||||
* ```ts
|
||||
* export default commandModule({
|
||||
* // highlight-next-line
|
||||
* type : CommandType.Text,
|
||||
* name : 'a text command'
|
||||
* execute(message) {
|
||||
* console.log(message.content)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @enum { number };
|
||||
*/
|
||||
export enum CommandType {
|
||||
/**
|
||||
* The CommandType for text commands
|
||||
*/
|
||||
Text = 1,
|
||||
/**
|
||||
* The CommandType for slash commands
|
||||
*/
|
||||
Slash = 2,
|
||||
/**
|
||||
* The CommandType for hybrid commands, text and slash
|
||||
*/
|
||||
Both = 3,
|
||||
/**
|
||||
* The CommandType for UserContextMenuInteraction commands
|
||||
*/
|
||||
CtxUser = 4,
|
||||
/**
|
||||
* The CommandType for MessageContextMenuInteraction commands
|
||||
*/
|
||||
CtxMsg = 8,
|
||||
/**
|
||||
* The CommandType for ButtonInteraction commands
|
||||
*/
|
||||
Button = 16,
|
||||
/**
|
||||
* The CommandType for StringSelectMenuInteraction commands
|
||||
*/
|
||||
StringSelect = 32,
|
||||
/**
|
||||
* The CommandType for ModalSubmitInteraction commands
|
||||
*/
|
||||
Modal = 64,
|
||||
/**
|
||||
* The CommandType for the other SelectMenuInteractions
|
||||
*/
|
||||
ChannelSelect = 256,
|
||||
MentionableSelect = 512,
|
||||
RoleSelect = 1024,
|
||||
UserSelect = 2048,
|
||||
enum CommandType {
|
||||
Text = 0b00000000001,
|
||||
Slash = 0b00000000010,
|
||||
Both = 0b0000011,
|
||||
MenuUser = 0b00000000100,
|
||||
MenuMsg = 0b0000001000,
|
||||
Button = 0b00000010000,
|
||||
MenuSelect = 0b00000100000,
|
||||
Modal = 0b00001000000,
|
||||
}
|
||||
|
||||
/**
|
||||
* A bitfield that discriminates event modules
|
||||
* @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 = 1,
|
||||
/**
|
||||
* The EventType for handling sern events
|
||||
*/
|
||||
Sern = 2,
|
||||
/**
|
||||
* The EventType for handling external events.
|
||||
* Could be for example, `process` events, database events
|
||||
*/
|
||||
External = 3,
|
||||
enum EventType {
|
||||
Discord = 0b01,
|
||||
Sern = 0b10,
|
||||
External = 0b11,
|
||||
}
|
||||
|
||||
/**
|
||||
* A bitfield that discriminates plugins
|
||||
* @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 InitPlugins
|
||||
*/
|
||||
Init = 1,
|
||||
/**
|
||||
* @deprecated
|
||||
* Use PluginType.Init instead
|
||||
*/
|
||||
Command = 1,
|
||||
/**
|
||||
* @deprecated
|
||||
* Use PluginType.Control instead
|
||||
*/
|
||||
Event = 2,
|
||||
/**
|
||||
* The PluginType for EventPlugins
|
||||
*/
|
||||
Control = 2,
|
||||
}
|
||||
/**
|
||||
* @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',
|
||||
enum PluginType {
|
||||
Command = 0b01,
|
||||
Event = 0b10,
|
||||
}
|
||||
|
||||
export { CommandType, PluginType, EventType };
|
||||
|
||||
@@ -1,38 +1,9 @@
|
||||
/**
|
||||
* @enum { string }
|
||||
*/
|
||||
export enum SernError {
|
||||
/**
|
||||
* 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!
|
||||
*/
|
||||
NonValidModuleType = 'Detected an unknown module type',
|
||||
UndefinedModule = `A module could not be detected at`,
|
||||
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`,
|
||||
/**
|
||||
* Required Dependency not found
|
||||
*/
|
||||
MissingRequired = `@sern/client is required but was not found`,
|
||||
}
|
||||
|
||||
45
src/handler/structures/events.ts
Normal file
45
src/handler/structures/events.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { Override, SernEventsMapping } from '../../types/handler';
|
||||
import type { BaseModule } from './module';
|
||||
import type {
|
||||
DiscordEmitterPlugin,
|
||||
DiscordEventPlugin,
|
||||
ExternalEmitterPlugin,
|
||||
ExternalEventPlugin,
|
||||
SernEmitterPlugin,
|
||||
SernEventPlugin,
|
||||
} from '../plugins/plugin';
|
||||
import type { Awaitable, ClientEvents } from 'discord.js';
|
||||
import type { EventType } from './enums';
|
||||
|
||||
export type SernEventCommand<T extends keyof SernEventsMapping = keyof SernEventsMapping> =
|
||||
Override<
|
||||
BaseModule,
|
||||
{
|
||||
name?: T;
|
||||
type: EventType.Sern;
|
||||
onEvent: SernEventPlugin[];
|
||||
plugins: SernEmitterPlugin[];
|
||||
execute(...args: SernEventsMapping[T]): Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
export type DiscordEventCommand<T extends keyof ClientEvents = keyof ClientEvents> = Override<
|
||||
BaseModule,
|
||||
{
|
||||
name?: T;
|
||||
type: EventType.Discord;
|
||||
onEvent: DiscordEventPlugin[];
|
||||
plugins: DiscordEmitterPlugin[];
|
||||
execute(...args: ClientEvents[T]): Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ExternalEventCommand = Override<
|
||||
BaseModule,
|
||||
{
|
||||
emitter: string;
|
||||
type: EventType.External;
|
||||
onEvent: ExternalEventPlugin[];
|
||||
plugins: ExternalEmitterPlugin[];
|
||||
execute(...args: unknown[]): Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
@@ -1,6 +0,0 @@
|
||||
import Context from './context';
|
||||
import type Wrapper from './wrapper';
|
||||
import { ModuleStore } from './moduleStore';
|
||||
export * from './errors';
|
||||
export * from './enums';
|
||||
export { Context, Wrapper, ModuleStore };
|
||||
212
src/handler/structures/module.ts
Normal file
212
src/handler/structures/module.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import type {
|
||||
ApplicationCommandAttachmentOption,
|
||||
ApplicationCommandChannelOptionData,
|
||||
ApplicationCommandChoicesData,
|
||||
ApplicationCommandNonOptionsData,
|
||||
ApplicationCommandNumericOptionData,
|
||||
ApplicationCommandOptionData,
|
||||
ApplicationCommandOptionType,
|
||||
ApplicationCommandSubCommandData,
|
||||
ApplicationCommandSubGroupData,
|
||||
AutocompleteInteraction,
|
||||
Awaitable,
|
||||
BaseApplicationCommandOptionsData,
|
||||
ButtonInteraction,
|
||||
MessageContextMenuCommandInteraction,
|
||||
ModalSubmitInteraction,
|
||||
SelectMenuInteraction,
|
||||
UserContextMenuCommandInteraction,
|
||||
} from 'discord.js';
|
||||
import type { Args, Override, SlashOptions } from '../../types/handler';
|
||||
import type { AutocompletePlugin, CommandPlugin, EventPlugin } from '../plugins/plugin';
|
||||
import type Context from './context';
|
||||
import { CommandType, EventType, PluginType } from './enums';
|
||||
import type { DiscordEventCommand, ExternalEventCommand, SernEventCommand } from './events';
|
||||
|
||||
export interface BaseModule {
|
||||
type: CommandType | PluginType;
|
||||
name?: string;
|
||||
description?: string;
|
||||
execute: (ctx: Context, args: Args) => Awaitable<void | unknown>;
|
||||
}
|
||||
|
||||
//possible refactoring types into interfaces and not types
|
||||
export type TextCommand = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.Text;
|
||||
onEvent: EventPlugin<CommandType.Text>[]; //maybe allow BothPlugins for this also?
|
||||
plugins: CommandPlugin[]; //maybe allow BothPlugins for this also?
|
||||
alias?: string[];
|
||||
execute: (ctx: Context, args: ['text', string[]]) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type SlashCommand = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.Slash;
|
||||
onEvent: EventPlugin<CommandType.Slash>[]; //maybe allow BothPlugins for this also?
|
||||
plugins: CommandPlugin[]; //maybe allow BothPlugins for this also?
|
||||
options?: SernOptionsData[];
|
||||
execute: (ctx: Context, args: ['slash', SlashOptions]) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type BothCommand = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.Both;
|
||||
onEvent: EventPlugin<CommandType.Both>[];
|
||||
plugins: CommandPlugin[];
|
||||
alias?: string[];
|
||||
options?: SernOptionsData[];
|
||||
execute: (ctx: Context, args: Args) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ContextMenuUser = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.MenuUser;
|
||||
onEvent: EventPlugin<CommandType.MenuUser>[];
|
||||
plugins: CommandPlugin[];
|
||||
execute: (ctx: UserContextMenuCommandInteraction) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ContextMenuMsg = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.MenuMsg;
|
||||
onEvent: EventPlugin<CommandType.MenuMsg>[];
|
||||
plugins: CommandPlugin[];
|
||||
execute: (ctx: MessageContextMenuCommandInteraction) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ButtonCommand = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.Button;
|
||||
onEvent: EventPlugin<CommandType.Button>[];
|
||||
plugins: CommandPlugin[];
|
||||
execute: (ctx: ButtonInteraction) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type SelectMenuCommand = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.MenuSelect;
|
||||
onEvent: EventPlugin<CommandType.MenuSelect>[];
|
||||
plugins: CommandPlugin[];
|
||||
execute: (ctx: SelectMenuInteraction) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type ModalSubmitCommand = Override<
|
||||
BaseModule,
|
||||
{
|
||||
type: CommandType.Modal;
|
||||
onEvent: EventPlugin<CommandType.Modal>[];
|
||||
plugins: CommandPlugin[];
|
||||
execute: (ctx: ModalSubmitInteraction) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
|
||||
// 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,
|
||||
{
|
||||
name?: never;
|
||||
description?: never;
|
||||
type?: never;
|
||||
onEvent: AutocompletePlugin[];
|
||||
execute: (ctx: AutocompleteInteraction) => Awaitable<void | unknown>;
|
||||
}
|
||||
>;
|
||||
export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand;
|
||||
export type CommandModule =
|
||||
| TextCommand
|
||||
| SlashCommand
|
||||
| BothCommand
|
||||
| ContextMenuUser
|
||||
| ContextMenuMsg
|
||||
| ButtonCommand
|
||||
| SelectMenuCommand
|
||||
| ModalSubmitCommand;
|
||||
|
||||
export type Module = CommandModule | EventModule;
|
||||
|
||||
//https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union
|
||||
// Explicit Module Definitions for mapping
|
||||
export type CommandModuleDefs = {
|
||||
[CommandType.Text]: TextCommand;
|
||||
[CommandType.Slash]: SlashCommand;
|
||||
[CommandType.Both]: BothCommand;
|
||||
[CommandType.MenuMsg]: ContextMenuMsg;
|
||||
[CommandType.MenuUser]: ContextMenuUser;
|
||||
[CommandType.Button]: ButtonCommand;
|
||||
[CommandType.MenuSelect]: SelectMenuCommand;
|
||||
[CommandType.Modal]: ModalSubmitCommand;
|
||||
};
|
||||
|
||||
export type EventModuleDefs = {
|
||||
[EventType.Sern]: SernEventCommand;
|
||||
[EventType.Discord]: DiscordEventCommand;
|
||||
[EventType.External]: ExternalEventCommand;
|
||||
};
|
||||
|
||||
//TODO: support deeply nested Autocomplete
|
||||
// objective: construct union of ApplicationCommandOptionData change any Autocomplete data
|
||||
// into Sern autocomplete data.
|
||||
|
||||
export type SernAutocompleteData = Override<
|
||||
BaseApplicationCommandOptionsData,
|
||||
{
|
||||
autocomplete: true;
|
||||
type:
|
||||
| ApplicationCommandOptionType.String
|
||||
| ApplicationCommandOptionType.Number
|
||||
| ApplicationCommandOptionType.Integer;
|
||||
command: AutocompleteCommand;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Type that just uses SernAutocompleteData and not regular autocomplete
|
||||
*/
|
||||
export type BaseOptions =
|
||||
| ApplicationCommandChoicesData
|
||||
| ApplicationCommandNonOptionsData
|
||||
| ApplicationCommandChannelOptionData
|
||||
| ApplicationCommandNumericOptionData
|
||||
| ApplicationCommandAttachmentOption
|
||||
| SernAutocompleteData;
|
||||
|
||||
export type SernSubCommandData = Override<
|
||||
Omit<BaseApplicationCommandOptionsData, 'required'>,
|
||||
{
|
||||
type: ApplicationCommandOptionType.Subcommand;
|
||||
options?: BaseOptions[];
|
||||
}
|
||||
>;
|
||||
|
||||
export type SernSubCommandGroupData = Override<
|
||||
Omit<BaseApplicationCommandOptionsData, 'required'>,
|
||||
{
|
||||
type: ApplicationCommandOptionType.SubcommandGroup;
|
||||
options?: SernSubCommandData[];
|
||||
}
|
||||
>;
|
||||
|
||||
export type SernOptionsData<U extends ApplicationCommandOptionData = ApplicationCommandOptionData> =
|
||||
U extends ApplicationCommandSubCommandData
|
||||
? SernSubCommandData
|
||||
: U extends ApplicationCommandSubGroupData
|
||||
? SernSubCommandGroupData
|
||||
: BaseOptions;
|
||||
@@ -1,26 +0,0 @@
|
||||
import type { CommandModule } from '../../types/module';
|
||||
import { ApplicationCommandType, ComponentType } from 'discord.js';
|
||||
import type { Processed } from '../../types/handler';
|
||||
|
||||
/**
|
||||
* Storing all command modules
|
||||
* This dependency is usually injected into ModuleManager
|
||||
*/
|
||||
export class ModuleStore {
|
||||
readonly BothCommands = new Map<string, Processed<CommandModule>>();
|
||||
readonly ApplicationCommands = {
|
||||
[ApplicationCommandType.User]: new Map<string, Processed<CommandModule>>(),
|
||||
[ApplicationCommandType.Message]: new Map<string, Processed<CommandModule>>(),
|
||||
[ApplicationCommandType.ChatInput]: new Map<string, Processed<CommandModule>>(),
|
||||
};
|
||||
readonly ModalSubmit = new Map<string, Processed<CommandModule>>();
|
||||
readonly TextCommands = new Map<string, Processed<CommandModule>>();
|
||||
readonly InteractionHandlers = {
|
||||
[ComponentType.Button]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.StringSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.ChannelSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.MentionableSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.RoleSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
[ComponentType.UserSelect]: new Map<string, Processed<CommandModule>>(),
|
||||
};
|
||||
}
|
||||
28
src/handler/structures/structxports.ts
Normal file
28
src/handler/structures/structxports.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import Context from './context';
|
||||
import type {
|
||||
BothCommand,
|
||||
Module,
|
||||
SlashCommand,
|
||||
TextCommand,
|
||||
SernOptionsData,
|
||||
BaseOptions,
|
||||
SernAutocompleteData,
|
||||
SernSubCommandData,
|
||||
SernSubCommandGroupData,
|
||||
} from './module';
|
||||
import type Wrapper from './wrapper';
|
||||
|
||||
export * from './enums';
|
||||
export {
|
||||
Context,
|
||||
SlashCommand,
|
||||
TextCommand,
|
||||
BothCommand,
|
||||
Module,
|
||||
Wrapper,
|
||||
SernOptionsData,
|
||||
BaseOptions,
|
||||
SernAutocompleteData,
|
||||
SernSubCommandData,
|
||||
SernSubCommandGroupData,
|
||||
};
|
||||
@@ -1,15 +1,25 @@
|
||||
import type { Dependencies } from '../../types/handler';
|
||||
import type { Client } from 'discord.js';
|
||||
import type SernEmitter from '../sernEmitter';
|
||||
import type { EventModule } from './module';
|
||||
|
||||
/**
|
||||
* An object to be passed into Sern#init() function.
|
||||
* An object to be passed into Sern.Handler constructor.
|
||||
* @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 {
|
||||
readonly client: Client;
|
||||
readonly sernEmitter?: SernEmitter;
|
||||
readonly defaultPrefix?: string;
|
||||
readonly commands: string;
|
||||
readonly events?: string;
|
||||
readonly containerConfig: {
|
||||
get: (...keys: (keyof Dependencies)[]) => unknown[];
|
||||
};
|
||||
readonly events?:
|
||||
| string
|
||||
| { mod: EventModule; absPath: string }[]
|
||||
| (() => { mod: EventModule; absPath: string }[]);
|
||||
}
|
||||
|
||||
export default Wrapper;
|
||||
|
||||
9
src/handler/utilities/asyncResolveArray.ts
Normal file
9
src/handler/utilities/asyncResolveArray.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { Awaitable } from 'discord.js';
|
||||
|
||||
export async function asyncResolveArray<T>(promiseLike: Awaitable<T>[]): Promise<T[]> {
|
||||
const arr: T[] = [];
|
||||
for await (const el of promiseLike) {
|
||||
arr.push(el);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
18
src/handler/utilities/externallyUsed.ts
Normal file
18
src/handler/utilities/externallyUsed.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import * as Files from './readFile';
|
||||
import { basename } from 'path';
|
||||
import { Err, Ok } from 'ts-results-es';
|
||||
/**
|
||||
* A function that returns whatever value is provided.
|
||||
* Warning: this evaluates { @param value }. It does not defer a value.
|
||||
* @param value
|
||||
*/
|
||||
// prettier-ignore
|
||||
export const _const = <T>(value: T) => () => value;
|
||||
/**
|
||||
*
|
||||
* @param modName
|
||||
* @param absPath
|
||||
*/
|
||||
export function nameOrFilename(modName: string | undefined, absPath: string) {
|
||||
return modName ?? Files.fmtFileName(basename(absPath));
|
||||
}
|
||||
|
||||
//function wrappers for empty ok / err
|
||||
export const ok = _const(Ok.EMPTY);
|
||||
export const err = _const(Err.EMPTY);
|
||||
|
||||
export function partition<T, V>(arr: (T & V)[], condition: (e: T & V) => boolean): [T[], V[]] {
|
||||
const t: T[] = [];
|
||||
const v: V[] = [];
|
||||
for (const el of arr) {
|
||||
if (condition(el)) {
|
||||
t.push(el as T);
|
||||
} else {
|
||||
v.push(el as V);
|
||||
}
|
||||
}
|
||||
return [t, v];
|
||||
}
|
||||
100
src/handler/utilities/predicates.ts
Normal file
100
src/handler/utilities/predicates.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
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,
|
||||
} from 'discord.js';
|
||||
import type {
|
||||
DiscordEventCommand,
|
||||
ExternalEventCommand,
|
||||
SernEventCommand,
|
||||
} from '../structures/events';
|
||||
import { EventType } from '../..';
|
||||
|
||||
export function correctModuleType<T extends keyof CommandModuleDefs>(
|
||||
plug: Module | undefined,
|
||||
type: T,
|
||||
): plug is CommandModuleDefs[T] {
|
||||
// Another way to check if type is equivalent,
|
||||
// It will check based on flag system instead
|
||||
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 {
|
||||
return interaction.type === InteractionType.ApplicationCommand;
|
||||
}
|
||||
|
||||
export function isModalSubmit(interaction: Interaction): interaction is ModalSubmitInteraction {
|
||||
return interaction.type === InteractionType.ModalSubmit;
|
||||
}
|
||||
export function isAutocomplete(interaction: Interaction): interaction is AutocompleteInteraction {
|
||||
return interaction.type === InteractionType.ApplicationCommandAutocomplete;
|
||||
}
|
||||
|
||||
export function isMessageComponent(
|
||||
interaction: Interaction,
|
||||
): interaction is MessageComponentInteraction {
|
||||
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;
|
||||
}
|
||||
export function isSernEvent(el: EventModule): el is SernEventCommand {
|
||||
return el.type === EventType.Sern;
|
||||
}
|
||||
|
||||
export function isExternalEvent(el: EventModule): el is ExternalEventCommand {
|
||||
return el.type === EventType.External && 'emitter' in el;
|
||||
}
|
||||
|
||||
// export function isEventPlugin<T extends CommandType>(
|
||||
// e: CommandModulePlugin<T>,
|
||||
// ): e is EventPlugin<T> {
|
||||
// return e.type === PluginType.Event;
|
||||
// }
|
||||
// export function isCommandPlugin<T extends CommandType>(
|
||||
// e: CommandModulePlugin<T>,
|
||||
// ): e is CommandPlugin<T> {
|
||||
// return !isEventPlugin(e);
|
||||
// }
|
||||
@@ -1,8 +1,35 @@
|
||||
import { ApplicationCommandType, ComponentType } from 'discord.js';
|
||||
import { readdirSync, statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { type Observable, from, concatAll } from 'rxjs';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import type { Module } from '../structures/module';
|
||||
import { SernError } from '../structures/errors';
|
||||
import { type Result, Err, Ok } from 'ts-results-es';
|
||||
import type { Result } from 'ts-results';
|
||||
import { Err, Ok } from 'ts-results';
|
||||
import type { EventEmitter } from 'events';
|
||||
|
||||
//Maybe move this? this probably doesnt belong in utlities/
|
||||
export const BothCommands = new Map<string, Module>();
|
||||
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> };
|
||||
|
||||
export const MessageCompCommands = {
|
||||
[ComponentType.Button]: new Map<string, Module>(),
|
||||
[ComponentType.SelectMenu]: new Map<string, Module>(),
|
||||
[ComponentType.TextInput]: new Map<string, Module>(),
|
||||
};
|
||||
export const TextCommands = {
|
||||
text: new Map<string, Module>(),
|
||||
aliases: new Map<string, Module>(),
|
||||
};
|
||||
export const ModalSubmitCommands = new Map<string, Module>();
|
||||
/**
|
||||
* keeps all external emitters stored here
|
||||
*/
|
||||
export const ExternalEventEmitters = new Map<string, EventEmitter>();
|
||||
|
||||
// Courtesy @Townsy45
|
||||
function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
|
||||
@@ -22,8 +49,7 @@ function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
|
||||
export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
|
||||
|
||||
/**
|
||||
* a directory string is converted into a stream of modules.
|
||||
* starts the stream of modules that sern needs to process on init
|
||||
*
|
||||
* @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files
|
||||
* @param commandDir
|
||||
*/
|
||||
@@ -31,33 +57,21 @@ export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
|
||||
export function buildData<T>(commandDir: string): Observable<
|
||||
Result<
|
||||
{
|
||||
module: T;
|
||||
mod: T;
|
||||
absPath: string;
|
||||
},
|
||||
SernError
|
||||
>
|
||||
> {
|
||||
const commands = getCommands(commandDir);
|
||||
return from(
|
||||
Promise.all(
|
||||
commands.map(async absPath => {
|
||||
let module: T | undefined;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
module = require(absPath).default;
|
||||
} catch {
|
||||
module = (await import(`file:///` + absPath)).default;
|
||||
}
|
||||
if (module === undefined) {
|
||||
return Err(SernError.UndefinedModule);
|
||||
}
|
||||
try {
|
||||
module = new (module as unknown as new () => T)();
|
||||
} catch {}
|
||||
return Ok({ module, absPath });
|
||||
}),
|
||||
),
|
||||
).pipe(concatAll());
|
||||
getCommands(commandDir).map(absPath => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const mod = <T | undefined>require(absPath).default;
|
||||
if (mod !== undefined) {
|
||||
return Ok({ mod, absPath });
|
||||
} else return Err(SernError.UndefinedModule);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function getCommands(dir: string): string[] {
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js';
|
||||
import type { SernAutocompleteData, SernOptionsData } from '../../types/module';
|
||||
|
||||
/**
|
||||
* Uses an iterative DFS to check if an autocomplete node exists
|
||||
* @param iAutocomplete
|
||||
* @param options
|
||||
*/
|
||||
export default function treeSearch(
|
||||
iAutocomplete: AutocompleteInteraction,
|
||||
options: SernOptionsData[] | undefined,
|
||||
): SernAutocompleteData | undefined {
|
||||
if (options === undefined) return undefined;
|
||||
const _options = options.slice(); // required to prevent direct mutation of options
|
||||
let autocompleteData: SernAutocompleteData | undefined;
|
||||
|
||||
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;
|
||||
}
|
||||
17
src/index.ts
17
src/index.ts
@@ -1,18 +1,7 @@
|
||||
import SernEmitter from './handler/sernEmitter';
|
||||
export {
|
||||
eventModule,
|
||||
commandModule,
|
||||
EventExecutable,
|
||||
CommandExecutable,
|
||||
controller,
|
||||
discordEvent,
|
||||
} from './handler/sern';
|
||||
export { eventModule, commandModule } from './handler/sern';
|
||||
export * as Sern from './handler/sern';
|
||||
export * from './types/handler';
|
||||
export * from './types/module';
|
||||
export * from './types/plugin';
|
||||
export * from './handler/structures';
|
||||
export * from './handler/plugins';
|
||||
export * from './handler/contracts';
|
||||
export * from './handler/structures/structxports';
|
||||
export * from './handler/plugins/plugin';
|
||||
export { SernEmitter };
|
||||
export * from './handler/dependencies';
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import type { CommandInteractionOptionResolver } from 'discord.js';
|
||||
import type { PayloadType } from '../handler/structures/enums';
|
||||
import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js';
|
||||
import type { EventEmitter } from 'events';
|
||||
import type { CommandModule, EventModule, AnyModule } from './module';
|
||||
import type { UnpackFunction } from 'iti';
|
||||
import type { ErrorHandling, Logging, ModuleManager } from '../handler/contracts';
|
||||
import type { ModuleStore } from '../handler/structures/moduleStore';
|
||||
import type SernEmitter from '../handler/sernEmitter';
|
||||
import type { Container } from 'iti';
|
||||
import type { CommandModule, EventModule, Module } from '../handler/structures/module';
|
||||
export type Nullish<T> = T | undefined | null;
|
||||
|
||||
// Thanks to @kelsny
|
||||
export type ParseType<T> = {
|
||||
[K in keyof T]: T[K] extends unknown ? [k: K, args: T[K]] : never;
|
||||
@@ -17,53 +11,41 @@ export type Args = ParseType<{ text: string[]; slash: SlashOptions }>;
|
||||
|
||||
export type SlashOptions = Omit<CommandInteractionOptionResolver, 'getMessage' | 'getFocused'>;
|
||||
|
||||
// Source: https://dev.to/vborodulin/ts-how-to-override-properties-with-type-intersection-554l
|
||||
export type Override<T1, T2> = Omit<T1, keyof T2> & T2;
|
||||
|
||||
export type DefinitelyDefined<T, K extends keyof T = keyof T> = {
|
||||
[L in K]-?: T[L] extends Record<string, unknown>
|
||||
? DefinitelyDefined<T[L], keyof T[L]>
|
||||
: 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;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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> = (
|
||||
args: Parameters<T>[number],
|
||||
) => unknown;
|
||||
|
||||
/**
|
||||
* After modules are transformed, name and description are given default values if none
|
||||
* are provided to Module. This type represents that transformation
|
||||
*/
|
||||
export type AnyDefinedModule = Processed<CommandModule | EventModule>;
|
||||
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: PayloadType.Success; module: AnyModule }
|
||||
| { type: PayloadType.Failure; module?: AnyModule; reason: string | Error }
|
||||
| { type: PayloadType.Warning; reason: string };
|
||||
| { type: 'success'; module: Module }
|
||||
| { type: 'failure'; module: Module | undefined; reason: string | Error };
|
||||
export type SernEventsMapping = {
|
||||
'module.register': [Payload];
|
||||
'module.activate': [Payload];
|
||||
error: [Payload];
|
||||
warning: [Payload];
|
||||
['module.register']: [Payload];
|
||||
['module.activate']: [Payload];
|
||||
['error']: [Error | string];
|
||||
};
|
||||
export type LogPayload<T = unknown> = { message: T };
|
||||
export type Singleton<T> = () => T;
|
||||
export type Transient<T> = () => () => T;
|
||||
|
||||
export interface Dependencies {
|
||||
'@sern/client': Singleton<EventEmitter>;
|
||||
'@sern/logger'?: Singleton<Logging>;
|
||||
'@sern/emitter': Singleton<SernEmitter>;
|
||||
'@sern/store': Singleton<ModuleStore>;
|
||||
'@sern/modules': Singleton<ModuleManager>;
|
||||
'@sern/errors': Singleton<ErrorHandling>;
|
||||
}
|
||||
|
||||
export type ReplyOptions =
|
||||
| string
|
||||
| Omit<InteractionReplyOptions, 'fetchReply'>
|
||||
| MessageReplyOptions;
|
||||
//prettier-ignore
|
||||
export type MapDeps<Deps extends Dependencies, T extends readonly unknown[]> = T extends [
|
||||
infer First extends keyof Deps,
|
||||
...infer Rest extends readonly unknown[],
|
||||
]
|
||||
? [
|
||||
UnpackFunction<Deps[First]>,
|
||||
...(MapDeps<Deps, Rest> extends [never] ? [] : MapDeps<Deps, Rest>),
|
||||
]
|
||||
: [never];
|
||||
//Basically, '@sern/client' | '@sern/store' | '@sern/modules' | '@sern/error' | '@sern/emitter' will be provided defaults, and you can exclude the rest
|
||||
export type OptionalDependencies = '@sern/logger';
|
||||
export type Processed<T> = T & { name: string; description: string };
|
||||
export type Deprecated<Message extends string> = [never, Message];
|
||||
export interface DependencyConfiguration<T extends Dependencies> {
|
||||
exclude?: Set<OptionalDependencies>;
|
||||
build: (root: Container<Omit<Dependencies, '@sern/client'>, {}>) => Container<T, {}>;
|
||||
}
|
||||
|
||||
@@ -1,228 +0,0 @@
|
||||
import type {
|
||||
ApplicationCommandAttachmentOption,
|
||||
ApplicationCommandChannelOptionData,
|
||||
ApplicationCommandChoicesData,
|
||||
ApplicationCommandNonOptionsData,
|
||||
ApplicationCommandNumericOptionData,
|
||||
ApplicationCommandOptionData,
|
||||
ApplicationCommandOptionType,
|
||||
ApplicationCommandSubCommandData,
|
||||
ApplicationCommandSubGroupData,
|
||||
AutocompleteInteraction,
|
||||
Awaitable,
|
||||
BaseApplicationCommandOptionsData,
|
||||
ButtonInteraction,
|
||||
MessageContextMenuCommandInteraction,
|
||||
ModalSubmitInteraction,
|
||||
UserContextMenuCommandInteraction,
|
||||
ChannelSelectMenuInteraction,
|
||||
MentionableSelectMenuInteraction,
|
||||
RoleSelectMenuInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
} from 'discord.js';
|
||||
import { CommandType } from '../handler/structures/enums';
|
||||
import type { Args, SlashOptions } from './handler';
|
||||
import type Context from '../handler/structures/context';
|
||||
import type { InitPlugin, ControlPlugin } from './plugin';
|
||||
import { EventType } from '../handler/structures/enums';
|
||||
import type { UserSelectMenuInteraction } from 'discord.js';
|
||||
import type { AnyCommandPlugin, AnyEventPlugin } from './plugin';
|
||||
import type { SernEventsMapping } from './handler';
|
||||
import type { ClientEvents } from 'discord.js';
|
||||
|
||||
export interface Module {
|
||||
type: CommandType | EventType;
|
||||
name?: string;
|
||||
onEvent: ControlPlugin[];
|
||||
plugins: InitPlugin[];
|
||||
description?: string;
|
||||
execute: (...args: any[]) => Awaitable<any>;
|
||||
}
|
||||
|
||||
export interface TextCommand extends Module {
|
||||
type: CommandType.Text;
|
||||
alias?: string[];
|
||||
execute: (ctx: Context, args: ['text', string[]]) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface SlashCommand extends Module {
|
||||
type: CommandType.Slash;
|
||||
description: string;
|
||||
options?: SernOptionsData[];
|
||||
execute: (ctx: Context, args: ['slash', SlashOptions]) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface BothCommand extends Module {
|
||||
type: CommandType.Both;
|
||||
alias?: string[];
|
||||
description: string;
|
||||
options?: SernOptionsData[];
|
||||
execute: (ctx: Context, args: Args) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface ContextMenuUser extends Module {
|
||||
type: CommandType.CtxUser;
|
||||
execute: (ctx: UserContextMenuCommandInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface ContextMenuMsg extends Module {
|
||||
type: CommandType.CtxMsg;
|
||||
execute: (ctx: MessageContextMenuCommandInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface ButtonCommand extends Module {
|
||||
type: CommandType.Button;
|
||||
execute: (ctx: ButtonInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface StringSelectCommand extends Module {
|
||||
type: CommandType.StringSelect;
|
||||
execute: (ctx: StringSelectMenuInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface ChannelSelectCommand extends Module {
|
||||
type: CommandType.ChannelSelect;
|
||||
execute: (ctx: ChannelSelectMenuInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface RoleSelectCommand extends Module {
|
||||
type: CommandType.RoleSelect;
|
||||
execute: (ctx: RoleSelectMenuInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface MentionableSelectCommand extends Module {
|
||||
type: CommandType.MentionableSelect;
|
||||
execute: (ctx: MentionableSelectMenuInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface UserSelectCommand extends Module {
|
||||
type: CommandType.UserSelect;
|
||||
execute: (ctx: UserSelectMenuInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface ModalSubmitCommand extends Module {
|
||||
type: CommandType.Modal;
|
||||
execute: (ctx: ModalSubmitInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface AutocompleteCommand
|
||||
extends Omit<Module, 'name' | 'type' | 'plugins' | 'description'> {
|
||||
onEvent: ControlPlugin[];
|
||||
execute: (ctx: AutocompleteInteraction) => Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface SernEventCommand<T extends keyof SernEventsMapping = keyof SernEventsMapping>
|
||||
extends Module {
|
||||
name?: T;
|
||||
type: EventType.Sern;
|
||||
execute(...args: SernEventsMapping[T]): Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface DiscordEventCommand<T extends keyof ClientEvents = keyof ClientEvents>
|
||||
extends Module {
|
||||
name?: T;
|
||||
type: EventType.Discord;
|
||||
execute(...args: ClientEvents[T]): Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export interface ExternalEventCommand extends Module {
|
||||
name?: string;
|
||||
emitter: string;
|
||||
type: EventType.External;
|
||||
execute(...args: unknown[]): Awaitable<unknown>;
|
||||
}
|
||||
|
||||
export type EventModule = DiscordEventCommand | SernEventCommand | ExternalEventCommand;
|
||||
export type CommandModule =
|
||||
| TextCommand
|
||||
| SlashCommand
|
||||
| BothCommand
|
||||
| ContextMenuUser
|
||||
| ContextMenuMsg
|
||||
| ButtonCommand
|
||||
| StringSelectCommand
|
||||
| MentionableSelectCommand
|
||||
| UserSelectCommand
|
||||
| ChannelSelectCommand
|
||||
| RoleSelectCommand
|
||||
| ModalSubmitCommand;
|
||||
|
||||
export type AnyModule = CommandModule | EventModule;
|
||||
|
||||
//https://stackoverflow.com/questions/64092736/alternative-to-switch-statement-for-typescript-discriminated-union
|
||||
// Explicit Module Definitions for mapping
|
||||
export type CommandModuleDefs = {
|
||||
[CommandType.Text]: TextCommand;
|
||||
[CommandType.Slash]: SlashCommand;
|
||||
[CommandType.Both]: BothCommand;
|
||||
[CommandType.CtxMsg]: ContextMenuMsg;
|
||||
[CommandType.CtxUser]: ContextMenuUser;
|
||||
[CommandType.Button]: ButtonCommand;
|
||||
[CommandType.StringSelect]: StringSelectCommand;
|
||||
[CommandType.RoleSelect]: RoleSelectCommand;
|
||||
[CommandType.ChannelSelect]: ChannelSelectCommand;
|
||||
[CommandType.MentionableSelect]: MentionableSelectCommand;
|
||||
[CommandType.UserSelect]: UserSelectCommand;
|
||||
[CommandType.Modal]: ModalSubmitCommand;
|
||||
};
|
||||
|
||||
export type EventModuleDefs = {
|
||||
[EventType.Sern]: SernEventCommand;
|
||||
[EventType.Discord]: DiscordEventCommand;
|
||||
[EventType.External]: ExternalEventCommand;
|
||||
};
|
||||
|
||||
export interface SernAutocompleteData
|
||||
extends Omit<BaseApplicationCommandOptionsData, 'autocomplete'> {
|
||||
autocomplete: true;
|
||||
type:
|
||||
| ApplicationCommandOptionType.String
|
||||
| ApplicationCommandOptionType.Number
|
||||
| ApplicationCommandOptionType.Integer;
|
||||
command: AutocompleteCommand;
|
||||
}
|
||||
|
||||
export type CommandModuleNoPlugins = {
|
||||
[T in CommandType]: Omit<CommandModuleDefs[T], 'plugins' | 'onEvent'>;
|
||||
};
|
||||
export type EventModulesNoPlugins = {
|
||||
[T in EventType]: Omit<EventModuleDefs[T], 'plugins' | 'onEvent'>;
|
||||
};
|
||||
|
||||
export type InputEvent = {
|
||||
[T in EventType]: EventModulesNoPlugins[T] & { plugins?: AnyEventPlugin[] };
|
||||
}[EventType];
|
||||
|
||||
export type InputCommand = {
|
||||
[T in CommandType]: CommandModuleNoPlugins[T] & { plugins?: AnyCommandPlugin[] };
|
||||
}[CommandType];
|
||||
|
||||
/**
|
||||
* Type that replaces autocomplete with {@link SernAutocompleteData}
|
||||
*/
|
||||
export type BaseOptions =
|
||||
| ApplicationCommandChoicesData
|
||||
| ApplicationCommandNonOptionsData
|
||||
| ApplicationCommandChannelOptionData
|
||||
| ApplicationCommandNumericOptionData
|
||||
| ApplicationCommandAttachmentOption
|
||||
| SernAutocompleteData;
|
||||
|
||||
export interface SernSubCommandData extends BaseApplicationCommandOptionsData {
|
||||
type: ApplicationCommandOptionType.Subcommand;
|
||||
required?: never;
|
||||
options?: BaseOptions[];
|
||||
}
|
||||
|
||||
export interface SernSubCommandGroupData extends BaseApplicationCommandOptionsData {
|
||||
type: ApplicationCommandOptionType.SubcommandGroup;
|
||||
required?: never;
|
||||
options?: SernSubCommandData[];
|
||||
}
|
||||
|
||||
export type SernOptionsData<U extends ApplicationCommandOptionData = ApplicationCommandOptionData> =
|
||||
U extends ApplicationCommandSubCommandData
|
||||
? SernSubCommandData
|
||||
: U extends ApplicationCommandSubGroupData
|
||||
? SernSubCommandGroupData
|
||||
: BaseOptions;
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Plugins can be inserted on all commands and are emitted
|
||||
*
|
||||
* 1. On ready event, where all commands are loaded.
|
||||
* 2. On corresponding observable (when command triggers)
|
||||
*
|
||||
* The goal of plugins is to organize commands and
|
||||
* provide extensions to repetitive patterns
|
||||
* examples include refreshing modules,
|
||||
* categorizing commands, cool-downs, permissions, etc.
|
||||
* Plugins are reminiscent of middleware in express.
|
||||
*/
|
||||
|
||||
import type { Awaitable } from 'discord.js';
|
||||
import type { Err, Ok, Result } from 'ts-results-es';
|
||||
import type { PluginType } from '../handler/structures/enums';
|
||||
import type { CommandModule, EventModule } from './module';
|
||||
import type { CommandArgs, InitArgs } from '../handler/plugins';
|
||||
import type { Deprecated, Processed } from './handler';
|
||||
import type { CommandType } from '../handler/structures/enums';
|
||||
export type PluginResult = Awaitable<VoidResult>;
|
||||
export type VoidResult = Result<void, void>;
|
||||
|
||||
export interface Controller {
|
||||
next: () => Ok<void>;
|
||||
stop: () => Err<void>;
|
||||
}
|
||||
export interface Plugin<Args extends any[] = any[]> {
|
||||
type: PluginType;
|
||||
execute: (...args: Args) => PluginResult;
|
||||
}
|
||||
|
||||
export interface InitPlugin<Args extends any[] = any[]> {
|
||||
type: PluginType.Init;
|
||||
execute: (...args: Args) => PluginResult;
|
||||
}
|
||||
export interface ControlPlugin<Args extends any[] = any[]> {
|
||||
type: PluginType.Control;
|
||||
execute: (...args: Args) => PluginResult;
|
||||
}
|
||||
|
||||
export type AnyCommandPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<CommandModule>>]>;
|
||||
export type AnyEventPlugin = ControlPlugin | InitPlugin<[InitArgs<Processed<EventModule>>]>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Use the newer helper functions and import { controller } from '@sern/handler'
|
||||
*/
|
||||
export interface CommandPlugin<T extends CommandType = CommandType> {
|
||||
name?: string;
|
||||
description?: string;
|
||||
type: PluginType.Command;
|
||||
execute: (
|
||||
m: InitArgs<Processed<CommandModule>>,
|
||||
controller?: Deprecated<'Please import controller instead'>,
|
||||
) => PluginResult;
|
||||
}
|
||||
/**
|
||||
* @deprecated
|
||||
* Use the newer helper functions
|
||||
*/
|
||||
export interface EventPlugin<T extends CommandType> {
|
||||
name?: string;
|
||||
description?: string;
|
||||
type: PluginType.Event;
|
||||
execute: (args: CommandArgs<T, PluginType.Event>, controller?: Controller) => PluginResult;
|
||||
}
|
||||
export type DiscordEmitterPlugin = Deprecated<'Please view alternatives: '>;
|
||||
export type ExternalEmitterPlugin = Deprecated<'Please view alternatives: '>;
|
||||
export type SernEmitterPlugin = Deprecated<'Please view alternatives: '>;
|
||||
export type AutocompletePlugin = Deprecated<'Please view alternatives: '>;
|
||||
export type SernEventPlugin = Deprecated<'Please view alternatives: '>;
|
||||
export type ExternalEventPlugin = Deprecated<'Please view alternatives: '>;
|
||||
export type DiscordEventPlugin = Deprecated<'Please view alternatives: '>;
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictNullChecks": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "dist/cjs",
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"outDir": "dist/esm",
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"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"],
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
const shared = {
|
||||
entry: ['src/index.ts'],
|
||||
external: ['discord.js'],
|
||||
platform: 'node',
|
||||
clean: true,
|
||||
sourcemap: false,
|
||||
};
|
||||
export default defineConfig([
|
||||
{
|
||||
format: 'esm',
|
||||
target: 'node16',
|
||||
tsconfig: './tsconfig-esm.json',
|
||||
outDir: './dist/esm',
|
||||
external: ['discord.js'],
|
||||
treeshake: true,
|
||||
outExtension() {
|
||||
return {
|
||||
js: '.mjs',
|
||||
};
|
||||
},
|
||||
...shared,
|
||||
},
|
||||
{
|
||||
format: 'cjs',
|
||||
splitting: false,
|
||||
external: ['discord.js'],
|
||||
target: 'node16',
|
||||
tsconfig: './tsconfig-cjs.json',
|
||||
outDir: './dist/cjs',
|
||||
outExtension() {
|
||||
return {
|
||||
js: '.cjs',
|
||||
};
|
||||
},
|
||||
...shared,
|
||||
},
|
||||
]);
|
||||
Reference in New Issue
Block a user