mirror of
https://github.com/sern-handler/handler
synced 2026-06-06 01:16:55 +00:00
Compare commits
360 Commits
api-update
...
fix/dispos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4722849f56 | ||
|
|
513ac8edf4 | ||
|
|
3673263f3a | ||
|
|
24ec4d6ad6 | ||
|
|
27d13f1ea5 | ||
|
|
81a0180d05 | ||
|
|
89d7409536 | ||
|
|
aa802f761e | ||
|
|
2414992b73 | ||
|
|
70c6236802 | ||
|
|
ed5bb20c60 | ||
|
|
0d77878dfb | ||
|
|
8ac7b60b59 | ||
|
|
1f25aa64b9 | ||
|
|
7cddee30aa | ||
|
|
e7286eee9f | ||
|
|
a67450328e | ||
|
|
926d531863 | ||
|
|
ef3d3d71a0 | ||
|
|
47401f46a3 | ||
|
|
1059065980 | ||
|
|
974c30fa6c | ||
|
|
3a569726d8 | ||
|
|
1b7f2a49a8 | ||
|
|
97fa2a2d78 | ||
|
|
a52ad270d8 | ||
|
|
3f703c17b8 | ||
|
|
f9e7eaf92d | ||
|
|
52e145600d | ||
|
|
59d08ef207 | ||
|
|
7deb79e907 | ||
|
|
f2d4b5bda1 | ||
|
|
a575b3ed74 | ||
|
|
2042559b4d | ||
|
|
220a60ecf8 | ||
|
|
55715d5659 | ||
|
|
d0c3b7469e | ||
|
|
eabfb81819 | ||
|
|
1789ccb2f2 | ||
|
|
25c5891ade | ||
|
|
2106cdc1d0 | ||
|
|
61e82fdc7b | ||
|
|
3755b95b1a | ||
|
|
06807ea77f | ||
|
|
3fd3f1c236 | ||
|
|
92623d2914 | ||
|
|
a91f260a86 | ||
|
|
dda0e3395b | ||
|
|
9a8904f5ae | ||
|
|
04c4625bfa | ||
|
|
91b3768e37 | ||
|
|
d6f49d1d97 | ||
|
|
8ecd30cf18 | ||
|
|
a19edaf883 | ||
|
|
90e55dfa14 | ||
|
|
2106522812 | ||
|
|
ce8c4bf649 | ||
|
|
e89b918390 | ||
|
|
f8b69ae542 | ||
|
|
48f9f6ec16 | ||
|
|
86ebb221ed | ||
|
|
4efdbb21fb | ||
|
|
07b11b357b | ||
|
|
ac7f47c590 | ||
| 45cbda7b42 | |||
|
|
5cad432589 | ||
|
|
044a10dace | ||
|
|
9d5c6c714f | ||
|
|
4f2387119a | ||
|
|
a6fa4e3dcb | ||
|
|
c281832db2 | ||
|
|
a359f73fa2 | ||
| 655bb8d358 | |||
| e8d5029834 | |||
|
|
b0399f9507 | ||
|
|
b962dae36c | ||
|
|
c73cf96cb2 | ||
|
|
7458befe8a | ||
|
|
efe49391e8 | ||
|
|
3140f80c10 | ||
|
|
504cdee7b2 | ||
|
|
c7661f272c | ||
|
|
daac37c288 | ||
|
|
a579e272d0 | ||
|
|
2051aa1ac0 | ||
|
|
237c8537c6 | ||
|
|
77fb00d386 | ||
|
|
89f6bbb975 | ||
|
|
8ef4ee87e9 | ||
|
|
fd39858636 | ||
|
|
132b625070 | ||
|
|
03439fec43 | ||
|
|
fc87e99ed0 | ||
|
|
a08541a8e7 | ||
|
|
8bd5eb4949 | ||
|
|
e1059f93f7 | ||
|
|
800531453f | ||
|
|
c9f2d75665 | ||
|
|
e59e0b9d40 | ||
|
|
26ccd118ff | ||
|
|
4b97d86908 | ||
|
|
b1c82448bd | ||
|
|
d80081384a | ||
|
|
50253ca322 | ||
|
|
215aca2f46 | ||
|
|
a7f5ea269f | ||
|
|
52d6368440 | ||
|
|
1e723a4154 | ||
|
|
5fe13f43d2 | ||
|
|
ab9d39306a | ||
|
|
d429f3adbf | ||
|
|
5e011b471e | ||
|
|
41344608c6 | ||
|
|
7a72cc4fe3 | ||
|
|
70cca0dbb0 | ||
|
|
7798e36458 | ||
|
|
9144485c39 | ||
|
|
cf15b67ede | ||
|
|
57cc94ff81 | ||
|
|
6a2a5b4565 | ||
|
|
5fdc1eda7f | ||
|
|
e00d1df32e | ||
|
|
31c221bd5e | ||
|
|
0aba4a6606 | ||
|
|
e9c7661804 | ||
|
|
446417bfb9 | ||
|
|
6b58ef731b | ||
|
|
b62129bf04 | ||
|
|
3d121ff01c | ||
|
|
d201087d4f | ||
|
|
1af4a2bed4 | ||
|
|
edcaed083e | ||
|
|
a4fe2c50df | ||
|
|
9ea991626d | ||
|
|
64f20f1cf5 | ||
|
|
41cc72fe63 | ||
|
|
d983f95906 | ||
|
|
c1f690633c | ||
|
|
8544d301ef | ||
|
|
52bcba9cfc | ||
|
|
21febd2c90 | ||
|
|
11daebb30a | ||
|
|
b817f98c10 | ||
|
|
563f583318 | ||
|
|
e4c7bfe686 | ||
|
|
69fa4908c3 | ||
|
|
4fa28d605f | ||
|
|
079b554f8b | ||
|
|
dec56335b9 | ||
|
|
50be972d4f | ||
|
|
507d183970 | ||
|
|
90edd4f91e | ||
|
|
5f11142599 | ||
|
|
7a635f9978 | ||
|
|
a17aeac558 | ||
|
|
af6ebed348 | ||
|
|
2f96b7634d | ||
|
|
97741faa69 | ||
|
|
94070d99e8 | ||
|
|
473be775f0 | ||
|
|
36af102251 | ||
|
|
cee740ea3f | ||
|
|
2fd7697300 | ||
|
|
f9609ce6cd | ||
|
|
a3064aa915 | ||
|
|
0a53a48521 | ||
|
|
05037b5315 | ||
|
|
06a3e69210 | ||
|
|
74c4b77d4b | ||
|
|
d381ff568e | ||
|
|
6db5c71506 | ||
|
|
507c9e7939 | ||
|
|
09610d0501 | ||
|
|
0862bf92d0 | ||
|
|
62162f6b8c | ||
|
|
eb501db09a | ||
|
|
964848a4e2 | ||
|
|
78dead1b49 | ||
|
|
39e6d6d2f9 | ||
|
|
5aff57ed6d | ||
|
|
4095471346 | ||
|
|
f7b9c52df1 | ||
|
|
d20d01524b | ||
|
|
5684c060bc | ||
|
|
ca8b31f280 | ||
|
|
ce9a0831a6 | ||
|
|
3a32968a17 | ||
|
|
e549f8bc3e | ||
|
|
3ab73459ad | ||
|
|
16af2e996d | ||
|
|
4f9a842b0e | ||
|
|
5dfd1a1fc1 | ||
|
|
c45a10c950 | ||
|
|
b45ba34f3c | ||
|
|
facee79c90 | ||
|
|
306eee071d | ||
|
|
00d55208a0 | ||
|
|
49fad801a5 | ||
|
|
c9f44ce72b | ||
|
|
a9a2528faf | ||
|
|
529edb7da5 | ||
|
|
f236dc05e2 | ||
|
|
c78936a225 | ||
|
|
1860b898f3 | ||
|
|
fd10772a9b | ||
|
|
cd36bd8a47 | ||
|
|
dda7f41231 | ||
|
|
371a57194c | ||
|
|
bfcc160a39 | ||
|
|
86fa531eb6 | ||
|
|
58052e94cb | ||
|
|
96f4281121 | ||
|
|
f9ae7c003b | ||
|
|
ec211d5a8d | ||
|
|
3faf83bbf7 | ||
|
|
8eed099503 | ||
|
|
e2874be4e7 | ||
|
|
1d6751a9cd | ||
|
|
f6afafa352 | ||
|
|
b4b195dc95 | ||
|
|
33f14467ec | ||
|
|
cb95105c1c | ||
|
|
845b82feef | ||
|
|
833a323f3c | ||
|
|
42e5f20425 | ||
|
|
2b25e6bfbb | ||
|
|
1a27341092 | ||
|
|
4680e451bb | ||
|
|
917d8b0d1b | ||
|
|
b8492ee45d | ||
|
|
08aac1d67a | ||
|
|
a13df6fb42 | ||
|
|
7089f5c0dc | ||
|
|
559c1a7a7b | ||
|
|
ac27d168e2 | ||
|
|
d1e6ec4589 | ||
|
|
ff379d03be | ||
|
|
1e4e933db2 | ||
|
|
ce960f4c8d | ||
|
|
1130456045 | ||
|
|
1617d2dcc3 | ||
|
|
ddacbd6e38 | ||
|
|
d69819e9fc | ||
|
|
49e4ba623f | ||
|
|
1b6c413fc2 | ||
|
|
e986535935 | ||
|
|
c30aac476c | ||
|
|
f9622d3788 | ||
|
|
f286a24686 | ||
|
|
166934d749 | ||
|
|
01d79177e8 | ||
|
|
50dac7fb46 | ||
|
|
714d23d401 | ||
|
|
565c4fc35a | ||
|
|
8d18c4b182 | ||
|
|
71cec6f142 | ||
|
|
14556223fd | ||
|
|
59c1c9c6a9 | ||
|
|
a120136f55 | ||
|
|
9b2d7eea5f | ||
|
|
4d7aa97b66 | ||
|
|
83eadcd2e5 | ||
|
|
c0bf346841 | ||
|
|
73c161fffe | ||
|
|
ee763301d0 | ||
|
|
c5f6eb9794 | ||
|
|
ec8a61a9ee | ||
|
|
87c17dbe10 | ||
|
|
790ce1681c | ||
|
|
cbad7380e1 | ||
|
|
ad36875be2 | ||
|
|
50288867a5 | ||
|
|
6b8995d149 | ||
|
|
82bbddac8d | ||
|
|
03936eb2ea | ||
|
|
c4019f7a08 | ||
|
|
992619f8e5 | ||
|
|
b995560ec6 | ||
|
|
f01ef9b86c | ||
|
|
702c5750fc | ||
|
|
d5d1b4129b | ||
|
|
7658d3e3ab | ||
|
|
9c1abc6b2e | ||
|
|
20bc135a7d | ||
|
|
8a373de880 | ||
|
|
395549c173 | ||
|
|
dbc180fa78 | ||
|
|
ff5c161469 | ||
|
|
82637245f3 | ||
|
|
bc3ed918da | ||
|
|
62bfcb1eb7 | ||
|
|
fbf54c9d36 | ||
|
|
b999918b71 | ||
|
|
224ce97fe8 | ||
|
|
f52dd01ca5 | ||
|
|
b8841e926d | ||
|
|
f0f54cb7f2 | ||
|
|
ebe759f5e0 | ||
|
|
58d97e08a5 | ||
|
|
21a438768b | ||
|
|
b3ed8da68f | ||
|
|
c5bd94131d | ||
|
|
3dec347ef0 | ||
|
|
4e9530f4d7 | ||
|
|
0da1b5a4dc | ||
|
|
8a7a671300 | ||
|
|
bf8a5d5a11 | ||
|
|
74378f0f12 | ||
|
|
0559fcca96 | ||
|
|
70d7bdb8c5 | ||
|
|
b6ea3da23f | ||
|
|
75a6a04db5 | ||
|
|
24fae252a7 | ||
|
|
d70a2a1d5e | ||
|
|
0d6e592614 | ||
|
|
44ee2f4440 | ||
|
|
b560d56346 | ||
|
|
344229b65f | ||
|
|
ca728f755b | ||
|
|
d8d6330260 | ||
|
|
c17c49bb17 | ||
|
|
7da7bff700 | ||
|
|
a0587f59d4 | ||
|
|
d62be87c9a | ||
|
|
35a200f055 | ||
|
|
d96681bfb5 | ||
|
|
d1b034b826 | ||
|
|
2387add445 | ||
|
|
7bf15a2d5d | ||
|
|
07e6dabce1 | ||
|
|
8e3feb81ca | ||
|
|
9a16c20dad | ||
|
|
17eb816ec9 | ||
|
|
79be5096d3 | ||
|
|
4fea383519 | ||
|
|
ca9ac52fae | ||
|
|
9cb25a585a | ||
|
|
17033254d4 | ||
|
|
2f5e0fc772 | ||
|
|
a35ceb9531 | ||
|
|
fbbd79babc | ||
|
|
501745b7ea | ||
|
|
6ea84ac8cd | ||
|
|
76c4333a81 | ||
|
|
ac459593a0 | ||
|
|
c5dac42da0 | ||
|
|
ef2200c257 | ||
|
|
fcb5f6747c | ||
|
|
dd75650a46 | ||
|
|
4872590b28 | ||
|
|
cc127bd040 | ||
|
|
9dd82a3cfb | ||
|
|
9340cf229c | ||
|
|
7c97fa9461 | ||
|
|
ef22aca5ff | ||
|
|
e71b63d261 | ||
|
|
e677ce0839 | ||
|
|
371f9d711a | ||
|
|
a5320dddb9 | ||
|
|
92b83c8d32 |
11
.eslintrc
11
.eslintrc
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"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
|
||||
}
|
||||
}
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -1 +1 @@
|
||||
src/* @jacoobes
|
||||
* @jacoobes
|
||||
|
||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
open_collective: sern
|
||||
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
33
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] Write a descriptive title."
|
||||
labels: bug
|
||||
assignees: EvolutionX-10, jacoobes, Murtatrxx
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Versioning**
|
||||
NodeJS version:
|
||||
DiscordJS version:
|
||||
SernHandler version:
|
||||
Channel: (e.g. beta)
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Sern Community Support
|
||||
url: https://discord.gg/9w8jzsR48U
|
||||
about: Please ask and answer questions here.
|
||||
43
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
43
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Ask for things that are not in sern
|
||||
title: "[Feature] Request a feature"
|
||||
labels: feature
|
||||
assignees: EvolutionX-10, jacoobes, Murtatrxx
|
||||
|
||||
---
|
||||
Request a new feature!
|
||||
---
|
||||
### Is your proposal related to a problem?
|
||||
|
||||
<!--
|
||||
Provide a clear and concise description of what the problem is.
|
||||
For example, "I'm always frustrated when..."
|
||||
-->
|
||||
|
||||
(Write your answer here.)
|
||||
|
||||
### Describe the solution you'd like
|
||||
|
||||
<!--
|
||||
Provide a clear and concise description of what you want to happen.
|
||||
-->
|
||||
|
||||
(Describe your proposed solution here.)
|
||||
|
||||
### Describe alternatives you've considered
|
||||
|
||||
<!--
|
||||
Let us know about other solutions you've tried or researched.
|
||||
-->
|
||||
|
||||
(Write your answer here.)
|
||||
|
||||
### Additional context
|
||||
|
||||
<!--
|
||||
Is there anything else you can add about the proposal?
|
||||
You might want to link to related issues here, if you haven't already.
|
||||
-->
|
||||
|
||||
(Write your answer here.)
|
||||
26
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal file
26
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Description
|
||||
|
||||
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
|
||||
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
Please delete options that are not relevant.
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] This change requires a documentation update
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
- [ ] Any dependent changes have been merged and published in downstream modules
|
||||
|
||||
36
.github/SECURITY.md
vendored
36
.github/SECURITY.md
vendored
@@ -6,13 +6,43 @@ Project is currently under heavy development but you can try out our [npm packag
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.1.0 @ dev | :white_check_mark: |
|
||||
| 2.6.1 | YES |
|
||||
| 2.6.0 | YES |
|
||||
| 2.5.3 | YES |
|
||||
| 2.5.2 | YES |
|
||||
| 2.5.1 | YES |
|
||||
| 2.5.0 | YES |
|
||||
| 2.1.1 | NO |
|
||||
| 2.1.0 | NO |
|
||||
| 2.0.0 | NO |
|
||||
| 1.2.1 | NO |
|
||||
| 1.2.0 | NO |
|
||||
| 1.1.0 | NO |
|
||||
1.0.1 | NO
|
||||
1.0.0 | NO
|
||||
1.1.9 @ beta | NO
|
||||
1.1.8 @ beta | NO
|
||||
1.1.7 @ beta | NO
|
||||
1.1.6 @ beta | NO
|
||||
1.1.5 @ beta | NO
|
||||
1.1.4 @ beta | NO
|
||||
1.1.3 @ beta | NO
|
||||
1.1.2 @ beta | NO
|
||||
1.1.1 @ beta | NO
|
||||
1.1.0 @ beta | NO
|
||||
1.0.4 @ beta | NO
|
||||
1.0.3 @ beta | NO
|
||||
1.0.2 @ beta | NO
|
||||
1.0.1 @ beta | NO
|
||||
1.0.0 @ beta | NO
|
||||
0.0.1 @ dev | NO (TRY IT)
|
||||
|
||||
* Dev versions might include bugs, use it with your own risk.
|
||||
|
||||
* Dev versions might include bugs and not supported use stable versions.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
You can report a vulnerability by opening an issue on the [project's GitHub](https://github.com/SernHandler/Sern/issues) repository.
|
||||
You can report a vulnerability by opening an issue on the [project's GitHub](https://github.com/sern-handler/handler/issues) repository.
|
||||
|
||||
Please provide as much information as possible when reporting a vulnerability. We are looking information for, the affected version, and the steps to reproduce the vulnerability.
|
||||
|
||||
|
||||
37
.github/workflows/codeql-analysis.yml
vendored
37
.github/workflows/codeql-analysis.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
schedule:
|
||||
- cron: '37 20 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
20
.github/workflows/dependency-review.yml
vendored
Normal file
20
.github/workflows/dependency-review.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# Dependency Review Action
|
||||
#
|
||||
# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
|
||||
#
|
||||
# Source repository: https://github.com/actions/dependency-review-action
|
||||
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
|
||||
name: 'Dependency Review'
|
||||
on: [pull_request]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
dependency-review:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@0efb1d1d84fc9633afcdaad14c485cbbc90ef46c # v2
|
||||
34
.github/workflows/npm-publish-dev.yml
vendored
Normal file
34
.github/workflows/npm-publish-dev.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: Continuous Delivery
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'package.json'
|
||||
|
||||
jobs:
|
||||
Publish:
|
||||
name: Publishing Dev
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3
|
||||
with:
|
||||
node-version: 18
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- name: Install Node.js dependencies
|
||||
run: npm i && npm run build:dev
|
||||
|
||||
- name: Publish to npm
|
||||
run: |
|
||||
npm version premajor --preid "dev.$(git rev-parse --verify --short HEAD)" --git-tag-version=false
|
||||
npm publish --tag dev
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
36
.github/workflows/npm-publish.yml
vendored
36
.github/workflows/npm-publish.yml
vendored
@@ -1,30 +1,22 @@
|
||||
name: NPM / Publish
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
workflow_dispatch:
|
||||
# We only publish if the version of sern handler is different. workflow automatically cancels if verson is the same
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
jobs:
|
||||
build:
|
||||
test-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
|
||||
- uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
|
||||
publish-npm:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
node-version: 18
|
||||
- run: npm i
|
||||
- run: npm run build:prod
|
||||
- uses: JS-DevTools/npm-publish@0f451a94170d1699fd50710966d48fb26194d939 # v1
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- run: npm ci
|
||||
- run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
||||
token: ${{ secrets.NPM_TOKEN }}
|
||||
access: "public"
|
||||
|
||||
14
.github/workflows/release-please.yml
vendored
Normal file
14
.github/workflows/release-please.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: release-please
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
release-please:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@db8f2c60ee802b3748b512940dde88eabd7b7e01 # v3
|
||||
with:
|
||||
release-type: node
|
||||
package-name: release-please-action
|
||||
bump-patch-for-minor-pre-major: true
|
||||
|
||||
28
.github/workflows/test.yml
vendored
Normal file
28
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x, 19.x, 20.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm run test
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -82,5 +82,18 @@ dist
|
||||
# VisualStudio Config file
|
||||
.vs
|
||||
|
||||
# VSCode settings and cache
|
||||
.vscode
|
||||
|
||||
# IntelliJ IDEA Config file
|
||||
.idea/
|
||||
|
||||
# Yarn files
|
||||
.yarn/install-state.gz
|
||||
.yarn/build-state.yml
|
||||
|
||||
.yalc
|
||||
|
||||
yalc.lock
|
||||
|
||||
*.svg
|
||||
|
||||
19
.npmignore
19
.npmignore
@@ -1,5 +1,4 @@
|
||||
src/
|
||||
tsconfig.json
|
||||
docs/
|
||||
.gitignore
|
||||
|
||||
@@ -9,7 +8,7 @@ logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.yarn
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
@@ -54,6 +53,7 @@ typings/
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
@@ -81,9 +81,7 @@ typings/
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# TypeScript build output
|
||||
dist
|
||||
|
||||
test
|
||||
# VisualStudio Config file
|
||||
.vs
|
||||
|
||||
@@ -106,4 +104,13 @@ CODE_OF_CONDUCT.md
|
||||
|
||||
babel.config.js
|
||||
|
||||
tests/
|
||||
tsup.config.js
|
||||
|
||||
tsconfig-base.json
|
||||
|
||||
tsconfig-cjs.json
|
||||
|
||||
tsconfig-esm.json
|
||||
|
||||
renovate.json
|
||||
fortnite
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
.github/
|
||||
*.md
|
||||
*.md
|
||||
dist/
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"semi": true,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 4,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
426
CHANGELOG.md
Normal file
426
CHANGELOG.md
Normal file
@@ -0,0 +1,426 @@
|
||||
# Changelog
|
||||
|
||||
## [4.2.4](https://github.com/sern-handler/handler/compare/v4.2.3...v4.2.4) (2025-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* flat autocomplete ([#395](https://github.com/sern-handler/handler/issues/395)) ([89d7409](https://github.com/sern-handler/handler/commit/89d74095363befddc3222b9e5c89c35e7c6457b9))
|
||||
|
||||
## [4.2.3](https://github.com/sern-handler/handler/compare/v4.2.2...v4.2.3) (2025-03-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* autocomplete sdt.module not present ([#393](https://github.com/sern-handler/handler/issues/393)) ([2414992](https://github.com/sern-handler/handler/commit/2414992b73a40065464b20f2d53826c78fcd3a5f))
|
||||
|
||||
## [4.2.2](https://github.com/sern-handler/handler/compare/v4.2.1...v4.2.2) (2025-02-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* faster autocomplete lookup ([#387](https://github.com/sern-handler/handler/issues/387)) ([974c30f](https://github.com/sern-handler/handler/commit/974c30fa6cccaae7b1c2c3246ffa9eecb6bc7bf9))
|
||||
|
||||
## [4.2.1](https://github.com/sern-handler/handler/compare/v4.2.0...v4.2.1) (2025-01-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* context-interactions error ([#382](https://github.com/sern-handler/handler/issues/382)) ([a52ad27](https://github.com/sern-handler/handler/commit/a52ad270d843e92db5bf2049d07527eed59d428c))
|
||||
|
||||
## [4.2.0](https://github.com/sern-handler/handler/compare/v4.1.1...v4.2.0) (2025-01-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* 4.2.0 load multiple directories & `handleModuleErrors` ([#378](https://github.com/sern-handler/handler/issues/378)) ([f9e7eaf](https://github.com/sern-handler/handler/commit/f9e7eaf92d22b76d3d02a1bbe8324ca6813f48f8))
|
||||
|
||||
## [4.1.1](https://github.com/sern-handler/handler/compare/v4.1.0...v4.1.1) (2025-01-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove rxjs ([#376](https://github.com/sern-handler/handler/issues/376)) ([59d08ef](https://github.com/sern-handler/handler/commit/59d08ef207c486ce1cf0aba267e6f862838e0dfb))
|
||||
* This puts the light back into lightweight (\- 4.1 MB)
|
||||
|
||||
## [4.1.0](https://github.com/sern-handler/handler/compare/v4.0.3...v4.1.0) (2025-01-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* moduleinfo-in-eventplugins ([#373](https://github.com/sern-handler/handler/issues/373)) ([220a60e](https://github.com/sern-handler/handler/commit/220a60ecf853df8d288de2533c669562a430c3f9))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* update github username ([#371](https://github.com/sern-handler/handler/issues/371)) ([55715d5](https://github.com/sern-handler/handler/commit/55715d565990fe686159f3c1eda3754d1262c72c))
|
||||
|
||||
## [4.0.3](https://github.com/sern-handler/handler/compare/v4.0.2...v4.0.3) (2024-10-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* async presence ([#369](https://github.com/sern-handler/handler/issues/369)) ([eabfb81](https://github.com/sern-handler/handler/commit/eabfb81819b53a4656d8eac6e21cfb488b724a42))
|
||||
* fix eventModule typing for Discord events ([#368](https://github.com/sern-handler/handler/issues/368)) ([1789ccb](https://github.com/sern-handler/handler/commit/1789ccb2f22f502f87538fecdb07106ff7110434))
|
||||
|
||||
## [4.0.2](https://github.com/sern-handler/handler/compare/v4.0.1...v4.0.2) (2024-08-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* type issue ([2106cdc](https://github.com/sern-handler/handler/commit/2106cdc1d033f88b6ee4ccca6754fe7a595a9328))
|
||||
|
||||
## [4.0.1](https://github.com/sern-handler/handler/compare/v4.0.0...v4.0.1) (2024-07-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add SDT typings to autocomplete commands ([#363](https://github.com/sern-handler/handler/issues/363)) ([92623d2](https://github.com/sern-handler/handler/commit/92623d2914fb80e31365f06cf896bb37f36fc814))
|
||||
|
||||
## [4.0.0](https://github.com/sern-handler/handler/compare/v3.3.4...v4.0.0) (2024-07-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* v4 ([#361](https://github.com/sern-handler/handler/issues/361)) ([9a8904f](https://github.com/sern-handler/handler/commit/9a8904f5aed4fa36b018ad73bbe58049bae33274))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 4.0.0 ([dda0e33](https://github.com/sern-handler/handler/commit/dda0e3395b6704862bfd3fda2a201e2cb9b45d2f))
|
||||
|
||||
## [3.3.4](https://github.com/sern-handler/handler/compare/v3.3.3...v3.3.4) (2024-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* sern emitter err ([#358](https://github.com/sern-handler/handler/issues/358)) ([90e55df](https://github.com/sern-handler/handler/commit/90e55dfa1466c91e5da48922251309331921b1ef))
|
||||
|
||||
## [3.3.3](https://github.com/sern-handler/handler/compare/v3.3.2...v3.3.3) (2024-02-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* rm deprecated class modules, clean up, rm indirection ([#355](https://github.com/sern-handler/handler/issues/355)) ([48f9f6e](https://github.com/sern-handler/handler/commit/48f9f6ec16e650d574bd24dcbb0ed176933bfe17))
|
||||
* singleton init not being fired when inserting function ([07b11b3](https://github.com/sern-handler/handler/commit/07b11b357baac0c3c7055c022bc353995c80f766))
|
||||
* typings and cleanup ([#356](https://github.com/sern-handler/handler/issues/356)) ([ce8c4bf](https://github.com/sern-handler/handler/commit/ce8c4bf6492b9680fb1c1a530d3e0028f214ad2f))
|
||||
|
||||
## [3.3.2](https://github.com/sern-handler/handler/compare/v3.3.1...v3.3.2) (2024-01-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* presence feature not working on cjs applications ([#351](https://github.com/sern-handler/handler/issues/351)) ([4f23871](https://github.com/sern-handler/handler/commit/4f2387119acfde036d0d1626553e9050f55627d1))
|
||||
|
||||
## [3.3.1](https://github.com/sern-handler/handler/compare/v3.3.0...v3.3.1) (2024-01-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* crashing when slash command is used as text command ([#349](https://github.com/sern-handler/handler/issues/349)) ([a359f73](https://github.com/sern-handler/handler/commit/a359f73fa24127a4964d411c8c1c0dfea5edc0f1))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* the last commit ([655bb8d](https://github.com/sern-handler/handler/commit/655bb8d35815fe0ce9797d8b169310a07b284ae0))
|
||||
|
||||
## [3.3.0](https://github.com/sern-handler/handler/compare/v3.2.1...v3.3.0) (2023-12-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* presence ([#345](https://github.com/sern-handler/handler/issues/345)) ([7458bef](https://github.com/sern-handler/handler/commit/7458befe8a5900480cd71900df02a8364837dc00))
|
||||
|
||||
## [3.2.1](https://github.com/sern-handler/handler/compare/v3.2.0...v3.2.1) (2023-12-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* logger swap failing ([daac37c](https://github.com/sern-handler/handler/commit/daac37c28858c42b21042bdcb8141239db634e7d))
|
||||
|
||||
## [3.2.0](https://github.com/sern-handler/handler/compare/v3.1.1...v3.2.0) (2023-12-15)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 3.2.0 ([237c853](https://github.com/sern-handler/handler/commit/237c8537c66052309d7e13a7e6e0a4f7995c2558))
|
||||
|
||||
## [3.1.1](https://github.com/sern-handler/handler/compare/v3.1.0...v3.1.1) (2023-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* queuing events ([fd39858](https://github.com/sern-handler/handler/commit/fd39858636d3038abb6d91021b65c99c488a3d6e))
|
||||
* queuing events ([#332](https://github.com/sern-handler/handler/issues/332)) @Benzo-Fury ([#333](https://github.com/sern-handler/handler/issues/333)) ([fd39858](https://github.com/sern-handler/handler/commit/fd39858636d3038abb6d91021b65c99c488a3d6e))
|
||||
|
||||
## [3.1.0](https://github.com/sern-handler/handler/compare/v3.0.2...v3.1.0) (2023-09-04)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add guaranteed `channelId` and `userId` getters to `Context` ([#320](https://github.com/sern-handler/handler/issues/320)) ([50253ca](https://github.com/sern-handler/handler/commit/50253ca322e7d6dbd2313139c0187a1028f71109))
|
||||
* dispose hooks (deprecate useContainerRaw) ([#323](https://github.com/sern-handler/handler/issues/323)) ([26ccd11](https://github.com/sern-handler/handler/commit/26ccd118ff8cbcde94158a4d09fc0df18da9f254))
|
||||
|
||||
## [3.0.2](https://github.com/sern-handler/handler/compare/v3.0.1...v3.0.2) (2023-08-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* invalid id for cts, mts, cjs, mjs files, node paths ([#318](https://github.com/sern-handler/handler/issues/318)) ([a7f5ea2](https://github.com/sern-handler/handler/commit/a7f5ea269fb344e221d10dbdc26a1611ffc8138f))
|
||||
|
||||
## [3.0.1](https://github.com/sern-handler/handler/compare/v3.0.0...v3.0.1) (2023-08-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* collectors ([4134460](https://github.com/sern-handler/handler/commit/41344608c677b6069c46412f5f16e4337182ca7d))
|
||||
|
||||
## [3.0.0](https://github.com/sern-handler/handler/compare/v2.6.3...v3.0.0) (2023-07-29)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* v3 ([#294](https://github.com/sern-handler/handler/issues/294))
|
||||
|
||||
### Features
|
||||
|
||||
* v3 ([#294](https://github.com/sern-handler/handler/issues/294)) ([7798e36](https://github.com/sern-handler/handler/commit/7798e36458c7f555d2bcb8a5857a6db47b7211da))
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 3.0.0 ([70cca0d](https://github.com/sern-handler/handler/commit/70cca0dbb01e70b47a8c899b1fc4f43dee5ed8ed))
|
||||
|
||||
## [2.6.3](https://github.com/sern-handler/handler/compare/v2.6.2...v2.6.3) (2023-06-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* autocomplete nested option and merge main ([5fdc1ed](https://github.com/sern-handler/handler/commit/5fdc1eda7f4fcc1f94af7eca661660c0edeb3251))
|
||||
|
||||
## [2.6.2](https://github.com/sern-handler/handler/compare/v2.6.1...v2.6.2) (2023-04-15)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 2.6.2 ([c1f6906](https://github.com/sern-handler/handler/commit/c1f690633c55ba41db1e035b7c16f9e19c70b385))
|
||||
|
||||
## [2.6.1](https://github.com/sern-handler/handler/compare/v2.6.0...v2.6.1) (2023-03-17)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 2.6.1 ([f9609ce](https://github.com/sern-handler/handler/commit/f9609ce6cd777fa0eb595d8c48d57905bbce5966))
|
||||
|
||||
## [2.6.0](https://github.com/sern-handler/handler/compare/v2.5.3...v2.6.0) (2023-03-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* adding pure annotation for better tree shaking ([d20d015](https://github.com/sern-handler/handler/commit/d20d01524b872549da501e21feec147ab204f397))
|
||||
|
||||
## [2.5.3](https://github.com/sern-handler/handler/compare/v2.5.2...v2.5.3) (2023-02-16)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 2.5.3 ([ce9a083](https://github.com/sern-handler/handler/commit/ce9a0831a6e47dd38648f34653f0bd89b1d2e48e))
|
||||
|
||||
## [2.5.2](https://github.com/sern-handler/handler/compare/v2.5.1...v2.5.2) (2023-02-16)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* version ([facee79](https://github.com/sern-handler/handler/commit/facee79c904ad663d3c57ce56fb825419fcc12f9))
|
||||
|
||||
## [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 SernHandler are expected to abide by our Code of Conduct, both online and during in-person events that are hosted and/or associated with SernHandler.
|
||||
All participants of sern are expected to abide by our Code of Conduct, both online and during in-person events that are hosted and/or associated with sern.
|
||||
|
||||
# The Pledge
|
||||
In the interest of fostering an open and welcoming environment, we pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
@@ -26,7 +26,7 @@ Examples of behaviour that contributes to creating a positive environment includ
|
||||
• Other conduct which you know could reasonably be considered inappropriate in a professional setting. <br/>
|
||||
• Enforcement. <br/>
|
||||
|
||||
Violations of the Code of Conduct may be reported by sending a message to [discord server](https://discord.com/). All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately.
|
||||
Violations of the Code of Conduct may be reported by reaching us on our [discord server](https://discord.com/). All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
We hold the right and responsibility to remove comments or other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any members for other behaviours that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
**Copyright (c) 2022 Sern**
|
||||
Copyright (c) 2025 sern
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -19,5 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
End license text.
|
||||
120
README.md
120
README.md
@@ -1,83 +1,79 @@
|
||||
# SernHandler
|
||||
<div align="center">
|
||||
<img src="https://raw.githubusercontent.com/sern-handler/.github/main/banner.png" width="900px" />
|
||||
</div>
|
||||
|
||||
<a href="https://www.npmjs.com/package/@sern/handler"><img src="https://img.shields.io/npm/v/@sern/handler?maxAge=3600" alt="NPM version" /></a>
|
||||
<a href="https://www.npmjs.com/package/@sern/handler"><img src="https://img.shields.io/npm/dt/@sern/handler?maxAge=3600" alt="NPM downloads" /></a>
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
<h1 align="center">Handlers. Redefined.</h1>
|
||||
<h4 align="center">A complete, customizable, typesafe, & reactive framework for discord bots</h4>
|
||||
|
||||
A customizable, batteries-included, powerful discord.js framework to automate and streamline your bot development.
|
||||
<div align="center" styles="margin-top: 10px">
|
||||
<img src="https://img.shields.io/badge/open-source-brightgreen" />
|
||||
<img src="https://img.shields.io/badge/built_with-sern-pink?labelColor=%230C3478&color=%23ed5087&link=https%3A%2F%2Fsern.dev"/>
|
||||
<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>
|
||||
|
||||
## Why?
|
||||
- For you. A framework that's tailored to your exact needs.
|
||||
- Lightweight. Does a lot while being small.
|
||||
- Latest features. Support for discord.js v14 and all of its interactions.
|
||||
- Start quickly. Plug and play or customize to your liking.
|
||||
- Works with [bun](https://bun.sh/) and [node](https://nodejs.org/en) out the box!
|
||||
- Use it with TypeScript or JavaScript. CommonJS and ESM supported.
|
||||
- Active and growing community, always here to help. [Join us](https://sern.dev/discord)
|
||||
- Unleash its full potential with a powerful CLI and awesome plugins.
|
||||
|
||||
## Installation
|
||||
## 📜 Installation
|
||||
[Start here!!](https://sern.dev/v4/reference/getting-started)
|
||||
|
||||
```sh
|
||||
npm install @sern/handler
|
||||
```
|
||||
## 👶 Basic Usage
|
||||
<details><summary>ping.ts</summary>
|
||||
|
||||
```sh
|
||||
yarn add @sern/handler
|
||||
```
|
||||
|
||||
```sh
|
||||
pnpm add @sern/handler
|
||||
```
|
||||
|
||||
## Basic Usage
|
||||
|
||||
#### ` index.js (CommonJS)`
|
||||
|
||||
```js
|
||||
const { Client, GatewayIntentBits } = require('discord.js');
|
||||
const { Sern } = require('sern-handler');
|
||||
const { defaultPrefix, token } = require('./config.json');
|
||||
|
||||
const client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.GuildMessages
|
||||
]
|
||||
```ts
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
//Installed plugin to publish to discord api and allow access to owners only.
|
||||
plugins: [publish(), ownerOnly()],
|
||||
description: 'A ping pong command',
|
||||
execute(ctx) {
|
||||
ctx.reply('Hello owner of the bot');
|
||||
}
|
||||
});
|
||||
|
||||
Sern.init({
|
||||
client,
|
||||
defaultPrefix,
|
||||
commands : 'src/commands',
|
||||
});
|
||||
|
||||
client.login(token);
|
||||
```
|
||||
</details>
|
||||
|
||||
#### ` ping.js (CommonJS)`
|
||||
# Show off your sern Discord Bot!
|
||||
|
||||
```js
|
||||
const { Sern, CommandType } = require('@sern/handler');
|
||||
## Badge
|
||||
- Copy this and add it to your [README.md](https://img.shields.io/badge/built_with-sern-pink?labelColor=%230C3478&color=%23ed5087&link=https%3A%2F%2Fsern.dev)
|
||||
<img src="https://img.shields.io/badge/built_with-sern-pink?labelColor=%230C3478&color=%23ed5087&link=https%3A%2F%2Fsern.dev">
|
||||
|
||||
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 to your favorite artists on Discord.
|
||||
- [Murayama](https://github.com/murayamabot/murayama) - :pepega:
|
||||
- [Protector](https://github.com/GlitchApotamus/Protector) - Just a simple bot to help enhance a private Minecraft server.
|
||||
- [SmokinWeed 💨](https://github.com/Peter-MJ-Parker/sern-bud) - A fun bot for a small, but growing server.
|
||||
- [Man Nomic](https://github.com/jacoobes/man-nomic) - A simple information bot to provide information to the nomic-ai Discord community.
|
||||
- [Linear-Discord](https://github.com/sern-handler/linear-discord) - Display and manage a linear dashboard.
|
||||
- [ZenithBot](https://github.com/CodeCraftersHaven/ZenithBot) - A versatile bot coded in TypeScript, designed to enhance server management and user interaction through its robust features.
|
||||
|
||||
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
|
||||
|
||||
- [Official Documentation](https://sern-handler.js.org)
|
||||
- [Support Server](https://discord.com/invite/mmyCTnYtbF)
|
||||
|
||||
## Contribute
|
||||
## 🔗 Links
|
||||
|
||||
- Read our contribution [guidelines](https://github.com/sern-handler/handler) carefully
|
||||
- [Official Documentation and Guide](https://sern.dev)
|
||||
- [Support Server](https://sern.dev/discord)
|
||||
|
||||
## 👋 Contribute
|
||||
- Read our contribution [guidelines](https://github.com/sern-handler/handler/blob/main/.github/CONTRIBUTING.md) 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.
|
||||
|
||||
4
bot/.gitignore
vendored
Normal file
4
bot/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
/node_modules
|
||||
/dist
|
||||
.env
|
||||
.sern
|
||||
6
bot/README.md
Normal file
6
bot/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Test bot
|
||||
|
||||
|
||||
## add .env
|
||||
DISCORD_TOKEN=<token>
|
||||
NODE_ENV=<production|development>
|
||||
12
bot/assets/locals/en-US.json
Normal file
12
bot/assets/locals/en-US.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"command/ping": {
|
||||
"name": "ping",
|
||||
"description": "yeth",
|
||||
"options": {
|
||||
"asdfs": {
|
||||
"name": "shidenglish",
|
||||
"description": "yeah"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
bot/assets/locals/es-ES.json
Normal file
12
bot/assets/locals/es-ES.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"command/ping": {
|
||||
"name": "ping",
|
||||
"description": "hola",
|
||||
"options": {
|
||||
"asdfs": {
|
||||
"name": "shidspnaol",
|
||||
"description": "si"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
bot/assets/test.txt
Normal file
1
bot/assets/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
{ "sdfasdfas": "asdf" }
|
||||
418
bot/package-lock.json
generated
Normal file
418
bot/package-lock.json
generated
Normal file
@@ -0,0 +1,418 @@
|
||||
{
|
||||
"name": "plugtest",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "plugtest",
|
||||
"version": "1.0.0",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@sern/handler": "file:../",
|
||||
"@sern/localizer": "^1.1.3",
|
||||
"@sern/publisher": "^1.1.2",
|
||||
"discord.js": "^14.15.0",
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.25",
|
||||
"typescript": "latest"
|
||||
}
|
||||
},
|
||||
"..": {
|
||||
"name": "@sern/handler",
|
||||
"version": "4.2.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sern/ioc": "^1.1.2",
|
||||
"callsites": "^3.1.0",
|
||||
"cron": "^3.1.7",
|
||||
"deepmerge": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.1",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "5.58.0",
|
||||
"@typescript-eslint/parser": "5.59.1",
|
||||
"discord.js": "^14.14.1",
|
||||
"eslint": "8.39.0",
|
||||
"typescript": "5.0.2",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.0.x"
|
||||
}
|
||||
},
|
||||
"../../tools/packages/builder": {
|
||||
"name": "@sern/builder",
|
||||
"version": "1.0.0-rc1",
|
||||
"extraneous": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"discord-api-types": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.1.0"
|
||||
}
|
||||
},
|
||||
"../handler": {
|
||||
"name": "@sern/handler",
|
||||
"version": "4.2.3",
|
||||
"extraneous": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sern/ioc": "^1.1.2",
|
||||
"callsites": "^3.1.0",
|
||||
"cron": "^3.1.7",
|
||||
"deepmerge": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.1",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "5.58.0",
|
||||
"@typescript-eslint/parser": "5.59.1",
|
||||
"discord.js": "^14.14.1",
|
||||
"eslint": "8.39.0",
|
||||
"typescript": "5.0.2",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.0.x"
|
||||
}
|
||||
},
|
||||
"../tools/packages/builder": {
|
||||
"name": "@sern/builder",
|
||||
"version": "1.0.0-rc1",
|
||||
"extraneous": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"discord-api-types": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.1.0"
|
||||
}
|
||||
},
|
||||
"../tools/packages/localizer": {
|
||||
"name": "@sern/localizer",
|
||||
"version": "1.1.1",
|
||||
"extraneous": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"shrimple-locales": "^0.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sern/handler": "^4.0.0",
|
||||
"discord.js": "^14.15.3",
|
||||
"vitest": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/builders": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.0.tgz",
|
||||
"integrity": "sha512-ikVZsZP+3shmVJ5S1oM+7SveUCK3L9fTyfA8aJ7uD9cNQlTqF+3Irbk2Y22KXTb3C3RNUahRkSInClJMkHrINg==",
|
||||
"dependencies": {
|
||||
"@discordjs/formatters": "^0.6.0",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@sapphire/shapeshift": "^4.0.0",
|
||||
"discord-api-types": "^0.37.114",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.4",
|
||||
"tslib": "^2.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/collection": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
|
||||
"integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==",
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/formatters": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz",
|
||||
"integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==",
|
||||
"dependencies": {
|
||||
"discord-api-types": "^0.37.114"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.2.tgz",
|
||||
"integrity": "sha512-9bOvXYLQd5IBg/kKGuEFq3cstVxAMJ6wMxO2U3wjrgO+lHv8oNCT+BBRpuzVQh7BoXKvk/gpajceGvQUiRoJ8g==",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.1",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@sapphire/async-queue": "^1.5.3",
|
||||
"@sapphire/snowflake": "^3.5.3",
|
||||
"@vladfrangu/async_event_emitter": "^2.4.6",
|
||||
"discord-api-types": "^0.37.114",
|
||||
"magic-bytes.js": "^1.10.0",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.19.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/rest/node_modules/@discordjs/collection": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/util": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz",
|
||||
"integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.0.tgz",
|
||||
"integrity": "sha512-QH5CAFe3wHDiedbO+EI3OOiyipwWd+Q6BdoFZUw/Wf2fw5Cv2fgU/9UEtJRmJa9RecI+TAhdGPadMaEIur5yJg==",
|
||||
"dependencies": {
|
||||
"@discordjs/collection": "^2.1.0",
|
||||
"@discordjs/rest": "^2.4.1",
|
||||
"@discordjs/util": "^1.1.0",
|
||||
"@sapphire/async-queue": "^1.5.2",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@vladfrangu/async_event_emitter": "^2.2.4",
|
||||
"discord-api-types": "^0.37.114",
|
||||
"tslib": "^2.6.2",
|
||||
"ws": "^8.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/ws/node_modules/@discordjs/collection": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz",
|
||||
"integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/async-queue": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz",
|
||||
"integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/shapeshift": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz",
|
||||
"integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"lodash": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v16"
|
||||
}
|
||||
},
|
||||
"node_modules/@sapphire/snowflake": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz",
|
||||
"integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sern/handler": {
|
||||
"resolved": "..",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@sern/localizer": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@sern/localizer/-/localizer-1.1.3.tgz",
|
||||
"integrity": "sha512-hTn0DtiAzIWSuokqMsvnVuFqU+P776p/Yv5etlrq+CWDgw332Hwuj3geyqN1C0yEjwF+ceyXJE/kGu2/inkEyg==",
|
||||
"dependencies": {
|
||||
"shrimple-locales": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sern/publisher": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@sern/publisher/-/publisher-1.1.2.tgz",
|
||||
"integrity": "sha512-1zh99JZykKUhqHhE75ZXfiLsBtf1WI+NnDCojv8UlpnGBEyzO8xyI1X7PNf6cPKRs4W9XqY3PqTJ+hrqzIsMkg=="
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "17.0.45",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz",
|
||||
"integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz",
|
||||
"integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@vladfrangu/async_event_emitter": {
|
||||
"version": "2.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz",
|
||||
"integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.37.119",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz",
|
||||
"integrity": "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg=="
|
||||
},
|
||||
"node_modules/discord.js": {
|
||||
"version": "14.17.3",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.17.3.tgz",
|
||||
"integrity": "sha512-8/j8udc3CU7dz3Eqch64UaSHoJtUT6IXK4da5ixjbav4NAXJicloWswD/iwn1ImZEMoAV3LscsdO0zhBh6H+0Q==",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^1.10.0",
|
||||
"@discordjs/collection": "1.5.3",
|
||||
"@discordjs/formatters": "^0.6.0",
|
||||
"@discordjs/rest": "^2.4.2",
|
||||
"@discordjs/util": "^1.1.1",
|
||||
"@discordjs/ws": "^1.2.0",
|
||||
"@sapphire/snowflake": "3.5.3",
|
||||
"discord-api-types": "^0.37.114",
|
||||
"fast-deep-equal": "3.1.3",
|
||||
"lodash.snakecase": "4.1.1",
|
||||
"tslib": "^2.6.3",
|
||||
"undici": "6.19.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/discordjs/discord.js?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "16.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.snakecase": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
|
||||
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
|
||||
},
|
||||
"node_modules/magic-bytes.js": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz",
|
||||
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ=="
|
||||
},
|
||||
"node_modules/shrimple-locales": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/shrimple-locales/-/shrimple-locales-0.2.1.tgz",
|
||||
"integrity": "sha512-j2vNBDXJgED3XqGXCD/vqXBSqwlDXP1iGkseVos8mCtZqHp3R+0FImx8xwtjeYufJcYfhjBMkaBTWgsBi8eJZw=="
|
||||
},
|
||||
"node_modules/ts-mixer": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz",
|
||||
"integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.7.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz",
|
||||
"integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
bot/package.json
Normal file
30
bot/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "plugtest",
|
||||
"version": "1.0.0",
|
||||
"description": "a descriptiuon",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"build": "sern build",
|
||||
"start": "node ./dist/index.js",
|
||||
"run": "node ./dist/index.js"
|
||||
},
|
||||
"keywords": [
|
||||
"typescript",
|
||||
"sern",
|
||||
"discord.js"
|
||||
],
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@sern/handler": "file:../",
|
||||
"@sern/localizer": "^1.1.3",
|
||||
"@sern/publisher": "^1.1.2",
|
||||
"discord.js": "^14.15.0",
|
||||
"dotenv": "^16.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^17.0.25",
|
||||
"typescript": "latest"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
11
bot/rm.py
Normal file
11
bot/rm.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import os
|
||||
|
||||
for root, dirs, files in os.walk('.'):
|
||||
for filename in files:
|
||||
if filename.endswith('.js'):
|
||||
file_path = os.path.join(root, filename)
|
||||
try:
|
||||
os.remove(file_path)
|
||||
print(f'Successfully deleted: {file_path}')
|
||||
except OSError as e:
|
||||
print(f'Error deleting {file_path}: {e.strerror}')
|
||||
13
bot/sern.config.json
Normal file
13
bot/sern.config.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"language": "typescript",
|
||||
"defaultPrefix": "!",
|
||||
"paths": {
|
||||
"base": "src",
|
||||
"commands": "commands",
|
||||
"events": "events"
|
||||
},
|
||||
"app": {
|
||||
"tags": ["Nice ass bot"],
|
||||
"description": "A bot"
|
||||
}
|
||||
}
|
||||
0
bot/src/commands/!plugins.ts
Normal file
0
bot/src/commands/!plugins.ts
Normal file
37
bot/src/commands/add.ts
Normal file
37
bot/src/commands/add.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
import { ApplicationCommandOptionType } from "discord.js";
|
||||
|
||||
export default commandModule({
|
||||
name: 'add',
|
||||
type: CommandType.Slash,
|
||||
description: 'Adds numbers together',
|
||||
options: [
|
||||
{
|
||||
type: ApplicationCommandOptionType.String,
|
||||
name: 'numbers',
|
||||
description: 'Numbers to add together separated by a space.',
|
||||
required: true,
|
||||
min_length: 3,
|
||||
},
|
||||
],
|
||||
execute: async (ctx) => {
|
||||
let numbers = ctx.options.getString('numbers')?.split(' ')!;
|
||||
|
||||
numbers = numbers.filter((num) => num !== '');
|
||||
|
||||
if (!numbers.every((num) => !isNaN(parseFloat(num)))) {
|
||||
return ctx.reply({
|
||||
content: 'You can only input numbers.',
|
||||
ephemeral: true,
|
||||
});
|
||||
}
|
||||
|
||||
const sum = numbers.reduce((acc, num) => acc + parseFloat(num), 0);
|
||||
|
||||
|
||||
return ctx.reply({
|
||||
content: `The sum is ${sum}`,
|
||||
ephemeral: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
8
bot/src/commands/admin/!plugins.ts
Normal file
8
bot/src/commands/admin/!plugins.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { filter, hasRole } from "../../plugins/filter.js";
|
||||
import { ownerOnly } from "../../plugins/ownerOnly.js";
|
||||
import { ADMIN } from '../../constants.js'
|
||||
|
||||
export default [
|
||||
ownerOnly(),
|
||||
filter({ condition: [hasRole(ADMIN)] })
|
||||
]
|
||||
11
bot/src/commands/admin/admin.ts
Normal file
11
bot/src/commands/admin/admin.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { commandModule, CommandType } from '@sern/handler'
|
||||
|
||||
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: "A",
|
||||
execute: (ctx, args) => {
|
||||
|
||||
}
|
||||
})
|
||||
14
bot/src/commands/btn.ts
Normal file
14
bot/src/commands/btn.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
import { json } from "../plugins/json-params.js";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Button,
|
||||
plugins: [json],
|
||||
execute(ctx, args) {
|
||||
console.log(args.state['json/data'])
|
||||
//@ts-ignore
|
||||
ctx.reply(args.state['json/data'].uid)
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
9
bot/src/commands/channelselect.ts
Normal file
9
bot/src/commands/channelselect.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.ChannelSelect,
|
||||
execute: (s) => {
|
||||
s.reply('clicked channel');
|
||||
}
|
||||
|
||||
});
|
||||
25
bot/src/commands/chicken.ts
Normal file
25
bot/src/commands/chicken.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
import { publishConfig } from "@sern/publisher";
|
||||
import { PermissionFlagsBits } from "discord.js";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
plugins: [
|
||||
publishConfig({
|
||||
integrationTypes: ['User'],
|
||||
contexts: [0,1,2],
|
||||
defaultMemberPermissions:
|
||||
PermissionFlagsBits.Speak
|
||||
| PermissionFlagsBits.Connect
|
||||
| PermissionFlagsBits.BanMembers
|
||||
|
||||
|
||||
|
||||
})
|
||||
],
|
||||
description: "yo",
|
||||
execute:(ctx) => {
|
||||
ctx.reply("hello");
|
||||
}
|
||||
|
||||
})
|
||||
103
bot/src/commands/collectors.ts
Normal file
103
bot/src/commands/collectors.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { CommandType, Context, commandModule } from "@sern/handler";
|
||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
|
||||
|
||||
export default commandModule ({
|
||||
type: CommandType.Slash,
|
||||
description: 'collectors',
|
||||
execute: async (ctx) => {
|
||||
//await close(ctx)
|
||||
await testCollect(ctx)
|
||||
}
|
||||
})
|
||||
|
||||
const testCollect = async (ctx: Context) => {
|
||||
const msgcmpt = ctx.interaction.channel?.createMessageComponentCollector()
|
||||
const buttonRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder().setCustomId("closeyes").setLabel("Yes").setStyle(ButtonStyle.Success),
|
||||
new ButtonBuilder().setCustomId("closeno").setLabel("No").setStyle(ButtonStyle.Danger)
|
||||
);
|
||||
ctx.reply({ components: [buttonRow] })
|
||||
msgcmpt?.on('collect', async button => {
|
||||
|
||||
await button.deferUpdate();
|
||||
if (button.customId === "closeyes") {
|
||||
try {
|
||||
await button.editReply('closing')
|
||||
} catch (e) {
|
||||
await button.editReply({ content: "An error has occurred and I could not close the ticket...", components: [] });
|
||||
}
|
||||
} else {
|
||||
await button.editReply({ content: "This ticket will remain open.", components: [] });
|
||||
msgcmpt.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
//export const quiz = async(client: Client, ctx: Context) => {
|
||||
// try {
|
||||
// const pokemon = Math.round(Math.random() * 890)
|
||||
// const question = `https://cdn.dagpi.xyz/wtp/pokemon/${pokemon}q.png`;
|
||||
// const answer = `https://cdn.dagpi.xyz/wtp/pokemon/${pokemon}a.png`;
|
||||
//
|
||||
// const correctPokemon = await (await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon}`)).json();
|
||||
// const allPokemon = await (await fetch("https://pokeapi.co/api/v2/pokemon?limit=899")).json();
|
||||
//
|
||||
// const options: string[] = [];
|
||||
// //client.utils.log("WARNING", "INFO", `Correct answer is ${correctPokemon.name}`);
|
||||
//
|
||||
// while (options.length < 9) {
|
||||
// let option = allPokemon.results[pokemon];
|
||||
// if (options.includes(option.name)) continue;
|
||||
// options.push(option.name);
|
||||
// }
|
||||
//
|
||||
// if (!options.includes(correctPokemon.name)) {
|
||||
// options.splice(client.utils.randomRange(0, 10), 0, correctPokemon.name.toLowerCase());
|
||||
// } else {
|
||||
// while (options.length < 10) {
|
||||
// let option = allPokemon.results[client.utils.randomRange(1, 890)];
|
||||
// if (options.includes(option.name)) continue;
|
||||
// options.push(option.name);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// const msgEmbed = (await client.utils.CustomEmbed({ userID: ctx.user.id })).setTitle("Who's that Pokémon?").setImage(question);
|
||||
// const row = new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(
|
||||
// new StringSelectMenuBuilder().setCustomId("pokequiz").addOptions(
|
||||
// options.sort().map((opt) => {
|
||||
// return { label: client.utils.titleCase(opt), value: opt.toLowerCase() };
|
||||
// })
|
||||
// )
|
||||
// );
|
||||
//
|
||||
// const msg = await client.utils.fetchReply(ctx.interaction, { embeds: [msgEmbed], components: [row] });
|
||||
// const filter = (i: StringSelectMenuInteraction) => i.user.id === ctx.user.id && i.message.id === msg.id;
|
||||
//
|
||||
// const collector = msg.createMessageComponentCollector({ filter, componentType: ComponentType.StringSelect, time: 1000 * 20 });
|
||||
// collector.on("collect", async (i) => {
|
||||
// const guess = i.values[0].toLowerCase();
|
||||
//
|
||||
// msgEmbed.setImage(answer).setTitle(`It's ${client.utils.titleCase(correctPokemon.name)}!`);
|
||||
//
|
||||
// if (guess === correctPokemon.name.toLowerCase()) msgEmbed.setColor("Green").setFooter({ text: "You're correct!" });
|
||||
// else msgEmbed.setColor("Red").setFooter({ text: `You guessed ${client.utils.titleCase(guess)}.` });
|
||||
//
|
||||
// await i.update({ embeds: [msgEmbed], components: [] });
|
||||
// collector.stop("Guessed");
|
||||
// });
|
||||
//
|
||||
// collector.on("end", async (i, reason) => {
|
||||
// if (reason === "Guessed") return;
|
||||
//
|
||||
// msgEmbed
|
||||
// .setImage(answer)
|
||||
// .setTitle(`It's ${client.utils.titleCase(correctPokemon.name)}!`)
|
||||
// .setColor("Red")
|
||||
// .setFooter({ text: "You did not guess in time." });
|
||||
//
|
||||
// await ctx.interaction.editReply({ embeds: [msgEmbed], components: [] });
|
||||
// });
|
||||
// } catch (e) {
|
||||
// client.utils.log("ERROR", __filename, `${e}`);
|
||||
// return ctx.interaction.reply({ content: "An error has occurred. Please try again.", ephemeral: true });
|
||||
// }
|
||||
//};
|
||||
6
bot/src/commands/dmMe.ts
Normal file
6
bot/src/commands/dmMe.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Modal,
|
||||
execute: (modal) => modal.reply('thanks')
|
||||
});
|
||||
24
bot/src/commands/flat-autocmp.ts
Normal file
24
bot/src/commands/flat-autocmp.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { commandModule, CommandType } from "@sern/handler";
|
||||
import { ApplicationCommandOptionType } from "discord.js";
|
||||
|
||||
export default commandModule({
|
||||
description: "testing",
|
||||
type: CommandType.Slash,
|
||||
options: [
|
||||
{
|
||||
name: "option",
|
||||
description: "option desc",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
required: true,
|
||||
autocomplete: true,
|
||||
command: {
|
||||
execute: (i) => {
|
||||
i.respond([{ name: "rah", value: "rah" }]);
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
execute: (ctx) => {
|
||||
return ctx.reply("rah");
|
||||
},
|
||||
});
|
||||
52
bot/src/commands/nested.ts
Normal file
52
bot/src/commands/nested.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { commandModule, CommandType } from "@sern/handler";
|
||||
import { ActionRowBuilder, ApplicationCommandOptionType, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description : 'a ping command',
|
||||
options: [
|
||||
{
|
||||
name: "nest",
|
||||
description: "testing nested",
|
||||
type: ApplicationCommandOptionType.SubcommandGroup,
|
||||
options : [
|
||||
{
|
||||
name: "nest",
|
||||
description: "testing nested",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
options : [
|
||||
{
|
||||
name: "sdfasd",
|
||||
description: "testing autocomplete",
|
||||
autocomplete: true,
|
||||
type: ApplicationCommandOptionType.String,
|
||||
command : {
|
||||
onEvent : [],
|
||||
async execute(autocmp, sdt) {
|
||||
//console.log(autocmp)
|
||||
const choices = ['butt', 'deez', 'lmao', 'lmfao', 'nuts', 'chicken'];
|
||||
await autocmp.respond(choices.map((e,i) => ({ name : e, value: i.toString()})));
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
async execute ({ interaction }) {
|
||||
const modal = new ModalBuilder()
|
||||
.setCustomId('dmMe')
|
||||
.setTitle('send something to my dm (nothing bad pls)');
|
||||
|
||||
const input = new TextInputBuilder()
|
||||
.setCustomId('message')
|
||||
.setLabel("Send something to me")
|
||||
.setStyle(TextInputStyle.Short);
|
||||
|
||||
const firstActionRow = new ActionRowBuilder<TextInputBuilder>().addComponents([input]);
|
||||
modal.addComponents([firstActionRow]);
|
||||
await interaction.showModal(modal);
|
||||
}
|
||||
});
|
||||
|
||||
8
bot/src/commands/ping-ctx-msg.ts
Normal file
8
bot/src/commands/ping-ctx-msg.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { CommandType, commandModule } from '@sern/handler'
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.CtxMsg,
|
||||
execute: (i, sdt) => {
|
||||
i.reply('pong msg')
|
||||
}
|
||||
})
|
||||
9
bot/src/commands/ping-ctx.ts
Normal file
9
bot/src/commands/ping-ctx.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.CtxUser,
|
||||
execute: (i, sdt) => {
|
||||
i.reply('pong')
|
||||
}
|
||||
})
|
||||
84
bot/src/commands/ping.ts
Normal file
84
bot/src/commands/ping.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import {commandModule, CommandType, controller, CommandInitPlugin, CommandControlPlugin } from '@sern/handler';
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
ChannelSelectMenuBuilder,
|
||||
RoleSelectMenuBuilder,
|
||||
UserSelectMenuBuilder,
|
||||
} from "discord.js";
|
||||
import { localize } from '@sern/localizer';
|
||||
|
||||
const plugin = CommandControlPlugin(() => {
|
||||
return controller.next({ a: 'from plugin1' });
|
||||
});
|
||||
|
||||
const plugin2 = CommandControlPlugin(() => {
|
||||
return controller.next({ a: 'from plugin2' });
|
||||
})
|
||||
|
||||
const updateDescription = (description: string) => {
|
||||
return CommandInitPlugin(() => {
|
||||
if(description.length > 100) {
|
||||
console.error("Description is invalid")
|
||||
return controller.stop("From updateDescription: description is invalid");
|
||||
}
|
||||
return controller.next({ description }); // continue to next plugin
|
||||
});
|
||||
};
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
plugins: [localize()],
|
||||
description: 'A ping command I just updated',
|
||||
options: [
|
||||
str(name("asdfs"),
|
||||
description("sdfds"))
|
||||
],
|
||||
execute: async (ctx, sdt) => {
|
||||
ctx.interaction
|
||||
const btn = new ButtonBuilder()
|
||||
.setStyle(ButtonStyle.Link)
|
||||
.setLabel("Click me")
|
||||
.setURL('https://www.youtube.com/watch?v=dQw4w9WgXcQ&pp=ygUIcmlja3JvbGw%3D')
|
||||
|
||||
const editButton = new ButtonBuilder({
|
||||
customId: `btn/{"uid":"1061421834341462036"}`,
|
||||
label: "click me also",
|
||||
emoji: "🛠",
|
||||
style: ButtonStyle.Primary,
|
||||
});
|
||||
|
||||
ctx.reply({ components: [
|
||||
new ActionRowBuilder<ButtonBuilder>().addComponents(btn, editButton),
|
||||
new ActionRowBuilder<UserSelectMenuBuilder>({
|
||||
components: [
|
||||
new UserSelectMenuBuilder({
|
||||
custom_id: "userselect",
|
||||
placeholder: "select channel",
|
||||
minValues: 1,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(
|
||||
new ChannelSelectMenuBuilder({
|
||||
custom_id: "channelselect",
|
||||
placeholder: "select channel",
|
||||
minValues: 1,
|
||||
|
||||
}),
|
||||
),
|
||||
new ActionRowBuilder<RoleSelectMenuBuilder>({
|
||||
components: [
|
||||
new RoleSelectMenuBuilder({
|
||||
custom_id: "roleselect",
|
||||
placeholder: "select role",
|
||||
minValues: 1,
|
||||
}),
|
||||
],
|
||||
})
|
||||
]})
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
9
bot/src/commands/roleselect.ts
Normal file
9
bot/src/commands/roleselect.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CommandType, commandModule } from "@sern/handler"
|
||||
|
||||
export default commandModule( {
|
||||
type: CommandType.RoleSelect,
|
||||
execute: (s) => {
|
||||
s.reply('selected role')
|
||||
}
|
||||
|
||||
})
|
||||
12
bot/src/commands/sernoptions.ts
Normal file
12
bot/src/commands/sernoptions.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: 'shid',
|
||||
execute({ interaction }) {
|
||||
interaction.reply('hello')
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
62
bot/src/commands/subcommandoption.ts
Normal file
62
bot/src/commands/subcommandoption.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { commandModule, CommandType } from '@sern/handler';
|
||||
import { ApplicationCommandOptionType } from 'discord.js';
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: 'A ping command',
|
||||
options: [
|
||||
{
|
||||
name: "art",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Lists out information about an Animal Crossing artwork.",
|
||||
options: [
|
||||
{
|
||||
name: "name",
|
||||
description: "The name of the artwork to lookup.",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
command: {
|
||||
async execute(ctx) {
|
||||
await ctx.respond([{ name: 'art', value: 'first' }])
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "villager",
|
||||
type: ApplicationCommandOptionType.Subcommand,
|
||||
description: "Lists out information about an Animal Crossing villager.",
|
||||
options: [
|
||||
{
|
||||
name: "name",
|
||||
description: "The name of the villager to lookup.",
|
||||
type: ApplicationCommandOptionType.String,
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
command: {
|
||||
onEvent: [],
|
||||
async execute(ctx) {
|
||||
await ctx.respond([{ name: 'villager', value: 'second' } ])
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
execute: async (ctx) => {
|
||||
const command = ctx.options.getSubcommand();
|
||||
switch (command) {
|
||||
case "art": {
|
||||
|
||||
ctx.reply('art');
|
||||
break;
|
||||
}
|
||||
case "villager": {
|
||||
ctx.reply('vil');
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
40
bot/src/commands/test-ctx.ts
Normal file
40
bot/src/commands/test-ctx.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { ApplicationCommandOptionType } from "discord.js";
|
||||
import { Service, commandModule, CommandType } from "@sern/handler";
|
||||
|
||||
export const config = {
|
||||
guildIds: ['941002690211766332']
|
||||
}
|
||||
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Both,
|
||||
description: 'tests context',
|
||||
options: [
|
||||
{
|
||||
type: ApplicationCommandOptionType.String,
|
||||
name: "hello",
|
||||
description: "wassup",
|
||||
required: false,
|
||||
}
|
||||
],
|
||||
async execute(ctx) {
|
||||
const logger = Service('@sern/logger');
|
||||
|
||||
if(ctx.isMessage()) {
|
||||
logger?.info({ message : ctx.message.content })
|
||||
logger?.info({ message : ctx.prefix })
|
||||
} else {
|
||||
logger?.info({ message : ctx.interaction.toString() })
|
||||
}
|
||||
|
||||
logger?.info({ message: ctx.id })
|
||||
logger?.info({ message: ctx.channel?.toString()! })
|
||||
logger?.info({ message: ctx.user.toString()! })
|
||||
logger?.info({ message: ctx.createdTimestamp.toString() })
|
||||
logger?.info({ message: ctx.guild?.toString() })
|
||||
logger?.info({ message: ctx.member?.toString() })
|
||||
logger?.info({ message: ctx.client })
|
||||
logger?.info({ message: ctx.inGuild })
|
||||
await ctx.reply("guayin bodishivatta")
|
||||
}
|
||||
})
|
||||
33
bot/src/commands/testing-neo.ts
Normal file
33
bot/src/commands/testing-neo.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
import { ActionRowBuilder, ModalActionRowComponentBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
|
||||
|
||||
const informationRequestModal = new ModalBuilder()
|
||||
.setCustomId("information-request")
|
||||
.setTitle("More Information")
|
||||
.addComponents(
|
||||
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
|
||||
new TextInputBuilder()
|
||||
.setCustomId("command-name")
|
||||
.setLabel("Command Name")
|
||||
.setPlaceholder("The name of the command that this bug occurred on.")
|
||||
.setStyle(TextInputStyle.Short)
|
||||
.setMinLength(4)
|
||||
.setMaxLength(20)
|
||||
.setRequired(true)));
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
plugins: [],
|
||||
description: "A random test command.",
|
||||
execute: async (ctx) => {
|
||||
await ctx.interaction.showModal(informationRequestModal);
|
||||
|
||||
await ctx.interaction
|
||||
.awaitModalSubmit({ time: 300_000 })
|
||||
.then(async (modal) => {
|
||||
modal.reply("thanks brody")
|
||||
})
|
||||
.catch(() => null);
|
||||
},
|
||||
});
|
||||
|
||||
8
bot/src/commands/userselect.ts
Normal file
8
bot/src/commands/userselect.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { CommandType, commandModule } from "@sern/handler";
|
||||
|
||||
export default commandModule( {
|
||||
type: CommandType.UserSelect,
|
||||
execute: (s) => {
|
||||
s.reply('selected user')
|
||||
}
|
||||
})
|
||||
1
bot/src/constants.ts
Normal file
1
bot/src/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const ADMIN = '983754333944434712'
|
||||
18
bot/src/dependencies.d.ts
vendored
Normal file
18
bot/src/dependencies.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import type {
|
||||
Logging,
|
||||
ErrorHandling,
|
||||
CoreDependencies
|
||||
} from '@sern/handler'
|
||||
import type { Publisher } from '@sern/publisher';
|
||||
import type { Localizer } from '@sern/localizer';
|
||||
declare global {
|
||||
interface Dependencies extends CoreDependencies {
|
||||
localizer: Localizer;
|
||||
publisher: Publisher
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
|
||||
|
||||
|
||||
10
bot/src/events/error.ts
Normal file
10
bot/src/events/error.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { EventType, eventModule } from "@sern/handler";
|
||||
|
||||
|
||||
export default eventModule({
|
||||
name: 'error',
|
||||
type: EventType.Sern,
|
||||
execute: (e) => {
|
||||
console.log(e)
|
||||
}
|
||||
})
|
||||
10
bot/src/events/messageCreate.ts
Normal file
10
bot/src/events/messageCreate.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { discordEvent } from "@sern/handler";
|
||||
|
||||
const execute = (...args: any[]) => {
|
||||
console.log(args[0].content)
|
||||
}
|
||||
export default discordEvent({
|
||||
name: 'messageCreate',
|
||||
once: true,
|
||||
execute
|
||||
})
|
||||
10
bot/src/events/module.activate.ts
Normal file
10
bot/src/events/module.activate.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import {eventModule, EventType} from "@sern/handler";
|
||||
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Sern,
|
||||
name: 'module.activate',
|
||||
execute(args) {
|
||||
|
||||
}
|
||||
})
|
||||
9
bot/src/events/modulesLoaded.ts
Normal file
9
bot/src/events/modulesLoaded.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { CommandType, EventType, Service, eventModule } from "@sern/handler";
|
||||
|
||||
|
||||
export default eventModule({
|
||||
type: EventType.Sern,
|
||||
execute: async () => {
|
||||
console.log('eventmodule: all loaded');
|
||||
}
|
||||
})
|
||||
8
bot/src/events/threadCreate.ts
Normal file
8
bot/src/events/threadCreate.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { discordEvent } from "@sern/handler";
|
||||
|
||||
export default discordEvent({
|
||||
name: 'threadCreate',
|
||||
execute(thread) {
|
||||
console.log(thread)
|
||||
}
|
||||
})
|
||||
41
bot/src/index.ts
Normal file
41
bot/src/index.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'dotenv/config';
|
||||
import { makeDependencies, Sern, Service } from '@sern/handler'
|
||||
import { Client, GatewayIntentBits, Partials } from 'discord.js';
|
||||
import { Publisher } from '@sern/publisher'
|
||||
import { Localization } from '@sern/localizer'
|
||||
|
||||
__DEV__: console.log(1);
|
||||
|
||||
const intents = GatewayIntentBits.Guilds |
|
||||
GatewayIntentBits.GuildMembers |
|
||||
GatewayIntentBits.GuildMessageReactions |
|
||||
GatewayIntentBits.GuildMessages |
|
||||
GatewayIntentBits.DirectMessages |
|
||||
GatewayIntentBits.MessageContent;
|
||||
|
||||
const partials = [
|
||||
Partials.Channel
|
||||
];
|
||||
|
||||
async function init() {
|
||||
await makeDependencies(({ add }) => {
|
||||
add('@sern/client', new Client({ intents, partials }));
|
||||
add('localizer', Localization());
|
||||
add('publisher', deps => {
|
||||
return new Publisher(deps['@sern/modules'],
|
||||
deps['@sern/emitter'],
|
||||
deps['@sern/logger']!)
|
||||
})
|
||||
})
|
||||
Sern.init({
|
||||
commands : "./dist/commands",
|
||||
events: "./dist/events",
|
||||
tasks: "./dist/tasks",
|
||||
defaultPrefix: "!"
|
||||
})
|
||||
}
|
||||
init().then(() => {
|
||||
Service('@sern/client').login()
|
||||
})
|
||||
//View docs for all options
|
||||
|
||||
156
bot/src/plugins/args.ts
Normal file
156
bot/src/plugins/args.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* @author HighArcs
|
||||
* @version 1.0.0
|
||||
* @description converts array of argument strings to an object (and maps them)
|
||||
* @license null
|
||||
* @example
|
||||
* ```ts
|
||||
* import { parsedCommandModule, args } from "../plugins/args";
|
||||
* import { CommandType } from "@sern/handler";
|
||||
*
|
||||
* interface Arg {
|
||||
* value: number;
|
||||
* }
|
||||
*
|
||||
* export default parsedCommandModule({
|
||||
* type : CommandType.Text
|
||||
* plugins: [args({ value: Number })],
|
||||
* execute: (ctx, args) => {
|
||||
* console.log(ctx.args.value);
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
|
||||
import {
|
||||
commandModule,
|
||||
CommandType,
|
||||
Context, ControlPlugin,
|
||||
Plugin, CommandControlPlugin, controller
|
||||
} from "@sern/handler";
|
||||
import type { Awaitable } from "discord.js";
|
||||
|
||||
type Converter<T> = (value?: string) => Awaitable<T>;
|
||||
type Struct = Record<string, any>;
|
||||
type ConverterList<T extends Struct> = {
|
||||
[K in keyof T]: Converter<T[K]>;
|
||||
};
|
||||
type Ctx<T> = Context & { _args: T };
|
||||
|
||||
interface Err {
|
||||
key: string;
|
||||
error: string;
|
||||
given: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
type OnError<T> = (context: Ctx<T>, error: Err) => any;
|
||||
|
||||
type SpecialEvt<T> = {
|
||||
readonly "@@plugin": symbol
|
||||
} & ControlPlugin<[Ctx<T>, ]>
|
||||
|
||||
async function convert<T extends Struct>(
|
||||
args: Array<string>,
|
||||
struct: ConverterList<T>
|
||||
) {
|
||||
const entries = Object.entries(struct);
|
||||
const result = {} as T;
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const value = args[i];
|
||||
const [key, converter] = entries[i]!;
|
||||
try {
|
||||
result[key as keyof T] = await converter(value);
|
||||
} catch (error) {
|
||||
throw { key, error: String(error), given: value, index: i };
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
interface ParsedInputCommandModule<T extends Struct> {
|
||||
name?: string;
|
||||
description: string;
|
||||
type: CommandType.Both | CommandType.Text | CommandType.Slash;
|
||||
execute: (context: Ctx<T>, args: Array<string>) => any;
|
||||
plugins: () =>
|
||||
| [SpecialEvt<T>, ...Array<Plugin>]
|
||||
| []
|
||||
| undefined;
|
||||
}
|
||||
|
||||
export const Structs = {
|
||||
string: (value: string) => String(value),
|
||||
number: (value: string) => Number(value),
|
||||
boolean: (value: string) => value === "true" || value === "1",
|
||||
date: (value: string) => new Date(value),
|
||||
integer: (value: string) => Number.parseInt(value),
|
||||
};
|
||||
|
||||
export function parsedCommandModule<T extends Struct>(
|
||||
a: ParsedInputCommandModule<T>
|
||||
) {
|
||||
const plugins = (a.plugins() ?? []);
|
||||
return commandModule({ ...a, plugins } as never);
|
||||
}
|
||||
|
||||
export namespace Checks {
|
||||
export function choices<K extends string>(
|
||||
choices: K[],
|
||||
value?: string
|
||||
): asserts value is K {
|
||||
if (!choices.includes(value as unknown as K)) {
|
||||
throw "value is not in choices";
|
||||
}
|
||||
}
|
||||
|
||||
export function required(value?: string): asserts value is string {
|
||||
if (value === undefined) {
|
||||
throw "value is required";
|
||||
}
|
||||
}
|
||||
|
||||
export function limit(min: number, max: number, value?: string) {
|
||||
required(value);
|
||||
const val = Structs.number(value);
|
||||
if (val < min) {
|
||||
throw `value must be higher than ${min}`;
|
||||
}
|
||||
|
||||
if (val > max) {
|
||||
throw `value must be lower than ${max}`;
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
export function args<T extends Struct>(
|
||||
struct: ConverterList<T>,
|
||||
onError?: OnError<T>
|
||||
): SpecialEvt<T> {
|
||||
const plugin = CommandControlPlugin<CommandType.Both>(async (ctx, args) => {
|
||||
switch(args.type) {
|
||||
case "slash": {
|
||||
let result: T;
|
||||
} break;
|
||||
case "text" : {
|
||||
let result: T;
|
||||
try {
|
||||
result = await convert(args, struct)
|
||||
} catch (e) {
|
||||
if (onError) {
|
||||
onError(ctx as Ctx<T>, e as Err);
|
||||
}
|
||||
return controller.stop();
|
||||
}
|
||||
//@warn - mutable assignment!
|
||||
(ctx as Ctx<T>)._args = result;
|
||||
return controller.next();
|
||||
}
|
||||
}
|
||||
return controller.next()
|
||||
})
|
||||
Object.defineProperty(plugin, "@@plugin", { value: Symbol("args") })
|
||||
return plugin as SpecialEvt<T>;
|
||||
}
|
||||
57
bot/src/plugins/assertFields.ts
Normal file
57
bot/src/plugins/assertFields.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* This plugin checks the fields of a ModalSubmitInteraction
|
||||
* with regex or a custom callback
|
||||
*
|
||||
* @author @jacoobes [<@182326315813306368>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* export default commandModule({
|
||||
* type: CommandType.Modal,
|
||||
* plugins: [
|
||||
* assertFields({
|
||||
* fields: {
|
||||
* // check the modal field "mcUsernameInput" with the regex /a+b+c/
|
||||
* mcUsernameInput: /a+b+c+/
|
||||
* },
|
||||
* failure: (errors, interaction) => {
|
||||
* interaction.reply(errors.join("\n"))
|
||||
* }
|
||||
* }),
|
||||
* ],
|
||||
* execute: ctx => {
|
||||
* ctx.reply("nice!")
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
import { CommandControlPlugin, CommandType, controller } from "@sern/handler";
|
||||
import type { ModalSubmitInteraction } from "discord.js";
|
||||
|
||||
type Assertion =
|
||||
| RegExp
|
||||
| ((value : string) => boolean);
|
||||
|
||||
export function assertFields(config: {
|
||||
fields: Record<string, Assertion>,
|
||||
failure: (errors: string[], interaction: ModalSubmitInteraction) => any
|
||||
}) {
|
||||
return CommandControlPlugin<CommandType.Modal>(modal => {
|
||||
const pairs = Object.entries(config.fields);
|
||||
const errors = [];
|
||||
for(const [ field, assertion ] of pairs) {
|
||||
// Keep in mind this doesn't check for typos!
|
||||
// feel free to add more checks.
|
||||
const input = modal.fields.getTextInputValue(field)
|
||||
const resolvedAssertion = assertion instanceof RegExp ? (value: string) => assertion.test(value) : assertion;
|
||||
if(!resolvedAssertion(input)) {
|
||||
errors.push(input + " failed to pass assertion " + resolvedAssertion.toString() )
|
||||
}
|
||||
}
|
||||
if(errors.length > 0) {
|
||||
config.failure(errors, modal);
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
})
|
||||
}
|
||||
39
bot/src/plugins/channelType.ts
Normal file
39
bot/src/plugins/channelType.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* This plugin checks if a channel is the specified type
|
||||
*
|
||||
* @author @Benzo-Fury [<@762918086349029386>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { channelType } from "../plugins/channelType";
|
||||
* import { ChannelType } from "discord.js"
|
||||
* import { commandModule } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [ channelType([ChannelType.GuildText], 'This cannot be used here') ],
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
import { ChannelType } from "discord.js";
|
||||
import {CommandControlPlugin, CommandType, controller } from "@sern/handler";
|
||||
export function channelType(
|
||||
channelType: ChannelType[],
|
||||
onFail?: string
|
||||
){
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx) => {
|
||||
let channel = ctx.channel?.type;
|
||||
//for some reason the dm channel type was returning undefined at some points
|
||||
if (channel === undefined) {
|
||||
channel = ChannelType.DM;
|
||||
}
|
||||
if (channelType.includes(channel)) {
|
||||
return controller.next();
|
||||
}
|
||||
if (onFail) {
|
||||
await ctx.reply(onFail);
|
||||
}
|
||||
return controller.stop();
|
||||
})
|
||||
}
|
||||
106
bot/src/plugins/confirmation.ts
Normal file
106
bot/src/plugins/confirmation.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* This is buttonConfirmation plugin, it runs confirmation prompt in the form of buttons.
|
||||
* Note that you need to use edit/editReply in the command itself because we are already replying in the plugin!
|
||||
* Credits to original plugin of confirmation using reactions and its author!
|
||||
*
|
||||
* @author @EvolutionX-10 [<@697795666373640213>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { buttonConfirmation } from "../plugins/buttonConfirmation";
|
||||
* import { commandModule } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [ buttonConfirmation() ],
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
|
||||
import {CommandControlPlugin, CommandType, controller} from "@sern/handler";
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
} from "discord.js";
|
||||
|
||||
export function confirmation(
|
||||
options?: Partial<ConfirmationOptions>
|
||||
) {
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx, args) => {
|
||||
options = {
|
||||
content: "Do you want to proceed?",
|
||||
denialMessage: "Cancelled",
|
||||
labels: ["No", "Yes"],
|
||||
time: 60_000,
|
||||
wrongUserResponse: "Not for you!",
|
||||
...options,
|
||||
};
|
||||
|
||||
const buttons = options.labels!.map((l, i) => {
|
||||
return new ButtonBuilder()
|
||||
.setCustomId(l)
|
||||
.setLabel(l)
|
||||
.setStyle( i === 0 ? ButtonStyle.Danger : ButtonStyle.Success
|
||||
);
|
||||
});
|
||||
const sent = await ctx.reply({
|
||||
content: options.content,
|
||||
components: [
|
||||
new ActionRowBuilder<ButtonBuilder>().setComponents(
|
||||
buttons
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
const collector = sent.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
filter: (i) => i.user.id === ctx.user.id,
|
||||
time: options.time,
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
collector.on("collect", async (i) => {
|
||||
await i.update({ components: [] });
|
||||
collector.stop();
|
||||
if (i.customId === options!.labels![1]) {
|
||||
resolve(controller.next());
|
||||
return;
|
||||
}
|
||||
await i.editReply({
|
||||
content: options?.denialMessage,
|
||||
});
|
||||
resolve(controller.stop());
|
||||
});
|
||||
|
||||
collector.on("end", async (c) => {
|
||||
if (c.size) return;
|
||||
buttons.forEach((b) => b.setDisabled());
|
||||
await sent.edit({
|
||||
components: [
|
||||
new ActionRowBuilder<ButtonBuilder>().setComponents(
|
||||
buttons
|
||||
),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
collector.on("ignore", async (i) => {
|
||||
await i.reply({
|
||||
content: options?.wrongUserResponse,
|
||||
ephemeral: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface ConfirmationOptions {
|
||||
content: string;
|
||||
denialMessage: string;
|
||||
time: number;
|
||||
labels: [string, string];
|
||||
wrongUserResponse: string;
|
||||
}
|
||||
6
bot/src/plugins/correctFile.ts
Normal file
6
bot/src/plugins/correctFile.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import {CommandInitPlugin, controller} from "@sern/handler";
|
||||
|
||||
|
||||
export const correctFile = CommandInitPlugin(() => {
|
||||
return controller.stop()
|
||||
})
|
||||
38
bot/src/plugins/disable.ts
Normal file
38
bot/src/plugins/disable.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* Disables a command entirely, for whatever reasons you may need.
|
||||
*
|
||||
* @author @jacoobes [<@182326315813306368>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { disable } from "../plugins/disable";
|
||||
* import { commandModule } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [ disable() ],
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
import { CommandType, EventPlugin, PluginType } from "@sern/handler";
|
||||
import { InteractionReplyOptions, ReplyMessageOptions } from "discord.js";
|
||||
|
||||
export function disable(
|
||||
onFail?:
|
||||
| string
|
||||
| Omit<InteractionReplyOptions, "fetchReply">
|
||||
| ReplyMessageOptions
|
||||
): EventPlugin<CommandType.Both> {
|
||||
return {
|
||||
type: PluginType.Event,
|
||||
description: "Disables command from responding",
|
||||
async execute([ctx], controller) {
|
||||
if (onFail !== undefined) {
|
||||
await ctx.reply(onFail);
|
||||
}
|
||||
return controller.stop();
|
||||
},
|
||||
};
|
||||
}
|
||||
29
bot/src/plugins/dmOnly.ts
Normal file
29
bot/src/plugins/dmOnly.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* This is dmOnly plugin, it allows commands to be run only in DMs.
|
||||
* For discord.js you should have the Partials.Channel and DirectMessages intent enabled.
|
||||
* @author @EvolutionX-10 [<@697795666373640213>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { dmOnly } from "../plugins/dmOnly";
|
||||
* import { commandModule } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [dmOnly()],
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
import {CommandControlPlugin, CommandType, controller } from "@sern/handler";
|
||||
export function dmOnly(
|
||||
content?: string,
|
||||
ephemeral?: boolean
|
||||
) {
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx, _) => {
|
||||
if (ctx.channel?.isDMBased()) return controller.next();
|
||||
|
||||
if (content) await ctx.reply({ content, ephemeral }); // Change this if you want or remove it for silent deny
|
||||
return controller.stop();
|
||||
})
|
||||
}
|
||||
637
bot/src/plugins/filter.ts
Normal file
637
bot/src/plugins/filter.ts
Normal file
@@ -0,0 +1,637 @@
|
||||
import {
|
||||
CommandControlPlugin,
|
||||
type CommandType,
|
||||
type Context,
|
||||
controller,
|
||||
} from "@sern/handler";
|
||||
import {
|
||||
GuildMember,
|
||||
GuildMemberRoleManager,
|
||||
PermissionResolvable,
|
||||
PermissionsBitField,
|
||||
User,
|
||||
} from "discord.js";
|
||||
|
||||
|
||||
export type Test = (context: Context) => boolean;
|
||||
|
||||
export class Criteria {
|
||||
public constructor(
|
||||
public readonly name: string,
|
||||
public readonly execute: Test,
|
||||
public readonly children: Array<Criteria>
|
||||
) {}
|
||||
toString() {
|
||||
return this.name + ' ' + this.children.map(c => c.name).join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
export const or = (...filters: Array<FilterImpl>): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
let pass = false;
|
||||
tests: for (const filter of filters) {
|
||||
if (filter.test(context)) {
|
||||
pass = true;
|
||||
break tests;
|
||||
}
|
||||
}
|
||||
|
||||
return pass;
|
||||
}
|
||||
|
||||
const children: Array<Criteria> = filters.map((x) => x.criteria);
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("or", execute, children),
|
||||
`or(${filters.map((x) => x.message).join(", ")})`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export const and = (...filters: Array<FilterImpl>): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
for (const filter of filters) {
|
||||
if (!filter.test(context)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const children: Array<Criteria> = filters.map((x) => x.criteria);
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("and", execute, children),
|
||||
`and(${filters.map((x) => x.message).join(", ")})`
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const not = (filter: FilterImpl): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
return !filter.test(context);
|
||||
}
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("not", execute, [filter.criteria]),
|
||||
`not(${filter.criteria})`
|
||||
);
|
||||
}
|
||||
export const custom =(execute: Test, message?: string): FilterImpl => {
|
||||
return new FilterImpl(new Criteria("custom", execute, []), message);
|
||||
}
|
||||
|
||||
export const withCustomMessage = (
|
||||
filter: FilterImpl,
|
||||
message?: string
|
||||
): FilterImpl => {
|
||||
return new FilterImpl(filter.criteria, message);
|
||||
}
|
||||
|
||||
export const hasGuildPermission = (
|
||||
permission: PermissionResolvable
|
||||
): FilterImpl => {
|
||||
const b = PermissionsBitField.resolve(permission);
|
||||
const field = Object.entries(PermissionsBitField.Flags).find(
|
||||
([, v]) => v === b
|
||||
);
|
||||
|
||||
if (field === undefined) {
|
||||
throw new Error(
|
||||
`unknown permission \`${permission}\` in filter \`hasGuildPermission\``
|
||||
);
|
||||
}
|
||||
|
||||
const [name] = field;
|
||||
|
||||
function execute(context: Context): boolean {
|
||||
if (context.member !== null) {
|
||||
if (typeof context.member.permissions === "string") {
|
||||
return new PermissionsBitField(BigInt(context.member.permissions)).has(b);
|
||||
}
|
||||
return context.member.permissions.has(b);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("hasGuildPermission", execute, []),
|
||||
`has guild permission: ${name}`
|
||||
);
|
||||
}
|
||||
export const hasChannelPermission = (
|
||||
permission: PermissionResolvable,
|
||||
channelId?: string
|
||||
): FilterImpl => {
|
||||
const b = PermissionsBitField.resolve(permission);
|
||||
const field = Object.entries(PermissionsBitField.Flags).find(
|
||||
([, v]) => v === b
|
||||
);
|
||||
|
||||
if (field === undefined) {
|
||||
throw new Error(
|
||||
`unknown permission \`${permission}\` in filter \`hasChannelPermission\``
|
||||
);
|
||||
}
|
||||
|
||||
const [name] = field;
|
||||
|
||||
function execute(context: Context): boolean {
|
||||
if (context.member !== null) {
|
||||
const channel =
|
||||
channelId !== undefined
|
||||
? context.guild?.channels.cache.get(channelId)
|
||||
: context.channel;
|
||||
|
||||
// ?
|
||||
if (channel == undefined || channel === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (channel.isDMBased()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const field2 = channel.permissionsFor(context.user);
|
||||
|
||||
// assume we have no permission overrides
|
||||
if (field2 === null) {
|
||||
if (context.member !== null) {
|
||||
if (typeof context.member.permissions === "string") {
|
||||
return new PermissionsBitField(
|
||||
BigInt(context.member.permissions)
|
||||
).has(b);
|
||||
}
|
||||
|
||||
return context.member.permissions.has(b);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return field2.has(b);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("hasChannelPermission", execute, []),
|
||||
channelId !== undefined
|
||||
? `has channel permission ${name} in <#${channelId}>`
|
||||
: `has channel permission ${name}`
|
||||
);
|
||||
}
|
||||
|
||||
export const canAddReactions =(channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("AddReactions", channelId);
|
||||
}
|
||||
|
||||
export const canAttachFiles =(channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("AttachFiles", channelId);
|
||||
}
|
||||
|
||||
export const canBanMembers = (): FilterImpl => {
|
||||
return hasGuildPermission("BanMembers");
|
||||
}
|
||||
|
||||
export const canChangeNickname = (): FilterImpl => {
|
||||
return hasGuildPermission("ChangeNickname");
|
||||
}
|
||||
|
||||
export const canConnect = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("Connect", channelId);
|
||||
}
|
||||
|
||||
export const canCreateInstantInvite =(channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("CreateInstantInvite", channelId);
|
||||
}
|
||||
|
||||
export const canDeafenMembers =(channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("DeafenMembers", channelId);
|
||||
}
|
||||
|
||||
export const canEmbedLinks =(channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("EmbedLinks", channelId);
|
||||
}
|
||||
|
||||
export const canKickMembers =(): FilterImpl => {
|
||||
return hasGuildPermission("KickMembers");
|
||||
}
|
||||
|
||||
export const canManageChannelWebhooks =(channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("ManageWebhooks", channelId);
|
||||
}
|
||||
|
||||
export const canManageChannels =(channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("ManageChannels", channelId);
|
||||
}
|
||||
|
||||
export const canManageEmojisAndStickers =(): FilterImpl => {
|
||||
return hasGuildPermission("ManageEmojisAndStickers");
|
||||
}
|
||||
|
||||
export const canManageGuild =(): FilterImpl => {
|
||||
return hasGuildPermission("ManageGuild");
|
||||
}
|
||||
|
||||
export const canManageGuildWebhooks =(): FilterImpl => {
|
||||
return hasGuildPermission("ManageWebhooks");
|
||||
}
|
||||
|
||||
export const canManageMessages =(channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("ManageMessages", channelId);
|
||||
}
|
||||
|
||||
export const canManageNicknames = (): FilterImpl => {
|
||||
return hasGuildPermission("ManageNicknames");
|
||||
}
|
||||
|
||||
export const canManageRoles = (): FilterImpl => {
|
||||
return hasGuildPermission("ManageRoles");
|
||||
}
|
||||
|
||||
export const canMentionEveryone = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("MentionEveryone", channelId);
|
||||
}
|
||||
|
||||
export const canMoveMembers = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("MoveMembers", channelId);
|
||||
}
|
||||
|
||||
export const canMuteMembers = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("MuteMembers", channelId);
|
||||
}
|
||||
|
||||
export const canPrioritySpeaker = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("PrioritySpeaker", channelId);
|
||||
}
|
||||
|
||||
export const canReadMessageHistory = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("ReadMessageHistory", channelId);
|
||||
}
|
||||
|
||||
export const canViewChannel = (channelId: string): FilterImpl => {
|
||||
return hasChannelPermission("ViewChannel", channelId);
|
||||
}
|
||||
|
||||
export const canSendMessages = (channelId: string): FilterImpl => {
|
||||
return hasChannelPermission("SendMessages", channelId);
|
||||
}
|
||||
|
||||
export const canSendTtsMessages = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("SendTTSMessages", channelId);
|
||||
}
|
||||
|
||||
export const canSpeak = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("Speak", channelId);
|
||||
}
|
||||
|
||||
export const canStream = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("Stream", channelId);
|
||||
}
|
||||
|
||||
export const canUseExternalEmojis = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("UseExternalEmojis", channelId);
|
||||
}
|
||||
|
||||
export const canUseVoiceActivity = (channelId?: string): FilterImpl => {
|
||||
return hasChannelPermission("UseVAD", channelId);
|
||||
}
|
||||
|
||||
export const canViewAuditLog = (): FilterImpl => {
|
||||
return hasGuildPermission("ViewAuditLog");
|
||||
}
|
||||
|
||||
export const canViewGuildInsights = (): FilterImpl => {
|
||||
return hasGuildPermission("ViewGuildInsights");
|
||||
}
|
||||
|
||||
export const channelIdIn = (channelIds: Array<string>): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
return channelIds.includes(
|
||||
context.isMessage()
|
||||
? context.message.channelId
|
||||
: context.interaction.channelId
|
||||
);
|
||||
}
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("channelIdIn", execute, []),
|
||||
`channel is one of: ${channelIds.map((v) => `<#${v}>`).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
export const hasEveryRole = (roles: Array<string>): FilterImpl => {
|
||||
return withCustomMessage(
|
||||
and(...roles.map((v) => hasRole(v))),
|
||||
`has all of: ${roles.map((v) => `<@&${v}>`).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
export const hasMentionableRole = (): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
if (context.member !== null) {
|
||||
if (context.member.roles instanceof GuildMemberRoleManager) {
|
||||
return (
|
||||
context.member.roles.cache.filter((x) => x.mentionable === true)
|
||||
.size > 0
|
||||
);
|
||||
}
|
||||
|
||||
if (context.guild === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return context.member.roles
|
||||
.map((roleId) => context.guild!.roles.cache.get(roleId))
|
||||
.filter((x) => x !== undefined)
|
||||
.some((x) => x!.mentionable);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return new FilterImpl(
|
||||
new Criteria("hasMentionableRole", execute, []),
|
||||
"has a mentionable role"
|
||||
);
|
||||
}
|
||||
|
||||
export const hasNickname = (nickname?: string): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
if (context.member !== null) {
|
||||
if (context.member instanceof GuildMember) {
|
||||
if (nickname !== null) {
|
||||
return context.member.nickname === nickname;
|
||||
}
|
||||
|
||||
return context.member.nickname !== null;
|
||||
}
|
||||
|
||||
if (nickname !== null) {
|
||||
return context.member.nick === nickname;
|
||||
}
|
||||
|
||||
return (
|
||||
context.member.nick !== null && context.member.nick !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
// dm members can technically have nicknames but they're per-user, so this should never be true.
|
||||
return false;
|
||||
}
|
||||
return new FilterImpl(new Criteria("hasNickname", execute, []), "has a nickname");
|
||||
}
|
||||
|
||||
export const hasParentId = (parentId: string): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
if (context.channel !== null) {
|
||||
if (context.channel.isDMBased()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return context.channel.parentId === parentId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("hasParentId", execute, []),
|
||||
`has channel parent <#${parentId}>`
|
||||
);
|
||||
}
|
||||
|
||||
export const hasRole = (roleId: string): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
if (context.member !== null) {
|
||||
if (context.member.roles instanceof GuildMemberRoleManager) {
|
||||
return context.member.roles.cache.has(roleId);
|
||||
}
|
||||
|
||||
if (context.guild === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return context.member.roles.includes(roleId);
|
||||
}
|
||||
|
||||
// assume dm members have every role ever.
|
||||
return true;
|
||||
}
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("hasRole", execute, []),
|
||||
`has role <@&${roleId}>`
|
||||
);
|
||||
}
|
||||
|
||||
export const hasSomeRole = (roles: Array<string>): FilterImpl => {
|
||||
return withCustomMessage(
|
||||
or(...roles.map((role) => hasRole(role))),
|
||||
`has any of: ${roles.map((v) => `<@&${v}>`).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
export const isAdministator = (): FilterImpl => {
|
||||
return hasGuildPermission("Administrator");
|
||||
}
|
||||
|
||||
export const isChannelId = (channelId: string): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
if (context.isMessage()) {
|
||||
return context.message.channelId === channelId;
|
||||
}
|
||||
|
||||
return context.interaction.channelId === channelId;
|
||||
}
|
||||
|
||||
return new FilterImpl(
|
||||
new Criteria("isChannelId", execute, []),
|
||||
`is channel <#${channelId}>`
|
||||
);
|
||||
}
|
||||
|
||||
export const isChannelNsfw = (): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
if (context.channel !== null) {
|
||||
if (context.channel.isDMBased() || context.channel.isThread()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return context.channel.nsfw;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return new FilterImpl(
|
||||
new Criteria("isChannelNsfw", execute, []),
|
||||
"channel marked as nsfw"
|
||||
);
|
||||
}
|
||||
|
||||
export const isGuildOwner = (): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
if (context.guild !== null) {
|
||||
return context.guild.ownerId === context.user.id;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return new FilterImpl(
|
||||
new Criteria("isGuildOwner", execute, []),
|
||||
"is guild owner"
|
||||
);
|
||||
}
|
||||
|
||||
export const isBotOwner = (): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
if (context.client.application !== null) {
|
||||
if (context.client.application.owner !== null) {
|
||||
if (context.client.application.owner instanceof User) {
|
||||
return context.user.id === context.client.application.owner.id;
|
||||
}
|
||||
|
||||
return context.client.application.owner.members.has(context.user.id);
|
||||
}
|
||||
}
|
||||
|
||||
// nope
|
||||
return false;
|
||||
}
|
||||
return new FilterImpl(new Criteria("isBotOwner", execute, []), "is bot owner");
|
||||
}
|
||||
|
||||
export const isUserId = (userId: string): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
return context.user.id === userId;
|
||||
}
|
||||
return new FilterImpl(
|
||||
new Criteria("isUserId", execute, []),
|
||||
`is user: <@${userId}>`
|
||||
);
|
||||
}
|
||||
|
||||
export const parentIdIn = (parentIds: Array<string>): FilterImpl => {
|
||||
return withCustomMessage(
|
||||
or(...parentIds.map((v) => hasParentId(v))),
|
||||
`channel parent is one of: ${parentIds.map((v) => `<#${v}>`).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
export const userIdIn = (userIds: Array<string>): FilterImpl => {
|
||||
return withCustomMessage(
|
||||
or(...userIds.map((v) => isUserId(v))),
|
||||
`user is one of: ${userIds.map((v) => `<@${v}>`).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
export const isInGuild = (): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
return context.guildId !== null;
|
||||
}
|
||||
|
||||
return new FilterImpl(new Criteria("isInGuild", execute, []), "is in guild");
|
||||
}
|
||||
|
||||
export const isInDm = (): FilterImpl => {
|
||||
const notInGuild = compose(not, isInGuild);
|
||||
return withCustomMessage(notInGuild(), "is in dm");
|
||||
}
|
||||
|
||||
export const never = (): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
void context;
|
||||
return false;
|
||||
}
|
||||
return new FilterImpl(new Criteria("never", execute, []), "never");
|
||||
}
|
||||
|
||||
export const always = (): FilterImpl => {
|
||||
function execute(context: Context): boolean {
|
||||
void context;
|
||||
return true;
|
||||
}
|
||||
return new FilterImpl(new Criteria("always", execute, []), "always");
|
||||
}
|
||||
type CtxMap<T> = (arg: T) => FilterImpl;
|
||||
|
||||
/**
|
||||
* Call FilterImpls in right to left order.
|
||||
* @example
|
||||
* import { compose, isUserId, not } from '../plugins/filter'
|
||||
* const isNotUserId = compose(not, isUserId)
|
||||
*
|
||||
*/
|
||||
export const compose = <T = void>(...funcs: CtxMap<any>[]): CtxMap<T> => {
|
||||
return (arg: T): FilterImpl =>
|
||||
//@ts-ignore
|
||||
funcs.reduceRight((result, func) => func(result), arg);
|
||||
}
|
||||
|
||||
|
||||
export class FilterImpl {
|
||||
public readonly test: Test;
|
||||
|
||||
public constructor(
|
||||
public readonly criteria: Criteria,
|
||||
public message?: string
|
||||
) {
|
||||
this.test = this.criteria.execute;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type FilterOptions = {
|
||||
condition: Array<FilterImpl> | FilterImpl,
|
||||
onFailed?: (context: Context, filters: Array<FilterImpl>) => unknown
|
||||
};
|
||||
/**
|
||||
* Generalized `filter` plugin. revised by jacoobes, all credit to original author.
|
||||
* Perform declarative conditionals as plugins.
|
||||
* @author @trueharuu [<@504698587221852172>]
|
||||
* @version 2.0.0
|
||||
* @example
|
||||
* import { filter, not, isGuildOwner, canMentionEveryone } from '../plugins/filter';
|
||||
* import { commandModule } from '@sern/handler';
|
||||
*
|
||||
* export default commandModule({
|
||||
* plugins: filter({ condition: [not(isGuildOwner()), canMentionEveryone()] }),
|
||||
* async execute(context) {
|
||||
* // your code here
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
|
||||
export const filter =
|
||||
(options: FilterOptions) => {
|
||||
return CommandControlPlugin<CommandType.Both>(async (context) => {
|
||||
const arrayifiedCondition = Array.isArray(options.condition) ? options.condition : [options.condition]
|
||||
const value = and(...arrayifiedCondition).test(context);
|
||||
|
||||
if (value) {
|
||||
return controller.next();
|
||||
}
|
||||
|
||||
if (options.onFailed !== undefined) {
|
||||
await options.onFailed(context, arrayifiedCondition);
|
||||
} else {
|
||||
await context.reply({
|
||||
ephemeral: true,
|
||||
content: `you do not match the criteria for this command:\n${arrayifiedCondition
|
||||
.map((x) => x.message)
|
||||
.filter((x) => x !== undefined)
|
||||
.join("\n")}`,
|
||||
allowedMentions: {
|
||||
repliedUser: false,
|
||||
parse: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return controller.stop();
|
||||
});
|
||||
};
|
||||
40
bot/src/plugins/filterA.ts
Normal file
40
bot/src/plugins/filterA.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { PluginType, makePlugin, controller, ControlPlugin } from "@sern/handler";
|
||||
import type { AutocompleteInteraction } from 'discord.js'
|
||||
|
||||
/**
|
||||
* @plugin
|
||||
* filters autocomplete interaction that pass the criteria
|
||||
* @author @jacoobes [<@182326315813306368>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { CommandType, commandModule } from "@sern/handler";
|
||||
* import { filterA } from '../plugins/filterA.js'
|
||||
* export default commandModule({
|
||||
* type : CommandType.Slash,
|
||||
* options: [
|
||||
* {
|
||||
* autocomplete: true,
|
||||
* command : {
|
||||
* //only accept autocomplete interactions that include 'poo' in the text
|
||||
* onEvent: [filterA(s => s.includes('poo'))],
|
||||
* execute: (autocomplete) => {
|
||||
* let data = [{ name: 'pooba', value: 'first' }, { name: 'pooga', value: 'second' }]
|
||||
* autocomplete.respond(data)
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ],
|
||||
* execute: (ctx, args) => {}
|
||||
* })
|
||||
* @end
|
||||
*/
|
||||
|
||||
export const filterA = (pred: (value: string) => boolean) => {
|
||||
return makePlugin(PluginType.Control, (a: AutocompleteInteraction) => {
|
||||
if(pred(a.options.getFocused())) {
|
||||
return controller.next();
|
||||
}
|
||||
return controller.stop();
|
||||
}) as ControlPlugin;
|
||||
}
|
||||
36
bot/src/plugins/fromCallback.ts
Normal file
36
bot/src/plugins/fromCallback.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
//@ts-nocheck
|
||||
/**
|
||||
* @plugin
|
||||
* fromCallback turns a callback into a plugin result.
|
||||
* if the callback returns truthy value, plugin continues.
|
||||
* This control plugin works for every command type. The arguments of the callback
|
||||
* mirror the execute method on the current module.
|
||||
* @author @jacoobes [<@182326315813306368>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* const myServer = "941002690211766332";
|
||||
* export default commandModule({
|
||||
* type: CommandType.Both,
|
||||
* plugins: [
|
||||
* fromCallback((ctx, args) => ctx.guildId == myServer)
|
||||
* ],
|
||||
* execute: ctx => {
|
||||
* ctx.reply("I only respond in myServer!");
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @end
|
||||
*/
|
||||
|
||||
|
||||
import { PluginType, makePlugin, controller } from "@sern/handler";
|
||||
|
||||
export const fromCallback = (cb: (...args: any[]) => boolean) =>
|
||||
makePlugin(PluginType.Control, (...args) => {
|
||||
console.log(args)
|
||||
if(cb.apply(null, args)) {
|
||||
return controller.next();
|
||||
}
|
||||
return controller.stop();
|
||||
});
|
||||
5
bot/src/plugins/json-params.ts
Normal file
5
bot/src/plugins/json-params.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { CommandControlPlugin, CommandType, controller } from "@sern/handler";
|
||||
|
||||
export const json = CommandControlPlugin<CommandType.Button>((ctx, args) => {
|
||||
return controller.next({ 'json/data': JSON.parse(args.params!) });
|
||||
})
|
||||
48
bot/src/plugins/nsfwOnly.ts
Normal file
48
bot/src/plugins/nsfwOnly.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* This plugin checks if the channel is nsfw and responds to user with a specified response if not nsfw
|
||||
*
|
||||
* @author @Benzo-Fury [<@762918086349029386>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { nsfwOnly } from "../plugins/nsfwOnly";
|
||||
* import { commandModule } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [ nsfwOnly('response', true) ],
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
import {
|
||||
ChannelType,
|
||||
GuildTextBasedChannel,
|
||||
TextBasedChannel,
|
||||
TextChannel,
|
||||
} from "discord.js";
|
||||
import {CommandControlPlugin, CommandType, controller } from "@sern/handler";
|
||||
function isGuildText(channel: TextBasedChannel|null): channel is GuildTextBasedChannel {
|
||||
return (channel?.type == ChannelType.GuildPublicThread ||
|
||||
channel?.type == ChannelType.GuildPrivateThread);
|
||||
}
|
||||
export function nsfwOnly(onFail: string, ephemeral: boolean) {
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx, _) => {
|
||||
if (ctx.guild === null) {
|
||||
await ctx.reply({ content: onFail, ephemeral });
|
||||
return controller.stop();
|
||||
}
|
||||
//channel is thread (not supported by nsfw)
|
||||
if (isGuildText(ctx.channel) == true) {
|
||||
await ctx.reply({ content: onFail, ephemeral });
|
||||
return controller.stop();
|
||||
}
|
||||
if (!(ctx.channel! as TextChannel).nsfw) {
|
||||
//channel is not nsfw
|
||||
await ctx.reply({ content: onFail, ephemeral });
|
||||
return controller.stop();
|
||||
}
|
||||
//continues to command if nsfw
|
||||
return controller.next();
|
||||
});
|
||||
}
|
||||
30
bot/src/plugins/ownerOnly.ts
Normal file
30
bot/src/plugins/ownerOnly.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* This is OwnerOnly plugin, it allows only bot owners to run the command, like eval.
|
||||
*
|
||||
* @author @EvolutionX-10 [<@697795666373640213>]
|
||||
* @version 1.2.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { ownerOnly } from "../plugins/ownerOnly";
|
||||
* import { commandModule } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [ ownerOnly() ], // can also pass array of IDs to override default owner IDs
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { CommandType, CommandControlPlugin, controller } from "@sern/handler";
|
||||
const ownerIDs = ["182326315813306368"]; //! Fill your ID
|
||||
export function ownerOnly(override?: string[]) {
|
||||
return CommandControlPlugin<CommandType.Both>((ctx) => {
|
||||
if ((override ?? ownerIDs).includes(ctx.user.id))
|
||||
return controller.next();
|
||||
//* If you want to reply when the command fails due to user not being owner, you can use following
|
||||
// await ctx.reply("Only owner can run it!!!");
|
||||
return controller.stop(); //! Important: It stops the execution of command!
|
||||
});
|
||||
}
|
||||
39
bot/src/plugins/permCheck.ts
Normal file
39
bot/src/plugins/permCheck.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* @plugin
|
||||
* This is perm check, it allows users to parse the permission you want and let the plugin do the rest. (check user for that perm).
|
||||
*
|
||||
* @author @Benzo-Fury [<@762918086349029386>]
|
||||
* @version 1.0.1
|
||||
* @example
|
||||
* ```ts
|
||||
* import { permCheck } from "../plugins/permCheck";
|
||||
* import { commandModule } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [ permCheck('permission', 'No permission response') ],
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @end
|
||||
*/
|
||||
|
||||
import type { GuildMember, PermissionResolvable } from "discord.js";
|
||||
import { CommandControlPlugin, CommandType, controller } from "@sern/handler";
|
||||
export function permCheck(perm: PermissionResolvable, response: string) {
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx, args) => {
|
||||
if (ctx.guild === null) {
|
||||
await ctx.reply("This command cannot be used here");
|
||||
console.warn(
|
||||
"PermCheck > A command stopped because we couldn't check a users permissions (was used in dms)",
|
||||
); //delete this line if you dont want to be notified when a command is used outside of a guild/server
|
||||
return controller.stop();
|
||||
}
|
||||
if (!(ctx.member! as GuildMember).permissions.has(perm)) {
|
||||
await ctx.reply(response);
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
});
|
||||
}
|
||||
215
bot/src/plugins/publish.ts
Normal file
215
bot/src/plugins/publish.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* @plugin
|
||||
* [DEPRECATED] It allows you to publish your application commands using the discord.js library with ease.
|
||||
*
|
||||
* @author @EvolutionX-10 [<@697795666373640213>]
|
||||
* @version 2.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { publish } from "../plugins/publish";
|
||||
* import { commandModule } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [ publish() ], // put an object containing permissions, ids for guild commands, boolean for dmPermission
|
||||
* // plugins: [ publish({ guildIds: ['guildId'], defaultMemberPermissions: 'Administrator'})]
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
* @end
|
||||
*/
|
||||
import {
|
||||
CommandInitPlugin,
|
||||
CommandType,
|
||||
controller,
|
||||
SernOptionsData,
|
||||
SlashCommand,
|
||||
Service,
|
||||
} from "@sern/handler";
|
||||
import {
|
||||
ApplicationCommandData,
|
||||
ApplicationCommandType,
|
||||
ApplicationCommandOptionType,
|
||||
PermissionResolvable,
|
||||
} from "discord.js";
|
||||
|
||||
export const CommandTypeRaw = {
|
||||
[CommandType.Both]: ApplicationCommandType.ChatInput,
|
||||
[CommandType.CtxUser]: ApplicationCommandType.User,
|
||||
[CommandType.CtxMsg]: ApplicationCommandType.Message,
|
||||
[CommandType.Slash]: ApplicationCommandType.ChatInput,
|
||||
} as const;
|
||||
|
||||
export function publish<
|
||||
T extends
|
||||
| CommandType.Both
|
||||
| CommandType.Slash
|
||||
| CommandType.CtxMsg
|
||||
| CommandType.CtxUser,
|
||||
>(options?: PublishOptions) {
|
||||
return CommandInitPlugin<T>(async ({ module }) => {
|
||||
// Users need to provide their own useContainer function.
|
||||
let client;
|
||||
try {
|
||||
client = (await import("@sern/handler")).Service("@sern/client");
|
||||
} catch {
|
||||
const { useContainer } = await import("../index.js");
|
||||
client = useContainer("@sern/client")[0];
|
||||
}
|
||||
const defaultOptions = {
|
||||
guildIds: [],
|
||||
dmPermission: undefined,
|
||||
defaultMemberPermissions: null,
|
||||
};
|
||||
|
||||
options = { ...defaultOptions, ...options } as PublishOptions &
|
||||
ValidPublishOptions;
|
||||
let { defaultMemberPermissions, dmPermission, guildIds } =
|
||||
options as unknown as ValidPublishOptions;
|
||||
|
||||
function c(e: unknown) {
|
||||
console.error("publish command didnt work for", module.name);
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
const log =
|
||||
(...message: any[]) =>
|
||||
() =>
|
||||
console.log(...message);
|
||||
const logged = (...message: any[]) => log(message);
|
||||
/**
|
||||
* a local function that returns either one value or the other,
|
||||
* depending on {t}'s CommandType. If the commandtype of
|
||||
* this module is CommandType.Both or CommandType.Text or CommandType.Slash,
|
||||
* return 'is', else return 'els'
|
||||
* @param t
|
||||
* @returns S | T
|
||||
*/
|
||||
const appCmd = <V extends CommandType, S, T>(t: V) => {
|
||||
return (is: S, els: T) => ((t & CommandType.Both) !== 0 ? is : els);
|
||||
};
|
||||
const curAppType = CommandTypeRaw[module.type];
|
||||
const createCommandData = () => {
|
||||
const cmd = appCmd(module.type);
|
||||
return {
|
||||
name: module.name,
|
||||
type: curAppType,
|
||||
description: cmd(module.description, ""),
|
||||
options: cmd(
|
||||
optionsTransformer((module as SlashCommand).options ?? []),
|
||||
[],
|
||||
),
|
||||
defaultMemberPermissions,
|
||||
dmPermission,
|
||||
} as ApplicationCommandData;
|
||||
};
|
||||
|
||||
try {
|
||||
const commandData = createCommandData();
|
||||
|
||||
if (!guildIds.length) {
|
||||
const cmd = (await client.application!.commands.fetch()).find(
|
||||
(c) => c.name === module.name && c.type === curAppType,
|
||||
);
|
||||
if (cmd) {
|
||||
if (!cmd.equals(commandData, true)) {
|
||||
logged(
|
||||
`Found differences in global command ${module.name}`,
|
||||
);
|
||||
cmd.edit(commandData).then(
|
||||
log(
|
||||
`${module.name} updated with new data successfully!`,
|
||||
),
|
||||
);
|
||||
}
|
||||
return controller.next();
|
||||
}
|
||||
client
|
||||
.application!.commands.create(commandData)
|
||||
.then(log("Command created", module.name))
|
||||
.catch(c);
|
||||
return controller.next();
|
||||
}
|
||||
|
||||
for (const id of guildIds) {
|
||||
const guild = await client.guilds.fetch(id).catch(c);
|
||||
if (!guild) continue;
|
||||
const guildCmd = (await guild.commands.fetch()).find(
|
||||
(c) => c.name === module.name && c.type === curAppType,
|
||||
);
|
||||
if (guildCmd) {
|
||||
if (!guildCmd.equals(commandData, true)) {
|
||||
logged(`Found differences in command ${module.name}`);
|
||||
guildCmd
|
||||
.edit(commandData)
|
||||
.then(
|
||||
log(
|
||||
`${module.name} updated with new data successfully!`,
|
||||
),
|
||||
)
|
||||
.catch(c);
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
guild.commands
|
||||
.create(commandData)
|
||||
.then(log("Guild Command created", module.name, guild.name))
|
||||
.catch(c);
|
||||
}
|
||||
return controller.next();
|
||||
} catch (e) {
|
||||
logged("Command did not register" + module.name);
|
||||
logged(e);
|
||||
return controller.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function optionsTransformer(ops: Array<SernOptionsData>) {
|
||||
return ops.map((el) => {
|
||||
switch (el.type) {
|
||||
case ApplicationCommandOptionType.String:
|
||||
case ApplicationCommandOptionType.Number:
|
||||
case ApplicationCommandOptionType.Integer: {
|
||||
return el.autocomplete && "command" in el
|
||||
? (({ command, ...el }) => el)(el)
|
||||
: el;
|
||||
}
|
||||
default:
|
||||
return el;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export type NonEmptyArray<T extends `${number}` = `${number}`> = [T, ...T[]];
|
||||
|
||||
export interface ValidPublishOptions {
|
||||
guildIds: string[];
|
||||
dmPermission: boolean;
|
||||
defaultMemberPermissions: PermissionResolvable;
|
||||
}
|
||||
|
||||
interface GuildPublishOptions {
|
||||
guildIds?: NonEmptyArray;
|
||||
defaultMemberPermissions?: PermissionResolvable;
|
||||
dmPermission?: never;
|
||||
}
|
||||
|
||||
interface GlobalPublishOptions {
|
||||
defaultMemberPermissions?: PermissionResolvable;
|
||||
dmPermission?: false;
|
||||
guildIds?: never;
|
||||
}
|
||||
|
||||
type BasePublishOptions = GuildPublishOptions | GlobalPublishOptions;
|
||||
|
||||
export type PublishOptions = BasePublishOptions &
|
||||
(
|
||||
| Required<Pick<BasePublishOptions, "defaultMemberPermissions">>
|
||||
| (
|
||||
| Required<Pick<BasePublishOptions, "dmPermission">>
|
||||
| Required<Pick<BasePublishOptions, "guildIds">>
|
||||
)
|
||||
);
|
||||
95
bot/src/plugins/requirePermission.ts
Normal file
95
bot/src/plugins/requirePermission.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* This is perm check, it allows users to parse the permission you want and let the plugin do the rest. (check bot or user for that perm).
|
||||
*
|
||||
* @author @Benzo-Fury [<@762918086349029386>]
|
||||
* @author @needhamgary [<@342314924804014081>]
|
||||
* @version 1.2.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { requirePermission } from "../plugins/myPermCheck";
|
||||
* import { commandModule, CommandType } from "@sern/handler";
|
||||
* export default commandModule({
|
||||
* plugins: [ requirePermission<CommandType>('target', 'permission', 'No response (optional)') ],
|
||||
* execute: (ctx) => {
|
||||
* //your code here
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
|
||||
import type { GuildMember, PermissionResolvable } from "discord.js";
|
||||
import {
|
||||
CommandType, CommandControlPlugin, controller,
|
||||
} from "@sern/handler";
|
||||
|
||||
function payload(resp?: string) {
|
||||
return {
|
||||
fetchReply: true,
|
||||
content: resp,
|
||||
allowedMentions: { repliedUser: false },
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function requirePermission(
|
||||
target: "user" | "bot" | "both",
|
||||
perm: PermissionResolvable[],
|
||||
response?: string
|
||||
) {
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx, _) => {
|
||||
if (ctx.guild === null) {
|
||||
ctx.reply(payload("This command cannot be used here"));
|
||||
console.warn(
|
||||
"PermCheck > A command stopped because we couldn't check a users permissions (was used in dms)"
|
||||
); //delete this line if you dont want to be notified when a command is used outside of a guild/server
|
||||
return controller.stop();
|
||||
}
|
||||
const bot = (await ctx.guild.members.fetchMe({
|
||||
cache: false,
|
||||
})!) as GuildMember; const memm = ctx.member! as GuildMember;
|
||||
switch (target) {
|
||||
//*********************************************************************************************************************//
|
||||
case "bot":
|
||||
if (!bot.permissions.has(perm)) {
|
||||
if (!response)
|
||||
response = `I cannot use this command, please give me \`${perm.join(
|
||||
", "
|
||||
)}\` permission(s).`;
|
||||
await ctx.reply(payload(response));
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
//*********************************************************************************************************************//
|
||||
case "user":
|
||||
if (!memm.permissions.has(perm)) {
|
||||
if (!response)
|
||||
response = `You cannot use this command because you are missing \`${perm.join(
|
||||
", "
|
||||
)}\` permission(s).`;
|
||||
await ctx.reply(payload(response));
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
//*********************************************************************************************************************//
|
||||
case "both":
|
||||
if (
|
||||
!bot.permissions.has(perm) ||
|
||||
!memm.permissions.has(perm)
|
||||
) {
|
||||
if (!response)
|
||||
response = `Please ensure <@${bot.user.id}> and <@${
|
||||
memm.user.id
|
||||
}> both have \`${perm.join(", ")}\` permission(s).`;
|
||||
await ctx.reply(payload(response));
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
//*********************************************************************************************************************//
|
||||
default:
|
||||
console.warn(
|
||||
"Perm Check >>> You didn't specify user or bot."
|
||||
);
|
||||
ctx.reply(payload("User or Bot was not specified."));
|
||||
return controller.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
38
bot/src/plugins/serverOnly.ts
Normal file
38
bot/src/plugins/serverOnly.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Checks if a command is available in a specific server.
|
||||
*
|
||||
* @author @Peter-MJ-Parker [<@1017182455926624316>]
|
||||
* @version 1.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* import { commandModule, CommandType } from "@sern/handler";
|
||||
* import { serverOnly } from "../plugins/serverOnly";
|
||||
* export default commandModule({
|
||||
* type: CommandType.Both,
|
||||
* plugins: [serverOnly(["guildId"], failMessage)], // fail message is the message you will see when the command is ran in the wrong server.
|
||||
* description: "command description",
|
||||
* execute: async (ctx, args) => {
|
||||
* // your code here
|
||||
* },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { CommandType, controller, CommandControlPlugin } from "@sern/handler";
|
||||
|
||||
export function serverOnly(
|
||||
guildId: string[],
|
||||
failMessage = "This command is not available in this guild. \nFor permission to use in your server, please contact my developer."
|
||||
) {
|
||||
return CommandControlPlugin<CommandType.Both>(async (ctx, _) => {
|
||||
if (!guildId.includes(ctx.guildId!)) {
|
||||
ctx.reply(failMessage).then(async (m) => {
|
||||
setTimeout(async () => {
|
||||
await m.delete();
|
||||
}, 3000);
|
||||
});
|
||||
return controller.stop();
|
||||
}
|
||||
return controller.next();
|
||||
})
|
||||
}
|
||||
34
bot/src/presence.ts
Normal file
34
bot/src/presence.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Presence } from '@sern/handler'
|
||||
import { ActivityType, ClientPresenceStatus } from 'discord.js';
|
||||
|
||||
function shuffleArray<T>(array: T[]) {
|
||||
for (let i = array.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[array[i], array[j]] = [array[j], array[i]];
|
||||
}
|
||||
return [...array];
|
||||
}
|
||||
|
||||
const statuses = [[ActivityType.Watching, "the sern community", "online"],
|
||||
[ActivityType.Listening, "Evo", "dnd"],
|
||||
[ActivityType.Playing, "with @sern/cli", "idle"],
|
||||
[ActivityType.Watching, "sern bots", "dnd"],
|
||||
[ActivityType.Watching, "github stars go brrr", "online"],
|
||||
[ActivityType.Listening, "Spotify", "dnd"],
|
||||
[ActivityType.Listening, "what's bofa", "idle"]] satisfies
|
||||
[ActivityType, string, ClientPresenceStatus][];
|
||||
|
||||
export default Presence.module({
|
||||
execute: () => {
|
||||
const [type, name, status] = statuses.at(0)!;
|
||||
return Presence
|
||||
.of({ activities: [ { type, name } ], status }) //start your presence with this.
|
||||
.repeated(() => {
|
||||
const [type, name, status] = [...shuffleArray(statuses)].shift()!;
|
||||
return {
|
||||
status,
|
||||
activities: [{ type, name }]
|
||||
};
|
||||
}, 60_000); //repeat and setPresence with returned result every minute
|
||||
}
|
||||
})
|
||||
9
bot/src/tasks/dbbackup.ts
Normal file
9
bot/src/tasks/dbbackup.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { scheduledTask } from "@sern/handler";
|
||||
|
||||
|
||||
export default scheduledTask({
|
||||
trigger: "* * * * *",
|
||||
execute: (args, { deps }) => {
|
||||
console.log("hello")
|
||||
}
|
||||
})
|
||||
3
bot/tsconfig.json
Normal file
3
bot/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.sern/tsconfig.json"
|
||||
}
|
||||
11154
package-lock.json
generated
11154
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
141
package.json
141
package.json
@@ -1,50 +1,97 @@
|
||||
{
|
||||
"name": "@sern/handler",
|
||||
"version": "1.1.0-beta",
|
||||
"description": "A customizable, batteries-included, powerful discord.js framework to automate and streamline bot development.",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"compile": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"format": "eslint src/**/*.ts --fix",
|
||||
"release": "standard-version && git push --follow-tags",
|
||||
"commit": "cz"
|
||||
},
|
||||
"keywords": [
|
||||
"sern-handler",
|
||||
"sern",
|
||||
"handler",
|
||||
"sern handler",
|
||||
"wrapper",
|
||||
"discord.js",
|
||||
"framework"
|
||||
],
|
||||
"author": "SernDevs",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"discord.js": "^14.0.0-dev.1647259751.2297c2b",
|
||||
"rxjs": "^7.5.5",
|
||||
"ts-pattern": "^4.0.2",
|
||||
"ts-results": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.8.0",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"cz-conventional-changelog": "3.0.1",
|
||||
"prettier": "2.6.2",
|
||||
"standard-version": "^9.3.2",
|
||||
"typescript": "4.5.5"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sern-handler/handler.git"
|
||||
},
|
||||
"homepage": "https://sern-handler.js.org",
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "cz-conventional-changelog"
|
||||
"name": "@sern/handler",
|
||||
"packageManager": "yarn@3.5.0",
|
||||
"version": "4.2.4",
|
||||
"description": "A complete, customizable, typesafe, & reactive framework for discord bots.",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "tsc --watch",
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"format": "eslint src/**/*.ts --fix",
|
||||
"build:dev": "tsc",
|
||||
"build:prod": "tsc",
|
||||
"prepare": "tsc",
|
||||
"pretty": "prettier --write .",
|
||||
"tdd": "vitest",
|
||||
"benchmark": "vitest bench",
|
||||
"test": "vitest --run",
|
||||
"analyze-imports": "npx depcruise src --include-only \"^src\" --output-type dot | dot -T svg > dependency-graph.svg"
|
||||
},
|
||||
"keywords": [
|
||||
"sern-handler",
|
||||
"sern",
|
||||
"handler",
|
||||
"sern handler",
|
||||
"wrapper",
|
||||
"discord.js",
|
||||
"framework"
|
||||
],
|
||||
"author": "SernDevs",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sern/ioc": "^1.1.2",
|
||||
"callsites": "^3.1.0",
|
||||
"cron": "^3.1.7",
|
||||
"deepmerge": "^4.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^8.0.1",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "5.58.0",
|
||||
"@typescript-eslint/parser": "5.59.1",
|
||||
"discord.js": "^14.14.1",
|
||||
"eslint": "8.39.0",
|
||||
"typescript": "5.0.2",
|
||||
"vitest": "^1.6.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/sern-handler/handler.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.0.x"
|
||||
},
|
||||
"homepage": "https://sern.dev",
|
||||
"overrides": {
|
||||
"ws": "8.17.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"ws": "8.17.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
renovate.json
Normal file
19
renovate.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base",
|
||||
"helpers:pinGitHubActionDigests",
|
||||
"group:allNonMajor"
|
||||
],
|
||||
"major": {
|
||||
"dependencyDashboardApproval": true,
|
||||
"reviewers": ["EvolutionX-10", "jacoobes", "Murtatrxx"]
|
||||
},
|
||||
"minor": {
|
||||
"reviewers": ["jacoobes", "Murtatrxx"]
|
||||
},
|
||||
"schedule": ["every weekend"],
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
"automerge": true
|
||||
}
|
||||
}
|
||||
111
src/cleanup.ts
Normal file
111
src/cleanup.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
// It's this package but without default console log / error https://github.com/trevorr/async-cleanup
|
||||
|
||||
/** A possibly asynchronous function invoked with the process is about to exit. */
|
||||
export type CleanupListener = () => void | Promise<void>;
|
||||
|
||||
let cleanupListeners: Set<CleanupListener> | undefined;
|
||||
|
||||
/** Registers a new cleanup listener. Adding the same listener more than once has no effect. */
|
||||
export function addCleanupListener(listener: CleanupListener): void {
|
||||
// Install exit listeners on initial cleanup listener
|
||||
if (!cleanupListeners) {
|
||||
installExitListeners();
|
||||
cleanupListeners = new Set();
|
||||
}
|
||||
|
||||
cleanupListeners.add(listener);
|
||||
}
|
||||
|
||||
/** Removes an existing cleanup listener, and returns whether the listener was registered. */
|
||||
export function removeCleanupListener(listener: CleanupListener): boolean {
|
||||
return cleanupListeners != null && cleanupListeners.delete(listener);
|
||||
}
|
||||
|
||||
/** Executes all cleanup listeners and then exits the process. Call this instead of `process.exit` to ensure all listeners are fully executed. */
|
||||
export async function exitAfterCleanup(code = 0): Promise<never> {
|
||||
await executeCleanupListeners();
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
/** Executes all cleanup listeners and then kills the process with the given signal. */
|
||||
export async function killAfterCleanup(signal: ExitSignal): Promise<void> {
|
||||
await executeCleanupListeners();
|
||||
process.kill(process.pid, signal);
|
||||
}
|
||||
|
||||
async function executeCleanupListeners(): Promise<void> {
|
||||
if (cleanupListeners) {
|
||||
// Remove exit listeners to restore normal event handling
|
||||
uninstallExitListeners();
|
||||
|
||||
// Clear cleanup listeners to reset state for testing
|
||||
const listeners = cleanupListeners;
|
||||
cleanupListeners = undefined;
|
||||
|
||||
// Call listeners in order added with async listeners running concurrently
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const listener of listeners) {
|
||||
try {
|
||||
const promise = listener();
|
||||
if (promise) promises.push(promise);
|
||||
} catch (err) {
|
||||
// console.error("Uncaught exception during cleanup", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all listeners to complete and log any rejections
|
||||
const results = await Promise.allSettled(promises);
|
||||
for (const result of results) {
|
||||
if (result.status === "rejected") {
|
||||
console.error("Unhandled rejection during cleanup", result.reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function beforeExitListener(code: number): void {
|
||||
// console.log(`Exiting with code ${code} due to empty event loop`);
|
||||
void exitAfterCleanup(code);
|
||||
}
|
||||
|
||||
function uncaughtExceptionListener(error: Error): void {
|
||||
// console.error("Exiting with code 1 due to uncaught exception", error);
|
||||
void exitAfterCleanup(1);
|
||||
}
|
||||
|
||||
function signalListener(signal: ExitSignal): void {
|
||||
// console.log(`Exiting due to signal ${signal}`);
|
||||
void killAfterCleanup(signal);
|
||||
}
|
||||
|
||||
// Listenable signals that terminate the process by default
|
||||
// (except SIGQUIT, which generates a core dump and should not trigger cleanup)
|
||||
// See https://nodejs.org/api/process.html#signal-events
|
||||
const listenedSignals = [
|
||||
"SIGBREAK", // Ctrl-Break on Windows
|
||||
"SIGHUP", // Parent terminal closed
|
||||
"SIGINT", // Terminal interrupt, usually by Ctrl-C
|
||||
"SIGTERM", // Graceful termination
|
||||
"SIGUSR2", // Used by Nodemon
|
||||
] as const;
|
||||
|
||||
/** Signals that can terminate the process. */
|
||||
export type ExitSignal =
|
||||
| typeof listenedSignals[number]
|
||||
| "SIGKILL"
|
||||
| "SIGQUIT"
|
||||
| "SIGSTOP";
|
||||
|
||||
function installExitListeners(): void {
|
||||
process.on("beforeExit", beforeExitListener);
|
||||
process.on("uncaughtException", uncaughtExceptionListener);
|
||||
listenedSignals.forEach((signal) => process.on(signal, signalListener));
|
||||
}
|
||||
|
||||
function uninstallExitListeners(): void {
|
||||
process.removeListener("beforeExit", beforeExitListener);
|
||||
process.removeListener("uncaughtException", uncaughtExceptionListener);
|
||||
listenedSignals.forEach((signal) =>
|
||||
process.removeListener(signal, signalListener)
|
||||
);
|
||||
}
|
||||
124
src/core/functions.ts
Normal file
124
src/core/functions.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { Module, SernAutocompleteData, SernOptionsData } from '../types/core-modules';
|
||||
import type {
|
||||
AnySelectMenuInteraction,
|
||||
ButtonInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
MessageContextMenuCommandInteraction,
|
||||
ModalSubmitInteraction,
|
||||
UserContextMenuCommandInteraction,
|
||||
AutocompleteInteraction,
|
||||
} from 'discord.js';
|
||||
import { ApplicationCommandOptionType, InteractionType } from 'discord.js';
|
||||
import { PluginType } from './structures/enums';
|
||||
import type { Payload, UnpackedDependencies } from '../types/utility';
|
||||
import path from 'node:path'
|
||||
|
||||
export const createSDT = (module: Module, deps: UnpackedDependencies, params: string|undefined) => {
|
||||
return {
|
||||
state: {},
|
||||
deps,
|
||||
params,
|
||||
type: module.type,
|
||||
module: {
|
||||
name: module.name,
|
||||
description: module.description,
|
||||
locals: module.locals,
|
||||
meta: module.meta
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the first character(s) _[depending on prefix length]_ of the message
|
||||
* @param msg
|
||||
* @param prefix The prefix to remove
|
||||
* @returns The message without the prefix
|
||||
* @example
|
||||
* message.content = '!ping';
|
||||
* console.log(fmt(message.content, '!'));
|
||||
* // [ 'ping' ]
|
||||
*/
|
||||
export function fmt(msg: string, prefix?: string): string[] {
|
||||
if(!prefix) throw Error("Unable to parse message without prefix");
|
||||
return msg.slice(prefix.length).trim().split(/\s+/g);
|
||||
}
|
||||
|
||||
|
||||
export function partitionPlugins<T,V>
|
||||
(arr: Array<{ type: PluginType }> = []): [T[], V[]] {
|
||||
const controlPlugins = [];
|
||||
const initPlugins = [];
|
||||
for (const el of arr) {
|
||||
switch (el.type) {
|
||||
case PluginType.Control: controlPlugins.push(el); break;
|
||||
case PluginType.Init: initPlugins.push(el); break;
|
||||
}
|
||||
}
|
||||
return [controlPlugins, initPlugins] as [T[], V[]];
|
||||
}
|
||||
|
||||
export const createLookupTable = (options: SernOptionsData[]): Map<string, SernAutocompleteData> => {
|
||||
const table = new Map<string, SernAutocompleteData>();
|
||||
_createLookupTable(table, options, "<parent>");
|
||||
return table;
|
||||
}
|
||||
|
||||
const _createLookupTable = (table: Map<string, SernAutocompleteData>, options: SernOptionsData[], parent: string) => {
|
||||
for (const opt of options) {
|
||||
const name = path.posix.join(parent, opt.name)
|
||||
switch(opt.type) {
|
||||
case ApplicationCommandOptionType.Subcommand: {
|
||||
_createLookupTable(table, opt.options ?? [], name);
|
||||
} break;
|
||||
case ApplicationCommandOptionType.SubcommandGroup: {
|
||||
_createLookupTable(table, opt.options ?? [], name);
|
||||
} break;
|
||||
default: {
|
||||
if(Reflect.get(opt, 'autocomplete') === true) {
|
||||
table.set(name, opt as SernAutocompleteData)
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface InteractionTypable {
|
||||
type: InteractionType;
|
||||
}
|
||||
//discord.js pls fix ur typings or i will >:(
|
||||
type AnyMessageComponentInteraction = AnySelectMenuInteraction | ButtonInteraction;
|
||||
type AnyCommandInteraction =
|
||||
| ChatInputCommandInteraction
|
||||
| MessageContextMenuCommandInteraction
|
||||
| UserContextMenuCommandInteraction;
|
||||
|
||||
export function isMessageComponent(i: InteractionTypable): i is AnyMessageComponentInteraction {
|
||||
return i.type === InteractionType.MessageComponent;
|
||||
}
|
||||
export function isCommand(i: InteractionTypable): i is AnyCommandInteraction {
|
||||
return i.type === InteractionType.ApplicationCommand;
|
||||
}
|
||||
export function isContextCommand(i: AnyCommandInteraction): i is MessageContextMenuCommandInteraction | UserContextMenuCommandInteraction {
|
||||
return i.isContextMenuCommand();
|
||||
}
|
||||
export function isAutocomplete(i: InteractionTypable): i is AutocompleteInteraction {
|
||||
return i.type === InteractionType.ApplicationCommandAutocomplete;
|
||||
}
|
||||
|
||||
export function isModal(i: InteractionTypable): i is ModalSubmitInteraction {
|
||||
return i.type === InteractionType.ModalSubmit;
|
||||
}
|
||||
|
||||
export function resultPayload<T extends 'success'|'warning'|'failure'>
|
||||
(type: T, module?: Module, reason?: unknown) {
|
||||
return { type, module, reason } as Payload & { type : T };
|
||||
}
|
||||
|
||||
export function pipe<T>(arg: unknown, firstFn: Function, ...fns: Function[]): T {
|
||||
let result = firstFn(arg);
|
||||
for (let fn of fns) {
|
||||
result = fn(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
74
src/core/id.ts
Normal file
74
src/core/id.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { ApplicationCommandType, ComponentType, type Interaction, InteractionType } from 'discord.js';
|
||||
import { CommandType, EventType } from './structures/enums';
|
||||
|
||||
const parseParams = (event: { customId: string }, append: string) => {
|
||||
const hasSlash = event.customId.indexOf('/')
|
||||
if(hasSlash === -1) {
|
||||
return { id:event.customId+append };
|
||||
}
|
||||
const baseid = event.customId.substring(0, hasSlash);
|
||||
const params = event.customId.substring(hasSlash+1);
|
||||
return { id: baseid+append, params }
|
||||
}
|
||||
/**
|
||||
* Construct unique ID for a given interaction object.
|
||||
* @param event The interaction object for which to create an ID.
|
||||
* @returns An array of unique string IDs based on the type and properties of the interaction object.
|
||||
*/
|
||||
export function reconstruct<T extends Interaction>(event: T) {
|
||||
switch (event.type) {
|
||||
case InteractionType.MessageComponent: {
|
||||
const data = parseParams(event, `_C${event.componentType}`)
|
||||
return [data];
|
||||
}
|
||||
case InteractionType.ApplicationCommand:
|
||||
case InteractionType.ApplicationCommandAutocomplete:
|
||||
return [{ id: `${event.commandName}_A${event.commandType}` }, { id: `${event.commandName}_B` }];
|
||||
//Modal interactions are classified as components for sern
|
||||
case InteractionType.ModalSubmit: {
|
||||
const data = parseParams(event, '_M');
|
||||
return [data];
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* A magic number to represent any commandtype that is an ApplicationCommand.
|
||||
*/
|
||||
const PUBLISHABLE = 0b000000001111;
|
||||
|
||||
|
||||
const TypeMap = new Map<number, number>([[CommandType.Text, 0],
|
||||
[CommandType.Both, 0],
|
||||
[CommandType.Slash, ApplicationCommandType.ChatInput],
|
||||
[CommandType.CtxUser, ApplicationCommandType.User],
|
||||
[CommandType.CtxMsg, ApplicationCommandType.Message],
|
||||
[CommandType.Button, ComponentType.Button],
|
||||
[CommandType.StringSelect, ComponentType.StringSelect],
|
||||
[CommandType.Modal, InteractionType.ModalSubmit],
|
||||
[CommandType.UserSelect, ComponentType.UserSelect],
|
||||
[CommandType.MentionableSelect, ComponentType.MentionableSelect],
|
||||
[CommandType.RoleSelect, ComponentType.RoleSelect],
|
||||
[CommandType.ChannelSelect, ComponentType.ChannelSelect]]);
|
||||
|
||||
/*
|
||||
* Generates an id based on name and CommandType.
|
||||
* A is for any ApplicationCommand. C is for any ComponentCommand
|
||||
* Then, another number fetched from TypeMap
|
||||
*/
|
||||
export function create(name: string, type: CommandType | EventType) {
|
||||
if(type == CommandType.Text) {
|
||||
return `${name}_T`;
|
||||
}
|
||||
if(type == CommandType.Both) {
|
||||
return `${name}_B`;
|
||||
}
|
||||
if(type == CommandType.Modal) {
|
||||
return `${name}_M`;
|
||||
}
|
||||
const am = (PUBLISHABLE & type) !== 0 ? 'A' : 'C';
|
||||
return `${name}_${am}${TypeMap.get(type)!}`
|
||||
}
|
||||
|
||||
|
||||
|
||||
55
src/core/interfaces.ts
Normal file
55
src/core/interfaces.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { AnyFunction } from '../types/utility';
|
||||
|
||||
|
||||
/**
|
||||
* Represents an initialization contract.
|
||||
* Let dependencies implement this to initiate some logic.
|
||||
*/
|
||||
export interface Init {
|
||||
init(): unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Disposable contract.
|
||||
* Let dependencies implement this to dispose and cleanup.
|
||||
*/
|
||||
export interface Disposable {
|
||||
dispose(): unknown;
|
||||
}
|
||||
|
||||
|
||||
export interface Emitter {
|
||||
addListener(eventName: string | symbol, listener: AnyFunction): this;
|
||||
removeListener(eventName: string | symbol, listener: AnyFunction): this;
|
||||
emit(eventName: string | symbol, ...payload: any[]): boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
export interface ErrorHandling {
|
||||
/**
|
||||
* @deprecated
|
||||
* Version 4 will remove this method
|
||||
*/
|
||||
crash(err: Error): never;
|
||||
/**
|
||||
* A function that is called on every throw.
|
||||
* @param error
|
||||
*/
|
||||
updateAlive(error: Error): void;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0.0
|
||||
*/
|
||||
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 type LogPayload<T = unknown> = { message: T };
|
||||
163
src/core/ioc.ts
Normal file
163
src/core/ioc.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { Service as $Service, Services as $Services } from '@sern/ioc/global'
|
||||
import { Container } from '@sern/ioc';
|
||||
import * as Contracts from './interfaces';
|
||||
import * as __Services from './structures/default-services';
|
||||
import type { Logging } from './interfaces';
|
||||
import { __init_container, useContainerRaw } from '@sern/ioc/global';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { Client } from 'discord.js';
|
||||
import { Module } from '../types/core-modules';
|
||||
import { UnpackFunction } from '../types/utility';
|
||||
|
||||
export function disposeAll(logger: Logging|undefined) {
|
||||
useContainerRaw()
|
||||
?.disposeAll()
|
||||
.then(() => logger?.info({ message: 'Cleaning container and crashing' }));
|
||||
}
|
||||
|
||||
type Insertable = | ((container: Dependencies) => object)
|
||||
| object
|
||||
|
||||
const dependencyBuilder = (container: Container) => {
|
||||
return {
|
||||
/**
|
||||
* Insert a dependency into your container.
|
||||
* Supply the correct key and dependency
|
||||
*/
|
||||
add(key: keyof Dependencies, v: Insertable) {
|
||||
if(typeof v !== 'function') {
|
||||
container.addSingleton(key, v)
|
||||
} else {
|
||||
//@ts-ignore
|
||||
container.addWiredSingleton(key, (cntr) => v(cntr))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param key the key of the dependency
|
||||
* @param v The dependency to swap out.
|
||||
* Swap out a preexisting dependency.
|
||||
*/
|
||||
swap(key: keyof Dependencies, v: Insertable) {
|
||||
if(typeof v !== 'function') {
|
||||
container.swap(key, v);
|
||||
} else {
|
||||
container.swap(key, v(container.deps()));
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type ValidDependencyConfig =
|
||||
(c: ReturnType<typeof dependencyBuilder>) => any
|
||||
|
||||
/**
|
||||
* makeDependencies constructs a dependency injection container for sern handler to use.
|
||||
* This is required to start the handler, and is to be called before Sern.init.
|
||||
* @example
|
||||
* ```ts
|
||||
* await makeDependencies(({ add }) => {
|
||||
* add('@sern/client', new Client({ intents, partials })
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export async function makeDependencies (conf: ValidDependencyConfig) {
|
||||
const container = await __init_container({ autowire: false });
|
||||
//We only include logger if it does not exist
|
||||
const includeLogger = !container.hasKey('@sern/logger');
|
||||
|
||||
if(includeLogger) {
|
||||
container.addSingleton('@sern/logger', new __Services.DefaultLogging);
|
||||
}
|
||||
container.addSingleton('@sern/errors', new __Services.DefaultErrorHandling);
|
||||
container.addSingleton('@sern/modules', new Map);
|
||||
container.addSingleton('@sern/emitter', new EventEmitter({ captureRejections: true }))
|
||||
container.addSingleton('@sern/scheduler', new __Services.TaskScheduler)
|
||||
conf(dependencyBuilder(container));
|
||||
await container.ready();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Service api, which allows users to access dependencies in places IOC cannot reach.
|
||||
* To obtain intellisense, ensure a .d.ts file exists in the root of compilation.
|
||||
* Our scaffolding tool takes care of this.
|
||||
* Note: this method only works AFTER your container has been initiated
|
||||
* @since 3.0.0
|
||||
* @example
|
||||
* ```ts
|
||||
* const client = Service('@sern/client');
|
||||
* ```
|
||||
* @param key a key that corresponds to a dependency registered.
|
||||
* @throws if container is absent or not present
|
||||
*/
|
||||
export function Service<const T extends keyof Dependencies>(key: T) {
|
||||
return $Service(key) as Dependencies[T]
|
||||
}
|
||||
/**
|
||||
* @since 3.0.0
|
||||
* The plural version of {@link Service}
|
||||
* @throws if container is absent or not present
|
||||
* @returns array of dependencies, in the same order of keys provided
|
||||
*
|
||||
*/
|
||||
export function Services<const T extends (keyof Dependencies)[]>(...keys: [...T]) {
|
||||
return $Services<T, IntoDependencies<T>>(...keys)
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* Creates a singleton object.
|
||||
* @param cb
|
||||
*/
|
||||
export function single<T>(cb: () => T) {
|
||||
console.log('The `single` function is deprecated and has no effect')
|
||||
return cb();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @since 2.0.0
|
||||
* Creates a transient object
|
||||
* @param cb
|
||||
*/
|
||||
export function transient<T>(cb: () => () => T) {
|
||||
console.log('The `transient` function is deprecated and has no effect')
|
||||
return cb()();
|
||||
}
|
||||
|
||||
export type DependencyFromKey<T extends keyof Dependencies> = Dependencies[T];
|
||||
|
||||
|
||||
|
||||
export type IntoDependencies<Tuple extends [...any[]]> = {
|
||||
[Index in keyof Tuple]: UnpackFunction<NonNullable<DependencyFromKey<Tuple[Index]>>>; //Unpack and make NonNullable
|
||||
} & { length: Tuple['length'] };
|
||||
|
||||
export interface CoreDependencies {
|
||||
/**
|
||||
* discord.js client.
|
||||
*/
|
||||
'@sern/client': Client;
|
||||
/**
|
||||
* sern emitter listens to events that happen throughout
|
||||
* the handler. some include module.register, module.activate.
|
||||
*/
|
||||
'@sern/emitter': Contracts.Emitter;
|
||||
/**
|
||||
* An error handler which is the final step before
|
||||
* the sern process actually crashes.
|
||||
*/
|
||||
'@sern/errors': Contracts.ErrorHandling;
|
||||
/**
|
||||
* Optional logger. Performs ... logging
|
||||
*/
|
||||
'@sern/logger'?: Contracts.Logging;
|
||||
/**
|
||||
* Readonly module store. sern stores these
|
||||
* by module.meta.id -> Module
|
||||
*/
|
||||
'@sern/modules': Map<string, Module>;
|
||||
|
||||
'@sern/scheduler': __Services.TaskScheduler
|
||||
}
|
||||
|
||||
70
src/core/module-loading.ts
Normal file
70
src/core/module-loading.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import path from 'node:path';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { readdir } from 'fs/promises';
|
||||
import assert from 'node:assert';
|
||||
import * as Id from './id'
|
||||
import { Module } from '../types/core-modules';
|
||||
|
||||
export const parseCallsite = (site: string) => {
|
||||
const pathobj = path.posix.parse(site.replace(/file:\\?/, "")
|
||||
.split(path.sep)
|
||||
.join(path.posix.sep))
|
||||
return { name: pathobj.name,
|
||||
absPath : path.posix.format(pathobj) }
|
||||
}
|
||||
|
||||
export const shouldHandle = (pth: string, filenam: string) => {
|
||||
const file_name = filenam+path.extname(pth);
|
||||
let newPath = path.join(path.dirname(pth), file_name)
|
||||
.replace(/file:\\?/, "");
|
||||
return { exists: existsSync(newPath),
|
||||
path: 'file://'+newPath };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Import any module based on the absolute path.
|
||||
* This can accept four types of exported modules
|
||||
* commonjs, javascript :
|
||||
* ```js
|
||||
* exports = commandModule({ })
|
||||
* //or
|
||||
* exports.default = commandModule({ })
|
||||
* ```
|
||||
* esm javascript, typescript, and commonjs typescript
|
||||
* export default commandModule({})
|
||||
*/
|
||||
export async function importModule<T>(absPath: string) {
|
||||
let fileModule = await import(absPath);
|
||||
|
||||
let commandModule: Module = fileModule.default;
|
||||
|
||||
assert(commandModule , `No default export @ ${absPath}`);
|
||||
if ('default' in commandModule) {
|
||||
commandModule = commandModule.default as Module;
|
||||
}
|
||||
const p = path.parse(absPath)
|
||||
commandModule.name ??= p.name; commandModule.description ??= "...";
|
||||
commandModule.meta = {
|
||||
id: Id.create(commandModule.name, commandModule.type),
|
||||
absPath,
|
||||
};
|
||||
return { module: commandModule as T };
|
||||
}
|
||||
|
||||
|
||||
export async function* readRecursive(dir: string): AsyncGenerator<string> {
|
||||
const files = await readdir(dir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
const fullPath = path.posix.join(dir, file.name);
|
||||
if (file.isDirectory()) {
|
||||
if (!file.name.startsWith('!')) {
|
||||
yield* readRecursive(fullPath);
|
||||
}
|
||||
} else if (!file.name.startsWith('!')) {
|
||||
yield "file:///"+path.resolve(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
135
src/core/modules.ts
Normal file
135
src/core/modules.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import type { ClientEvents } from 'discord.js';
|
||||
import { EventType } from '../core/structures/enums';
|
||||
import type {
|
||||
InputCommand,
|
||||
InputEvent,
|
||||
Module,
|
||||
ScheduledTask,
|
||||
} from '../types/core-modules';
|
||||
import { partitionPlugins } from './functions'
|
||||
import type { Awaitable } from '../types/utility';
|
||||
|
||||
/**
|
||||
* Creates a command module with standardized structure and plugin support.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @param {InputCommand} mod - Command module configuration
|
||||
* @returns {Module} Processed command module ready for registration
|
||||
*
|
||||
* @example
|
||||
* // Basic slash command
|
||||
* export default commandModule({
|
||||
* type: CommandType.Slash,
|
||||
* description: "Ping command",
|
||||
* execute: async (ctx) => {
|
||||
* await ctx.reply("Pong! 🏓");
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Command with component interaction
|
||||
* export default commandModule({
|
||||
* type: CommandType.Slash,
|
||||
* description: "Interactive command",
|
||||
* execute: async (ctx) => {
|
||||
* const button = new ButtonBuilder({
|
||||
* customId: "btn/someData",
|
||||
* label: "Click me",
|
||||
* style: ButtonStyle.Primary
|
||||
* });
|
||||
* await ctx.reply({
|
||||
* content: "Interactive message",
|
||||
* components: [new ActionRowBuilder().addComponents(button)]
|
||||
* });
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
export function commandModule(mod: InputCommand): Module {
|
||||
const [onEvent, plugins] = partitionPlugins(mod.plugins);
|
||||
return { ...mod,
|
||||
onEvent,
|
||||
plugins,
|
||||
locals: {} } as Module;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an event module for handling Discord.js or custom events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @template T - Event name from ClientEvents
|
||||
* @param {InputEvent<T>} mod - Event module configuration
|
||||
* @returns {Module} Processed event module ready for registration
|
||||
* @throws {Error} If ControlPlugins are used in event modules
|
||||
*
|
||||
* @example
|
||||
* // Discord event listener
|
||||
* export default eventModule({
|
||||
* type: EventType.Discord,
|
||||
* execute: async (message) => {
|
||||
* console.log(`${message.author.tag}: ${message.content}`);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Custom sern event
|
||||
* export default eventModule({
|
||||
* type: EventType.Sern,
|
||||
* execute: async (eventData) => {
|
||||
* // Handle sern-specific event
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
export function eventModule<T extends keyof ClientEvents = keyof ClientEvents>(mod: InputEvent<T>): Module {
|
||||
const [onEvent, plugins] = partitionPlugins(mod.plugins);
|
||||
if(onEvent.length !== 0) throw Error("Event modules cannot have ControlPlugins");
|
||||
return { ...mod,
|
||||
plugins,
|
||||
locals: {} } as Module;
|
||||
}
|
||||
|
||||
/** Create event modules from discord.js client events,
|
||||
* This was an {@link eventModule} for discord events,
|
||||
* where typings were bad.
|
||||
* @deprecated Use {@link eventModule} instead
|
||||
* @param mod
|
||||
*/
|
||||
export function discordEvent<T extends keyof ClientEvents>(mod: {
|
||||
name: T;
|
||||
once?: boolean;
|
||||
execute: (...args: ClientEvents[T]) => Awaitable<unknown>;
|
||||
}) {
|
||||
return eventModule({ type: EventType.Discord, ...mod, });
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a scheduled task that can be executed at specified intervals using cron patterns
|
||||
*
|
||||
* @param {ScheduledTask} ism - The scheduled task configuration object
|
||||
* @param {string} ism.trigger - A cron pattern that determines when the task should execute
|
||||
* Format: "* * * * *" (minute hour day month day-of-week)
|
||||
* @param {Function} ism.execute - The function to execute when the task is triggered
|
||||
* @param {Object} ism.execute.context - The execution context passed to the task
|
||||
*
|
||||
* @returns {ScheduledTask} The configured scheduled task
|
||||
*
|
||||
* @example
|
||||
* // Create a task that runs every minute
|
||||
* export default scheduledTask({
|
||||
* trigger: "* * * * *",
|
||||
* execute: (context) => {
|
||||
* console.log("Task executed!");
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @remarks
|
||||
* - Tasks must be placed in the 'tasks' directory specified in your config
|
||||
* - The file name serves as a unique identifier for the task
|
||||
* - Tasks can be cancelled using deps['@sern/scheduler'].kill(uuid)
|
||||
*
|
||||
* @see {@link https://crontab.guru/} for testing and creating cron patterns
|
||||
*/
|
||||
export function scheduledTask(ism: ScheduledTask): ScheduledTask {
|
||||
return ism
|
||||
}
|
||||
|
||||
137
src/core/plugin.ts
Normal file
137
src/core/plugin.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { CommandType, PluginType } from './structures/enums';
|
||||
import type { Plugin, PluginResult, CommandArgs, InitArgs } from '../types/core-plugin';
|
||||
import { Err, Ok } from './structures/result';
|
||||
import type { Dictionary } from '../types/utility';
|
||||
|
||||
export function makePlugin<V extends unknown[]>(
|
||||
type: PluginType,
|
||||
execute: (...args: any[]) => any,
|
||||
): Plugin<V> {
|
||||
return { type, execute } as Plugin<V>;
|
||||
}
|
||||
/**
|
||||
* @since 2.5.0
|
||||
*/
|
||||
export function EventInitPlugin(execute: (args: InitArgs) => PluginResult) {
|
||||
return makePlugin(PluginType.Init, execute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an initialization plugin for command preprocessing and modification
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @template I - Extends CommandType to enforce type safety for command modules
|
||||
*
|
||||
* @param {function} execute - Function to execute during command initialization
|
||||
* @param {InitArgs<T>} execute.args - The initialization arguments
|
||||
* @param {T} execute.args.module - The command module being initialized
|
||||
* @param {string} execute.args.absPath - The absolute path to the module file
|
||||
* @param {Dependencies} execute.args.deps - Dependency injection container
|
||||
*
|
||||
* @returns {Plugin} A plugin that runs during command initialization
|
||||
*
|
||||
* @example
|
||||
* // Plugin to update command description
|
||||
* export const updateDescription = (description: string) => {
|
||||
* return CommandInitPlugin(({ deps }) => {
|
||||
* if(description.length > 100) {
|
||||
* deps.logger?.info({ message: "Invalid description" })
|
||||
* return controller.stop("From updateDescription: description is invalid");
|
||||
* }
|
||||
* module.description = description;
|
||||
* return controller.next();
|
||||
* });
|
||||
* };
|
||||
*
|
||||
* @example
|
||||
* // Plugin to store registration date in module locals
|
||||
* export const dateRegistered = () => {
|
||||
* return CommandInitPlugin(({ module }) => {
|
||||
* module.locals.registered = Date.now()
|
||||
* return controller.next();
|
||||
* });
|
||||
* };
|
||||
*
|
||||
* @remarks
|
||||
* - Init plugins can modify how commands are loaded and perform preprocessing
|
||||
* - The module.locals object can be used to store custom plugin-specific data
|
||||
* - Be careful when modifying module fields as multiple plugins may interact with them
|
||||
* - Use controller.next() to continue to the next plugin
|
||||
* - Use controller.stop(reason) to halt plugin execution
|
||||
*/
|
||||
export function CommandInitPlugin<I extends CommandType>(
|
||||
execute: (args: InitArgs) => PluginResult
|
||||
): Plugin {
|
||||
return makePlugin(PluginType.Init, execute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a control plugin for command preprocessing, filtering, and state management
|
||||
*
|
||||
* @since 2.5.0
|
||||
* @template I - Extends CommandType to enforce type safety for command modules
|
||||
*
|
||||
* @param {function} execute - Function to execute during command control flow
|
||||
* @param {CommandArgs<I>} execute.args - The command arguments array
|
||||
* @param {Context} execute.args[0] - The discord context (e.g., guild, channel, user info, interaction)
|
||||
* @param {SDT} execute.args[1] - The State, Dependencies, Params, Module, and Type object
|
||||
*
|
||||
* @returns {Plugin} A plugin that runs during command execution flow
|
||||
*
|
||||
* @example
|
||||
* // Plugin to restrict command to specific guild
|
||||
* export const inGuild = (guildId: string) => {
|
||||
* return CommandControlPlugin((ctx, sdt) => {
|
||||
* if(ctx.guild.id !== guildId) {
|
||||
* return controller.stop();
|
||||
* }
|
||||
* return controller.next();
|
||||
* });
|
||||
* };
|
||||
*
|
||||
* @example
|
||||
* // Plugins passing state through the chain
|
||||
* const plugin1 = CommandControlPlugin((ctx, sdt) => {
|
||||
* return controller.next({ 'plugin1/data': 'from plugin1' });
|
||||
* });
|
||||
*
|
||||
* const plugin2 = CommandControlPlugin((ctx, sdt) => {
|
||||
* return controller.next({ 'plugin2/data': ctx.user.id });
|
||||
* });
|
||||
*
|
||||
* export default commandModule({
|
||||
* type: CommandType.Slash,
|
||||
* plugins: [plugin1, plugin2],
|
||||
* execute: (ctx, sdt) => {
|
||||
* console.log(sdt.state); // Access accumulated state
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* @remarks
|
||||
* - Control plugins are executed in order when a discord.js event is emitted
|
||||
* - Use controller.next() to continue to next plugin or controller.stop() to halt execution
|
||||
* - State can be passed between plugins using controller.next({ key: value })
|
||||
* - State keys should be namespaced to avoid collisions (e.g., 'plugin-name/key')
|
||||
* - Final accumulated state is passed to the command's execute function
|
||||
* - All plugins must succeed for the command to execute
|
||||
* - Plugins have access to dependencies through the sdt.deps object
|
||||
* - Useful for implementing preconditions, filters, and command preprocessing
|
||||
*/
|
||||
export function CommandControlPlugin<I extends CommandType>(
|
||||
execute: (...args: CommandArgs<I>) => PluginResult,
|
||||
) {
|
||||
return makePlugin(PluginType.Control, execute);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 1.0.0
|
||||
* The object passed into every plugin to control a command's behavior
|
||||
*/
|
||||
export const controller = {
|
||||
next: (val?: Dictionary) => Ok(val),
|
||||
stop: (val?: string) => Err(val),
|
||||
};
|
||||
|
||||
|
||||
export type Controller = typeof controller;
|
||||
65
src/core/presences.ts
Normal file
65
src/core/presences.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { ActivitiesOptions } from "discord.js";
|
||||
import type { IntoDependencies } from "./ioc";
|
||||
import type { Emitter } from "./interfaces";
|
||||
import { Awaitable } from "../types/utility";
|
||||
|
||||
type Status = 'online' | 'idle' | 'invisible' | 'dnd'
|
||||
type PresenceReduce = (previous: Presence.Result) => Awaitable<Presence.Result>;
|
||||
|
||||
export const Presence = {
|
||||
/**
|
||||
* A small wrapper to provide type inference.
|
||||
* Create a Presence module which **MUST** be put in a file called presence.(language-extension)
|
||||
* adjacent to the file where **Sern.init** is CALLED.
|
||||
*/
|
||||
module : <T extends (keyof Dependencies)[]>(conf: Presence.Config<T>) => conf,
|
||||
/**
|
||||
* Create a Presence body which can be either:
|
||||
* - once, the presence is activated only once.
|
||||
* - repeated, per cycle or event, the presence can be changed.
|
||||
*/
|
||||
of : (root: Omit<Presence.Result, 'repeat' | 'onRepeat'>) => {
|
||||
return {
|
||||
/**
|
||||
* @example
|
||||
* Presence
|
||||
* .of({ activities: [{ name: "deez nuts" }] }) //starts presence with "deez nuts".
|
||||
* .repeated(prev => {
|
||||
* return {
|
||||
* afk: true,
|
||||
* activities: prev.activities?.map(s => ({ ...s, name: s.name+"s" }))
|
||||
* };
|
||||
* }, 10000)) //every 10 s, the callback sets the presence to the value returned.
|
||||
*/
|
||||
repeated: (onRepeat: PresenceReduce, repeat: number | [Emitter, string]) => {
|
||||
return { repeat, onRepeat, ...root }
|
||||
},
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* Presence.of({
|
||||
* activities: [{ name: "Chilling out" }]
|
||||
* }).once() // Sets the presence once, with what's provided in '.of()'
|
||||
* ```
|
||||
*/
|
||||
once: () => root
|
||||
};
|
||||
}
|
||||
}
|
||||
export declare namespace Presence {
|
||||
export type Config<T extends (keyof Dependencies)[]> = {
|
||||
inject?: [...T]
|
||||
execute: (...v: IntoDependencies<T>) => Awaitable<Presence.Result>;
|
||||
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
status?: Status;
|
||||
afk?: boolean;
|
||||
activities?: ActivitiesOptions[];
|
||||
shardId?: number[];
|
||||
repeat?: number | [Emitter, string];
|
||||
onRepeat?: PresenceReduce
|
||||
}
|
||||
}
|
||||
|
||||
131
src/core/structures/context.ts
Normal file
131
src/core/structures/context.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import type {
|
||||
BaseInteraction,
|
||||
ChatInputCommandInteraction,
|
||||
Client,
|
||||
InteractionReplyOptions,
|
||||
Message,
|
||||
MessageReplyOptions,
|
||||
Snowflake,
|
||||
User,
|
||||
} from 'discord.js';
|
||||
import { Result, Ok, Err, val } from './result';
|
||||
import * as assert from 'assert';
|
||||
import type { ReplyOptions } from '../../types/utility';
|
||||
import { fmt } from '../functions'
|
||||
import { SernError } from './enums';
|
||||
|
||||
|
||||
/**
|
||||
* @since 1.0.0
|
||||
* Provides values shared between
|
||||
* Message and ChatInputCommandInteraction
|
||||
*/
|
||||
export class Context {
|
||||
|
||||
get options() {
|
||||
if(this.isMessage()) {
|
||||
const [, ...rest] = fmt(this.message.content, this.prefix);
|
||||
return rest;
|
||||
}
|
||||
return this.interaction.options;
|
||||
}
|
||||
|
||||
|
||||
protected constructor(protected ctx: Result<Message, ChatInputCommandInteraction>,
|
||||
private __prefix?: string) { }
|
||||
public get prefix() {
|
||||
return this.__prefix;
|
||||
}
|
||||
public get id(): Snowflake {
|
||||
return val(this.ctx).id
|
||||
}
|
||||
|
||||
public get channel() {
|
||||
return val(this.ctx).channel;
|
||||
}
|
||||
|
||||
public get channelId(): Snowflake {
|
||||
return val(this.ctx).channelId;
|
||||
}
|
||||
|
||||
/**
|
||||
* If context is holding a message, message.author
|
||||
* else, interaction.user
|
||||
*/
|
||||
public get user(): User {
|
||||
if(this.ctx.ok) {
|
||||
return this.ctx.value.author;
|
||||
}
|
||||
return this.ctx.error.user;
|
||||
|
||||
}
|
||||
|
||||
public get userId(): Snowflake {
|
||||
return this.user.id;
|
||||
}
|
||||
|
||||
public get createdTimestamp(): number {
|
||||
return val(this.ctx).createdTimestamp;
|
||||
}
|
||||
|
||||
public get guild() {
|
||||
return val(this.ctx).guild;
|
||||
}
|
||||
|
||||
public get guildId() {
|
||||
return val(this.ctx).guildId;
|
||||
}
|
||||
/*
|
||||
* interactions can return APIGuildMember if the guild it is emitted from is not cached
|
||||
*/
|
||||
public get member() {
|
||||
return val(this.ctx).member;
|
||||
}
|
||||
|
||||
get message(): Message {
|
||||
if(this.ctx.ok) {
|
||||
return this.ctx.value;
|
||||
}
|
||||
throw Error(SernError.MismatchEvent);
|
||||
}
|
||||
public isMessage(): this is Context & { ctx: Result<Message, never> } {
|
||||
return this.ctx.ok;
|
||||
}
|
||||
|
||||
public isSlash(): this is Context & { ctx: Result<never, ChatInputCommandInteraction> } {
|
||||
return !this.isMessage();
|
||||
}
|
||||
|
||||
get interaction(): ChatInputCommandInteraction {
|
||||
if(!this.ctx.ok) {
|
||||
return this.ctx.error;
|
||||
}
|
||||
throw Error(SernError.MismatchEvent);
|
||||
}
|
||||
|
||||
|
||||
public get client(): Client {
|
||||
return val(this.ctx).client;
|
||||
}
|
||||
|
||||
public get inGuild(): boolean {
|
||||
return val(this.ctx).inGuild()
|
||||
}
|
||||
|
||||
public async reply(content: ReplyOptions) {
|
||||
if(this.ctx.ok) {
|
||||
return this.ctx.value.reply(content as MessageReplyOptions)
|
||||
}
|
||||
interface FetchReply { fetchReply: true };
|
||||
return this.ctx.error.reply(content as InteractionReplyOptions & FetchReply)
|
||||
|
||||
}
|
||||
|
||||
static wrap(wrappable: BaseInteraction | Message, prefix?: string): Context {
|
||||
if ('interaction' in wrappable) {
|
||||
return new Context(Ok(wrappable), prefix);
|
||||
}
|
||||
assert.ok(wrappable.isChatInputCommand(), "Context created with bad interaction.");
|
||||
return new Context(Err(wrappable), prefix);
|
||||
}
|
||||
}
|
||||
89
src/core/structures/default-services.ts
Normal file
89
src/core/structures/default-services.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { ScheduledTask } from '../../types/core-modules';
|
||||
import type { LogPayload, Logging, ErrorHandling, Disposable } from '../interfaces';
|
||||
import { CronJob } from 'cron';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 2.0.0
|
||||
* Version 4.0.0 will internalize this api. Please refrain from using the defaults!
|
||||
*/
|
||||
export class DefaultErrorHandling implements ErrorHandling {
|
||||
crash(err: Error): never {
|
||||
throw err;
|
||||
}
|
||||
updateAlive(err: Error) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 2.0.0
|
||||
* Version 4.0.0 will internalize this api. Please refrain from using ModuleStore!
|
||||
*/
|
||||
export class DefaultLogging implements Logging {
|
||||
private date() { return 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}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class TaskScheduler implements Disposable {
|
||||
private __tasks: Map<string, CronJob<any, any>> = new Map();
|
||||
|
||||
schedule(uuid: string, task: ScheduledTask, deps: Dependencies) {
|
||||
if (this.__tasks.has(uuid)) {
|
||||
throw Error("while scheduling a task \
|
||||
found another task of same name. Not scheduling " +
|
||||
uuid + "again." );
|
||||
}
|
||||
try {
|
||||
const onTick = async function(this: CronJob) {
|
||||
task.execute({ id: uuid,
|
||||
lastTimeExecution: this.lastExecution,
|
||||
nextTimeExecution: this.nextDate().toJSDate() }, { deps })
|
||||
}
|
||||
const job = CronJob.from({ cronTime: task.trigger, onTick, timeZone: task.timezone });
|
||||
job.start();
|
||||
this.__tasks.set(uuid, job);
|
||||
} catch (error) {
|
||||
throw Error(`while scheduling a task ${uuid} ` + error);
|
||||
}
|
||||
}
|
||||
|
||||
kill(taskName: string): boolean {
|
||||
const job = this.__tasks.get(taskName);
|
||||
if (job) {
|
||||
job.stop();
|
||||
this.__tasks.delete(taskName);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get tasks(): string[] {
|
||||
return Array.from(this.__tasks.keys());
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.__tasks.forEach((_, id) => {
|
||||
this.kill(id);
|
||||
this.__tasks.delete(id);
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
134
src/core/structures/enums.ts
Normal file
134
src/core/structures/enums.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @since 1.0.0
|
||||
* 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)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export enum CommandType {
|
||||
Text = 1 << 0,
|
||||
Slash = 1 << 1,
|
||||
Both = 3,
|
||||
CtxUser = 1 << 2,
|
||||
CtxMsg = 1 << 3,
|
||||
Button = 1 << 4,
|
||||
StringSelect = 1 << 5,
|
||||
Modal = 1 << 6,
|
||||
UserSelect = 1 << 7,
|
||||
RoleSelect = 1 << 8,
|
||||
MentionableSelect = 1 << 9,
|
||||
ChannelSelect = 1 << 10,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* The EventType for handling sern events
|
||||
*/
|
||||
Sern,
|
||||
/**
|
||||
* The EventType for handling external events.
|
||||
* Could be for example, `process` events, database events
|
||||
*/
|
||||
External,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* The PluginType for EventPlugins
|
||||
*/
|
||||
Control = 2,
|
||||
}
|
||||
/**
|
||||
* @deprecated - Use strings 'success' | 'failure' | 'warning'
|
||||
* @enum { string }
|
||||
*/
|
||||
export enum PayloadType {
|
||||
Success = 'success',
|
||||
Failure = 'failure',
|
||||
Warning = 'warning',
|
||||
}
|
||||
|
||||
/**
|
||||
* @enum { string }
|
||||
*/
|
||||
export const 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!
|
||||
*/
|
||||
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`,
|
||||
}
|
||||
20
src/core/structures/result.ts
Normal file
20
src/core/structures/result.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export type Result<Ok, Err> =
|
||||
| { ok: true; value: Ok }
|
||||
| { ok: false; error: Err };
|
||||
|
||||
export const Ok = <Ok>(value: Ok) => ({ ok: true, value } as const);
|
||||
export const Err = <Err>(error: Err) => ({ ok: false, error } as const);
|
||||
|
||||
export const val = <O, E>(r: Result<O, E>) => r.ok ? r.value : r.error;
|
||||
export const EMPTY_ERR = Err(undefined);
|
||||
|
||||
/**
|
||||
* Wrap an async operation that may throw an Error (`try-catch` style) into checked exception style
|
||||
* @param op The operation function
|
||||
*/
|
||||
export async function wrapAsync<T, E = unknown>(op: () => Promise<T>): Promise<Result<T, E>> {
|
||||
try { return op()
|
||||
.then(Ok)
|
||||
.catch(Err); }
|
||||
catch (e) { return Promise.resolve(Err(e as E)); }
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
import type {
|
||||
CommandInteraction,
|
||||
Interaction,
|
||||
MessageComponentInteraction,
|
||||
ModalSubmitInteraction,
|
||||
SelectMenuInteraction,
|
||||
} from 'discord.js';
|
||||
import { concatMap, fromEvent, map, Observable, of, throwError } from 'rxjs';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import * as Files from '../utilities/readFile';
|
||||
import { match } from 'ts-pattern';
|
||||
import { SernError } from '../structures/errors';
|
||||
import Context from '../structures/context';
|
||||
import { controller } from '../sern';
|
||||
import type { Module } from '../structures/module';
|
||||
import {
|
||||
isApplicationCommand,
|
||||
isAutocomplete,
|
||||
isButton,
|
||||
isChatInputCommand,
|
||||
isMessageComponent,
|
||||
isMessageCtxMenuCmd,
|
||||
isModalSubmit,
|
||||
isSelectMenu,
|
||||
isUserContextMenuCmd,
|
||||
} from '../utilities/predicates';
|
||||
import { filterCorrectModule } from './observableHandling';
|
||||
import { CommandType } from '../structures/enums';
|
||||
import type { AutocompleteInteraction } from 'discord.js';
|
||||
import { asyncResolveArray } from '../utilities/asyncResolveArray';
|
||||
|
||||
function applicationCommandHandler(mod: Module | undefined, interaction: CommandInteraction) {
|
||||
const mod$ = <T extends CommandType>(cmdTy: T) => of(mod).pipe(filterCorrectModule(cmdTy));
|
||||
return (
|
||||
match(interaction)
|
||||
.when(isChatInputCommand, i => {
|
||||
const ctx = Context.wrap(i);
|
||||
return mod$(CommandType.Slash).pipe(
|
||||
concatMap(m => {
|
||||
return of(
|
||||
m.onEvent.map(e => e.execute([ctx, ['slash', i.options]], controller)),
|
||||
).pipe(
|
||||
map(res => ({
|
||||
mod,
|
||||
res,
|
||||
execute() {
|
||||
return m.execute(ctx, ['slash', i.options]);
|
||||
},
|
||||
})),
|
||||
);
|
||||
}),
|
||||
);
|
||||
})
|
||||
//Todo: refactor so that we dont have to have two separate branches. They're near identical!!
|
||||
//Only thing that differs is type of interaction
|
||||
.when(isMessageCtxMenuCmd, ctx => {
|
||||
return mod$(CommandType.MenuMsg).pipe(
|
||||
concatMap(m => {
|
||||
return of(m.onEvent.map(e => e.execute([ctx], controller))).pipe(
|
||||
map(res => ({
|
||||
mod,
|
||||
res,
|
||||
execute() {
|
||||
return m.execute(ctx);
|
||||
},
|
||||
})),
|
||||
);
|
||||
}),
|
||||
);
|
||||
})
|
||||
.when(isUserContextMenuCmd, ctx => {
|
||||
return mod$(CommandType.MenuUser).pipe(
|
||||
concatMap(m => {
|
||||
return of(m.onEvent.map(e => e.execute([ctx], controller))).pipe(
|
||||
map(res => ({
|
||||
mod,
|
||||
res,
|
||||
execute() {
|
||||
return m.execute(ctx);
|
||||
},
|
||||
})),
|
||||
);
|
||||
}),
|
||||
);
|
||||
})
|
||||
.run()
|
||||
);
|
||||
}
|
||||
|
||||
function messageComponentInteractionHandler(
|
||||
mod: Module | undefined,
|
||||
interaction: MessageComponentInteraction,
|
||||
) {
|
||||
const mod$ = <T extends CommandType>(ty: T) => of(mod).pipe(filterCorrectModule(ty));
|
||||
//Todo: refactor so that we dont have to have two separate branches. They're near identical!!
|
||||
//Only thing that differs is type of interaction
|
||||
return match(interaction)
|
||||
.when(isButton, ctx => {
|
||||
return mod$(CommandType.Button).pipe(
|
||||
concatMap(m => {
|
||||
return of(m.onEvent.map(e => e.execute([ctx], controller))).pipe(
|
||||
map(res => ({
|
||||
mod,
|
||||
res,
|
||||
execute() {
|
||||
return m.execute(ctx);
|
||||
},
|
||||
})),
|
||||
);
|
||||
}),
|
||||
);
|
||||
})
|
||||
.when(isSelectMenu, (ctx: SelectMenuInteraction) => {
|
||||
return mod$(CommandType.MenuSelect).pipe(
|
||||
concatMap(m => {
|
||||
return of(m.onEvent.map(e => e.execute([ctx], controller))).pipe(
|
||||
map(res => ({
|
||||
mod,
|
||||
res,
|
||||
execute() {
|
||||
return m.execute(ctx);
|
||||
},
|
||||
})),
|
||||
);
|
||||
}),
|
||||
);
|
||||
})
|
||||
.otherwise(() => throwError(() => SernError.NotSupportedInteraction));
|
||||
}
|
||||
|
||||
function modalHandler(modul: Module | undefined, ctx: ModalSubmitInteraction) {
|
||||
return of(modul).pipe(
|
||||
filterCorrectModule(CommandType.Modal),
|
||||
concatMap(mod => {
|
||||
return of(mod.onEvent.map(e => e.execute([ctx], controller))).pipe(
|
||||
map(res => ({
|
||||
mod,
|
||||
res,
|
||||
execute() {
|
||||
return mod.execute(ctx);
|
||||
},
|
||||
})),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
function autoCmpHandler(mod: Module | undefined, interaction: AutocompleteInteraction) {
|
||||
return of(mod).pipe(
|
||||
filterCorrectModule(CommandType.Slash),
|
||||
concatMap(mod => {
|
||||
const choice = interaction.options.getFocused(true);
|
||||
const selectedOption = mod.options?.find(o => o.autocomplete && o.name === choice.name);
|
||||
if (selectedOption !== undefined && selectedOption.autocomplete) {
|
||||
return of(
|
||||
selectedOption.command.onEvent.map(e => e.execute(interaction, controller)),
|
||||
).pipe(
|
||||
map(res => ({
|
||||
mod,
|
||||
res,
|
||||
execute() {
|
||||
return selectedOption.command.execute(interaction);
|
||||
},
|
||||
})),
|
||||
);
|
||||
}
|
||||
return throwError(
|
||||
() =>
|
||||
SernError.NotSupportedInteraction +
|
||||
` There is probably no autocomplete tag for this option`,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export function onInteractionCreate(wrapper: Wrapper) {
|
||||
const { client } = wrapper;
|
||||
|
||||
const interactionEvent$ = <Observable<Interaction>>fromEvent(client, 'interactionCreate');
|
||||
|
||||
interactionEvent$
|
||||
.pipe(
|
||||
/*processing plugins*/
|
||||
concatMap(interaction => {
|
||||
if (isApplicationCommand(interaction)) {
|
||||
const modul =
|
||||
Files.ApplicationCommands[interaction.commandType].get(
|
||||
interaction.commandName,
|
||||
) ?? Files.BothCommands.get(interaction.commandName);
|
||||
return applicationCommandHandler(modul, interaction);
|
||||
}
|
||||
if (isMessageComponent(interaction)) {
|
||||
const modul = Files.MessageCompCommands[interaction.componentType].get(
|
||||
interaction.customId,
|
||||
);
|
||||
return messageComponentInteractionHandler(modul, interaction);
|
||||
}
|
||||
if (isModalSubmit(interaction)) {
|
||||
const modul = Files.ModalSubmitCommands.get(interaction.customId);
|
||||
return modalHandler(modul, interaction);
|
||||
}
|
||||
if (isAutocomplete(interaction)) {
|
||||
const modul =
|
||||
Files.ApplicationCommands['1'].get(interaction.commandName) ??
|
||||
Files.BothCommands.get(interaction.commandName);
|
||||
return autoCmpHandler(modul, interaction);
|
||||
}
|
||||
return throwError(() => SernError.NotSupportedInteraction);
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
async next({ mod, res: eventPluginRes, execute }) {
|
||||
const ePlugArr = await asyncResolveArray(eventPluginRes);
|
||||
if (ePlugArr.every(e => e.ok)) {
|
||||
await execute();
|
||||
wrapper.sernEmitter?.emit('module.activate', { type: 'success', module: mod! });
|
||||
} else {
|
||||
wrapper.sernEmitter?.emit('module.activate', {
|
||||
type: 'failure',
|
||||
module: mod!,
|
||||
reason: SernError.PluginFailure,
|
||||
});
|
||||
}
|
||||
},
|
||||
error(err) {
|
||||
wrapper.sernEmitter?.emit('error', err);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
import type { Message } from 'discord.js';
|
||||
import { concatMap, from, fromEvent, map, Observable, of } from 'rxjs';
|
||||
import { controller } from '../sern';
|
||||
import Context from '../structures/context';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import { fmt } from '../utilities/messageHelpers';
|
||||
import * as Files from '../utilities/readFile';
|
||||
import { filterCorrectModule, ignoreNonBot } from './observableHandling';
|
||||
import { CommandType } from '../structures/enums';
|
||||
import { SernError } from '../structures/errors';
|
||||
import { asyncResolveArray } from '../utilities/asyncResolveArray';
|
||||
|
||||
export const onMessageCreate = (wrapper: Wrapper) => {
|
||||
const { client, defaultPrefix } = wrapper;
|
||||
if (defaultPrefix === undefined) return;
|
||||
|
||||
const messageEvent$ = <Observable<Message>>fromEvent(client, 'messageCreate');
|
||||
|
||||
const processMessage$ = messageEvent$.pipe(
|
||||
ignoreNonBot(defaultPrefix),
|
||||
map(message => {
|
||||
const [prefix, ...rest] = fmt(message, defaultPrefix);
|
||||
return {
|
||||
ctx: Context.wrap(message),
|
||||
args: <['text', string[]]>['text', rest],
|
||||
mod:
|
||||
Files.TextCommands.text.get(prefix) ??
|
||||
Files.BothCommands.get(prefix) ??
|
||||
Files.TextCommands.aliases.get(prefix),
|
||||
};
|
||||
}),
|
||||
);
|
||||
const ensureModuleType$ = processMessage$.pipe(
|
||||
concatMap(payload =>
|
||||
of(payload.mod).pipe(
|
||||
filterCorrectModule(CommandType.Text),
|
||||
map(mod => ({ ...payload, mod })),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const processEventPlugins$ = ensureModuleType$.pipe(
|
||||
concatMap(({ ctx, args, mod }) => {
|
||||
const res = asyncResolveArray(
|
||||
mod.onEvent.map(ePlug => {
|
||||
return ePlug.execute([ctx, args], controller);
|
||||
}),
|
||||
);
|
||||
return from(res).pipe(map(res => ({ mod, ctx, args, res })));
|
||||
}),
|
||||
);
|
||||
|
||||
processEventPlugins$.subscribe({
|
||||
next({ mod, ctx, args, res }) {
|
||||
if (res.every(pl => pl.ok)) {
|
||||
Promise.resolve(mod.execute(ctx, args)).then(() => {
|
||||
wrapper.sernEmitter?.emit('module.activate', { type: 'success', module: mod! });
|
||||
});
|
||||
} else {
|
||||
wrapper.sernEmitter?.emit('module.activate', {
|
||||
type: 'failure',
|
||||
module: mod!,
|
||||
reason: SernError.PluginFailure,
|
||||
});
|
||||
}
|
||||
},
|
||||
error(e) {
|
||||
wrapper.sernEmitter?.emit('error', e);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
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(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function ignoreNonBot(prefix: string) {
|
||||
return (src: Observable<Message>) =>
|
||||
new Observable<Message>(subscriber => {
|
||||
return src.subscribe({
|
||||
next(m) {
|
||||
const messageFromHumanAndHasPrefix =
|
||||
!m.author.bot &&
|
||||
m.content
|
||||
.slice(0, prefix.length)
|
||||
.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0;
|
||||
if (messageFromHumanAndHasPrefix) {
|
||||
subscriber.next(m);
|
||||
}
|
||||
},
|
||||
error: e => subscriber.error(e),
|
||||
complete: () => subscriber.complete(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current value in Result stream is an error, calls callback.
|
||||
* @param cb
|
||||
*/
|
||||
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) {
|
||||
cb(value.val);
|
||||
} else {
|
||||
subscriber.next(value.val);
|
||||
}
|
||||
},
|
||||
error: e => subscriber.error(e),
|
||||
complete: () => subscriber.complete(),
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import { concat, concatMap, from, fromEvent, map, Observable, of, skip, take } from 'rxjs';
|
||||
import { basename } from 'path';
|
||||
import * as Files from '../utilities/readFile';
|
||||
import type Wrapper from '../structures/wrapper';
|
||||
import type { Result } from 'ts-results';
|
||||
import { Err, Ok } from 'ts-results';
|
||||
import type { Awaitable } from 'discord.js';
|
||||
import { ApplicationCommandType, ComponentType } from 'discord.js';
|
||||
import type { CommandModule } from '../structures/module';
|
||||
import { match } from 'ts-pattern';
|
||||
import { SernError } from '../structures/errors';
|
||||
import type { DefinedCommandModule } from '../../types/handler';
|
||||
import { CommandType, PluginType } from '../structures/enums';
|
||||
import { errTap } from './observableHandling';
|
||||
import { processCommandPlugins } from './userDefinedEventsHandling';
|
||||
|
||||
export function onReady(wrapper: Wrapper) {
|
||||
const { client, commands } = wrapper;
|
||||
const ready$ = fromEvent(client, 'ready').pipe(take(1), skip(1));
|
||||
|
||||
// Using sernModule function already checks if module is not EventModule
|
||||
const processCommandFiles$ = Files.buildData<CommandModule>(commands).pipe(
|
||||
errTap(reason => {
|
||||
wrapper.sernEmitter?.emit('module.register', {
|
||||
type: 'failure',
|
||||
module: undefined,
|
||||
reason,
|
||||
});
|
||||
}),
|
||||
map(({ mod, absPath }) => {
|
||||
return {
|
||||
absPath,
|
||||
mod: <DefinedCommandModule>{
|
||||
name: mod?.name ?? Files.fmtFileName(basename(absPath)),
|
||||
description: mod?.description ?? '...',
|
||||
...mod,
|
||||
},
|
||||
};
|
||||
}),
|
||||
);
|
||||
const processPlugins$ = processCommandFiles$.pipe(
|
||||
concatMap(payload => {
|
||||
const cmdPluginRes = processCommandPlugins(wrapper, payload);
|
||||
return of({ mod: payload.mod, cmdPluginRes });
|
||||
}),
|
||||
);
|
||||
|
||||
(
|
||||
concat(ready$, processPlugins$) as Observable<{
|
||||
mod: DefinedCommandModule;
|
||||
cmdPluginRes: {
|
||||
execute: Awaitable<Result<void, void>>;
|
||||
type: PluginType.Command;
|
||||
name: string;
|
||||
description: string;
|
||||
}[];
|
||||
}>
|
||||
)
|
||||
.pipe(
|
||||
concatMap(pl => {
|
||||
return from(
|
||||
//refactor, this allocates too many objects
|
||||
Promise.all(
|
||||
pl.cmdPluginRes.map(async e => ({ ...e, execute: await e.execute })),
|
||||
),
|
||||
).pipe(map(res => ({ ...pl, cmdPluginsRes: res })));
|
||||
}),
|
||||
)
|
||||
.subscribe(({ mod, cmdPluginsRes }) => {
|
||||
const loadedPluginsCorrectly = cmdPluginsRes.every(({ execute }) => execute.ok);
|
||||
if (loadedPluginsCorrectly) {
|
||||
const res = registerModule(mod);
|
||||
if (res.err) {
|
||||
throw Error(SernError.NonValidModuleType);
|
||||
}
|
||||
wrapper.sernEmitter?.emit('module.register', { type: 'success', module: mod });
|
||||
} else {
|
||||
wrapper.sernEmitter?.emit('module.register', {
|
||||
type: 'failure',
|
||||
module: mod,
|
||||
reason: SernError.PluginFailure,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registerModule(mod: DefinedCommandModule): Result<void, void> {
|
||||
const name = mod.name;
|
||||
return match<DefinedCommandModule>(mod)
|
||||
.with({ type: CommandType.Text }, mod => {
|
||||
mod.alias?.forEach(a => Files.TextCommands.aliases.set(a, mod));
|
||||
Files.TextCommands.text.set(name, mod);
|
||||
return Ok.EMPTY;
|
||||
})
|
||||
.with({ type: CommandType.Slash }, mod => {
|
||||
Files.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod);
|
||||
return Ok.EMPTY;
|
||||
})
|
||||
.with({ type: CommandType.Both }, mod => {
|
||||
Files.BothCommands.set(name, mod);
|
||||
mod.alias?.forEach(a => Files.TextCommands.aliases.set(a, mod));
|
||||
return Ok.EMPTY;
|
||||
})
|
||||
.with({ type: CommandType.MenuUser }, mod => {
|
||||
Files.ApplicationCommands[ApplicationCommandType.User].set(name, mod);
|
||||
return Ok.EMPTY;
|
||||
})
|
||||
.with({ type: CommandType.MenuMsg }, mod => {
|
||||
Files.ApplicationCommands[ApplicationCommandType.Message].set(name, mod);
|
||||
return Ok.EMPTY;
|
||||
})
|
||||
.with({ type: CommandType.Button }, mod => {
|
||||
Files.ApplicationCommands[ComponentType.Button].set(name, mod);
|
||||
return Ok.EMPTY;
|
||||
})
|
||||
.with({ type: CommandType.MenuSelect }, mod => {
|
||||
Files.MessageCompCommands[ComponentType.SelectMenu].set(name, mod);
|
||||
return Ok.EMPTY;
|
||||
})
|
||||
.with({ type: CommandType.Modal }, mod => {
|
||||
Files.ModalSubmitCommands.set(name, mod);
|
||||
return Ok.EMPTY;
|
||||
})
|
||||
.otherwise(() => Err.EMPTY);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user