Compare commits

...

26 Commits

Author SHA1 Message Date
github-actions[bot]
d983f95906 chore(main): release 2.6.2 (#281)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-04-15 12:42:59 -05:00
Jacob Nguyen
c1f690633c chore: release 2.6.2
Release-As: 2.6.2
2023-04-15 12:40:17 -05:00
Jacob Nguyen
8544d301ef bump version 2023-04-15 12:19:12 -05:00
Jacob Nguyen
52bcba9cfc docs: add deprecation warning 2023-04-15 12:16:35 -05:00
xxDeveloper
21febd2c90 chore: Update SECURITY.md (#276)
Semantic security file

Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-04-11 22:46:02 -05:00
xxDeveloper
11daebb30a chore: Update LICENSE (#275)
We're in 2023
We're sern, not Sern

Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-04-11 22:45:45 -05:00
github-actions[bot]
b817f98c10 style: pretty please (#277)
Co-authored-by: EvolutionX-10 <EvolutionX-10@users.noreply.github.com>
2023-04-11 22:45:30 -05:00
Evo
563f583318 chore: switch to yarn (#273)
* chore: switch to yarn

* chore: pointless limitation

permalink: http://whatthecommit.com/468a491808723d12de48b079d9092b44

* chore: i can't believe it took so long to fix this.

permalink: http://whatthecommit.com/b298fe6d3375ab953abfdb0f1f737826
2023-04-11 12:45:16 -05:00
EvolutionX
e4c7bfe686 chore: ok work pls 2023-04-11 22:32:31 +05:30
EvolutionX
69fa4908c3 chore: refresh lockfile 2023-04-11 22:09:32 +05:30
renovate[bot]
4fa28d605f chore(deps): lock file maintenance (#245)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-11 10:32:46 -05:00
Jacob Nguyen
079b554f8b Update continuous-integration.yml 2023-04-11 10:31:15 -05:00
Jacob Nguyen
dec56335b9 Update codeql-analysis.yml 2023-04-11 10:30:41 -05:00
Jacob Nguyen
50be972d4f Update continuous-integration.yml 2023-04-11 10:29:06 -05:00
Jacob Nguyen
507d183970 Update codeql-analysis.yml 2023-04-11 10:28:29 -05:00
renovate[bot]
90edd4f91e chore(deps): update dependency eslint to v8.38.0 (#180)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-11 10:24:32 -05:00
renovate[bot]
5f11142599 chore(deps): update dependency @typescript-eslint/parser to v5.58.0 (#250)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-04-11 10:22:27 -05:00
renovate[bot]
7a635f9978 chore(deps): update actions/checkout digest to 8f4b7f8 (#261)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-04-11 10:21:59 -05:00
renovate[bot]
a17aeac558 chore(deps): update pnpm to v7.32.0 (#262)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
2023-04-11 10:21:39 -05:00
renovate[bot]
af6ebed348 chore(deps): update dependency @typescript-eslint/eslint-plugin to v5.58.0 (#249)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-11 10:18:46 -05:00
renovate[bot]
2f96b7634d chore(deps): update dependency prettier to v2.8.7 (#263)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-11 10:18:17 -05:00
EvolutionX
97741faa69 chore: refresh lockfile 2023-04-11 16:02:13 +05:30
Jacob Nguyen
94070d99e8 refactor/decoupling (#265)
* fix npm script for workflows

* filter lazy modules

* lift inline function for readability

* perf: use one instance of operator instead of creating instances

* chore: move fmt closer to call site

* refactor: inline function lifting and readability

* add import payload type

* refactor: remove redundant pipe for single function operators

* refactor: clearer naming for resultResolver

* refactor: no unused variable warning for updateAlive

* style: pretty

* refactor: remove redundant getter

* style: pretty

* fix: typescript needs explicit definition for defineAllFields

* add LazyPaths map

* chore: update tsup and typescript

* chore: revert lazy module work and work on decoupling core

* fix npm script for workflows

* chore: fix typings

* refactor: inline function `defineAllFields`

* docs: add @since annotation

* style: prettier

* docs: add since annotations

* fix: typings

* chore: update dependencies

* chore: remove unused import

* style: pretty

* merge on home pc

* refactor: use dependencies less

---------

Co-authored-by: jacoobes <jacobnguyend@gmail.com>
2023-04-10 22:12:26 -05:00
Jacob Nguyen
473be775f0 Update README.md 2023-03-29 15:12:26 -05:00
Neo
36af102251 docs: removed ALMA (#264)
Not working on it anymore, also not running it.
2023-03-29 12:55:16 -05:00
github-actions[bot]
cee740ea3f style: pretty please (#260)
Co-authored-by: renovate[bot] <renovate[bot]@users.noreply.github.com>
2023-03-17 17:01:20 -05:00
37 changed files with 4406 additions and 2237 deletions

36
.github/SECURITY.md vendored
View File

@@ -6,13 +6,43 @@ Project is currently under heavy development but you can try out our [npm packag
| Version | Supported | | 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 ## 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. 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.

View File

@@ -3,8 +3,10 @@ name: "CodeQL"
on: on:
push: push:
branches: [ main ] branches: [ main ]
paths: ["src/**/*"]
pull_request: pull_request:
branches: [ main ] branches: [ main ]
paths: ["src/**/*"]
schedule: schedule:
- cron: '37 20 * * 4' - cron: '37 20 * * 4'

View File

@@ -3,13 +3,14 @@ name: Continuous Integration
on: on:
# Trigger the workflow on push or pull request or custom # Trigger the workflow on push or pull request or custom
push: push:
branches: branches: [main]
main
paths: paths:
- '**.ts' - '*.ts'
pull_request_target: pull_request_target:
branches: branches:
main main
paths:
- '*ts'
workflow_dispatch: workflow_dispatch:
jobs: jobs:
@@ -19,7 +20,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3 uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3 uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3
@@ -27,14 +28,14 @@ jobs:
node-version: 17 node-version: 17
- name: Install pnpm - name: Install pnpm
run: npm i -g pnpm run: npm i -g yarn
# Prettier must be in `package.json` # Prettier must be in `package.json`
- name: Install Node.js dependencies - name: Install Node.js dependencies
run: pnpm i run: yarn --immutable
- name: Run Prettier - name: Run Prettier
run: pnpm run pretty run: yarn pretty
- name: Create Pull Request - name: Create Pull Request
id: cpr id: cpr

View File

@@ -10,13 +10,8 @@ jobs:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 17 node-version: 17
- uses: pnpm/action-setup@v2 - run: yarn --immutable
with: - run: yarn build:prod
run_install: |
- recursive: true
args: [--strict-peer-dependencies]
- run: pnpm install
- run: pnpm build:prod
- uses: JS-DevTools/npm-publish@v1 - uses: JS-DevTools/npm-publish@v1
with: with:
token: ${{ secrets.NPM_TOKEN }} token: ${{ secrets.NPM_TOKEN }}

4
.gitignore vendored
View File

@@ -87,3 +87,7 @@ dist
# IntelliJ IDEA Config file # IntelliJ IDEA Config file
.idea/ .idea/
# Yarn files
.yarn/install-state.gz
.yarn/build-state.yml

873
.yarn/releases/yarn-3.5.0.cjs vendored Normal file

File diff suppressed because one or more lines are too long

5
.yarnrc.yml Normal file
View File

@@ -0,0 +1,5 @@
enableGlobalCache: true
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.5.0.cjs

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
## [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) ## [2.6.1](https://github.com/sern-handler/handler/compare/v2.6.0...v2.6.1) (2023-03-17)

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
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.

View File

@@ -113,7 +113,7 @@ client.login("YOUR_BOT_TOKEN_HERE");
- [Vinci](https://github.com/SrIzan10/vinci), the bot for Mara Turing. - [Vinci](https://github.com/SrIzan10/vinci), the bot for Mara Turing.
- [Bask](https://github.com/baskbotml/bask), Listen your favorite artists on Discord. - [Bask](https://github.com/baskbotml/bask), Listen your favorite artists on Discord.
- [ava](https://github.com/SrIzan10/ava), A discord bot that plays KNGI and Gensokyo Radio. - [ava](https://github.com/SrIzan10/ava), A discord bot that plays KNGI and Gensokyo Radio.
- [ALMA (WIP)](https://github.com/Benzo-Fury/ALMA), Using AI to unleash the power in your server. - [Murayama](https://github.com/murayamabot/murayama), :pepega:
- [Protector (WIP)](https://github.com/needhamgary/Protector), Just a simple bot to help enhance a private minecraft server. - [Protector (WIP)](https://github.com/needhamgary/Protector), Just a simple bot to help enhance a private minecraft server.
## 💻 CLI ## 💻 CLI

View File

@@ -1,7 +1,7 @@
{ {
"name": "@sern/handler", "name": "@sern/handler",
"packageManager": "pnpm@7.28.0", "packageManager": "yarn@3.5.0",
"version": "2.6.1", "version": "2.6.2",
"description": "A complete, customizable, typesafe, & reactive framework for discord bots.", "description": "A complete, customizable, typesafe, & reactive framework for discord bots.",
"main": "dist/cjs/index.cjs", "main": "dist/cjs/index.cjs",
"module": "dist/esm/index.mjs", "module": "dist/esm/index.mjs",
@@ -19,7 +19,7 @@
"format": "eslint src/**/*.ts --fix", "format": "eslint src/**/*.ts --fix",
"build:dev": "tsup && tsup --dts-only --outDir dist", "build:dev": "tsup && tsup --dts-only --outDir dist",
"build:prod": "tsup --minify && tsup --dts-only --outDir dist", "build:prod": "tsup --minify && tsup --dts-only --outDir dist",
"publish": "npm run build:prod && npm publish", "publish": "npm run build:prod",
"pretty": "prettier --write ." "pretty": "prettier --write ."
}, },
"keywords": [ "keywords": [
@@ -36,17 +36,19 @@
"dependencies": { "dependencies": {
"iti": "^0.6.0", "iti": "^0.6.0",
"rxjs": "^7.8.0", "rxjs": "^7.8.0",
"ts-results-es": "^3.5.0" "ts-results-es": "^3.6.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "5.54.0", "@types/node": "^18.15.11",
"@typescript-eslint/parser": "5.54.0", "@typescript-eslint/eslint-plugin": "5.58.0",
"discord.js": "^14.8.0", "@typescript-eslint/parser": "5.58.0",
"discord.js": "^14.9.0",
"esbuild": "^0.15.2",
"esbuild-ifdef": "^0.2.0", "esbuild-ifdef": "^0.2.0",
"eslint": "8.30.0", "eslint": "8.38.0",
"prettier": "2.8.4", "prettier": "2.8.7",
"typescript": "4.9.5", "tsup": "^6.7.0",
"tsup": "^6.6.3" "typescript": "5.0.2"
}, },
"repository": { "repository": {
"type": "git", "type": "git",

1987
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,9 @@
import type { Observable } from 'rxjs'; import type { Observable } from 'rxjs';
import type { Logging } from './logging'; import type { Logging } from './logging';
import util from 'util'; import util from 'util';
/**
* @since 2.0.0
*/
export interface ErrorHandling { export interface ErrorHandling {
/** /**
* Number of times the process should throw an error until crashing and exiting * Number of times the process should throw an error until crashing and exiting
@@ -20,13 +22,15 @@ export interface ErrorHandling {
*/ */
updateAlive(error: Error): void; updateAlive(error: Error): void;
} }
/**
* @since 2.0.0
*/
export class DefaultErrorHandling implements ErrorHandling { export class DefaultErrorHandling implements ErrorHandling {
keepAlive = 5; keepAlive = 5;
crash(error: Error): never { crash(error: Error): never {
throw error; throw error;
} }
updateAlive(e: Error) { updateAlive(_: Error) {
this.keepAlive--; this.keepAlive--;
} }
} }

View File

@@ -1,12 +1,16 @@
import type { LogPayload } from '../../types/handler'; import type { LogPayload } from '../../types/handler';
/**
* @since 2.0.0
*/
export interface Logging<T = unknown> { export interface Logging<T = unknown> {
error(payload: LogPayload<T>): void; error(payload: LogPayload<T>): void;
warning(payload: LogPayload<T>): void; warning(payload: LogPayload<T>): void;
info(payload: LogPayload<T>): void; info(payload: LogPayload<T>): void;
debug(payload: LogPayload<T>): void; debug(payload: LogPayload<T>): void;
} }
/**
* @since 2.0.0
*/
export class DefaultLogging implements Logging { export class DefaultLogging implements Logging {
private date = () => new Date(); private date = () => new Date();
debug(payload: LogPayload): void { debug(payload: LogPayload): void {

View File

@@ -1,14 +1,18 @@
import type { CommandModuleDefs } from '../../types/module'; import type { CommandModuleDefs } from '../../types/module';
import type { CommandType, ModuleStore } from '../structures'; import type { CommandType, ModuleStore } from '../structures';
import type { Processed } from '../../types/handler'; import type { Processed } from '../../types/handler';
/**
* @since 2.0.0
*/
export interface ModuleManager { export interface ModuleManager {
get<T extends CommandType>( get<T extends CommandType>(
strat: (ms: ModuleStore) => Processed<CommandModuleDefs[T]> | undefined, strat: (ms: ModuleStore) => Processed<CommandModuleDefs[T]> | undefined,
): Processed<CommandModuleDefs[T]> | undefined; ): Processed<CommandModuleDefs[T]> | undefined;
set(strat: (ms: ModuleStore) => void): void; set(strat: (ms: ModuleStore) => void): void;
} }
/**
* @since 2.0.0
*/
export class DefaultModuleManager implements ModuleManager { export class DefaultModuleManager implements ModuleManager {
constructor(private moduleStore: ModuleStore) {} constructor(private moduleStore: ModuleStore) {}
get<T extends CommandType>( get<T extends CommandType>(

View File

@@ -18,11 +18,13 @@ type NotFunction =
export function single<T extends NotFunction>(cb: T): () => T; export function single<T extends NotFunction>(cb: T): () => T;
/** /**
* New signature * New signature
* @since 2.0.0
* @param cb * @param cb
*/ */
export function single<T extends () => unknown>(cb: T): T; export function single<T extends () => unknown>(cb: T): T;
/** /**
* @__PURE__ * @__PURE__
* @since 2.0.0.
* Please note that on intellij, the deprecation is for all signatures, which is unintended behavior (and * Please note that on intellij, the deprecation is for all signatures, which is unintended behavior (and
* very annoying). * very annoying).
* For future versions, ensure that single is being passed as a **callback!!** * For future versions, ensure that single is being passed as a **callback!!**
@@ -41,6 +43,7 @@ export function transient<T extends NotFunction>(cb: T): () => () => T;
export function transient<T extends () => () => unknown>(cb: T): T; export function transient<T extends () => () => unknown>(cb: T): T;
/** /**
* @__PURE__ * @__PURE__
* @since 2.0.0
* Following iti's singleton and transient implementation, * Following iti's singleton and transient implementation,
* use transient if you want a new dependency every time your container getter is called * use transient if you want a new dependency every time your container getter is called
* @param cb * @param cb

View File

@@ -6,7 +6,7 @@ import type { BothCommand, CommandModule, Module, SlashCommand } from '../../../
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import * as assert from 'assert'; import * as assert from 'assert';
import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs'; import { concatMap, from, fromEvent, map, OperatorFunction, pipe } from 'rxjs';
import { callPlugin } from '../operators'; import { arrayifySource, callPlugin } from '../operators';
import { createResultResolver } from '../observableHandling'; import { createResultResolver } from '../observableHandling';
export function dispatchCommand(module: Processed<CommandModule>, createArgs: () => unknown[]) { export function dispatchCommand(module: Processed<CommandModule>, createArgs: () => unknown[]) {
@@ -17,6 +17,21 @@ export function dispatchCommand(module: Processed<CommandModule>, createArgs: ()
}; };
} }
function intoPayload(module: Processed<Module>) {
return pipe(
arrayifySource,
map(args => ({ module, args })),
);
}
const createResult = createResultResolver<
Processed<Module>,
{ module: Processed<Module>; args: unknown[] },
unknown[]
>({
createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)),
onNext: ({ args }) => args,
});
/** /**
* Creates an observable from { source } * Creates an observable from { source }
* @param module * @param module
@@ -24,27 +39,15 @@ export function dispatchCommand(module: Processed<CommandModule>, createArgs: ()
*/ */
export function eventDispatcher(module: Processed<Module>, source: unknown) { export function eventDispatcher(module: Processed<Module>, source: unknown) {
assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`); assert.ok(source instanceof EventEmitter, `${source} is not an EventEmitter`);
/**
* Sometimes fromEvent emits a single parameter, which is not an Array. This const execute: OperatorFunction<unknown[], unknown> = concatMap(async args =>
* operator function flattens events into an array module.execute(...args),
* @param src
*/
const arrayify = pipe(
map(event => (Array.isArray(event) ? (event as unknown[]) : [event])),
map(args => ({ module, args })),
); );
const createResult = createResultResolver< return fromEvent(source, module.name).pipe(
Processed<Module>, intoPayload(module),
{ module: Processed<Module>; args: unknown[] }, concatMap(createResult),
unknown[] execute,
>({
createStream: ({ module, args }) => from(module.onEvent).pipe(callPlugin(args)),
onSuccess: ({ args }) => args,
});
const execute: OperatorFunction<unknown[], unknown> = pipe(
concatMap(async args => module.execute(...args)),
); );
return fromEvent(source, module.name).pipe(arrayify, concatMap(createResult), execute);
} }
export function dispatchAutocomplete( export function dispatchAutocomplete(

View File

@@ -1,4 +1,4 @@
import type { Interaction } from 'discord.js'; import { Interaction } from 'discord.js';
import { import {
catchError, catchError,
concatMap, concatMap,
@@ -32,9 +32,10 @@ function makeInteractionProcessor(
return pipe( return pipe(
concatMap(event => { concatMap(event => {
if (event.isMessageComponent()) { if (event.isMessageComponent()) {
const module = get(ms => const customId = event.customId;
ms.InteractionHandlers[event.componentType].get(event.customId), const module = get(ms => {
); return ms.InteractionHandlers[event.componentType].get(customId);
});
return of({ module, event }); return of({ module, event });
} else if (event.isCommand() || event.isAutocomplete()) { } else if (event.isCommand() || event.isAutocomplete()) {
const commandName = event.commandName; const commandName = event.commandName;
@@ -95,21 +96,23 @@ function createDispatcher({
event: Interaction; event: Interaction;
module: Processed<CommandModule>; module: Processed<CommandModule>;
}) { }) {
switch(module.type) { switch (module.type) {
case CommandType.Text: case CommandType.Text:
throw Error(SernError.MismatchEvent); throw Error(SernError.MismatchEvent);
case CommandType.Slash: case CommandType.Both : { case CommandType.Slash:
if(event.isAutocomplete()) { case CommandType.Both: {
if (event.isAutocomplete()) {
/** /**
* Autocomplete is a special case that * Autocomplete is a special case that
* must be handled separately, since it's * must be handled separately, since it's
* too different from regular command modules * too different from regular command modules
*/ */
return dispatchAutocomplete(module, event); return dispatchAutocomplete(module, event);
} else { } else {
return dispatchCommand(module, contextArgs(event)); return dispatchCommand(module, contextArgs(event));
} }
} }
default : return dispatchCommand(module, interactionArg(event)); default:
return dispatchCommand(module, interactionArg(event));
} }
} }

View File

@@ -1,8 +1,7 @@
import { catchError, concatMap, EMPTY, finalize, fromEvent, map, of, pipe } from 'rxjs'; import { catchError, concatMap, EMPTY, finalize, fromEvent, map, Observable, of, pipe } from 'rxjs';
import { type ModuleStore, SernError } from '../structures'; import { type ModuleStore, SernError } from '../structures';
import type { Message } from 'discord.js'; import type { Message } from 'discord.js';
import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling'; import { executeModule, ignoreNonBot, makeModuleExecutor } from './observableHandling';
import { fmt } from '../utilities/messageHelpers';
import type { CommandModule, TextCommand } from '../../types/module'; import type { CommandModule, TextCommand } from '../../types/module';
import { ErrorHandling, handleError } from '../contracts/errorHandling'; import { ErrorHandling, handleError } from '../contracts/errorHandling';
import { contextArgs, dispatchCommand } from './dispatchers'; import { contextArgs, dispatchCommand } from './dispatchers';
@@ -12,6 +11,20 @@ import { useContainerRaw } from '../dependencies';
import type { Logging, ModuleManager } from '../contracts'; import type { Logging, ModuleManager } from '../contracts';
import type { EventEmitter } from 'node:events'; import type { EventEmitter } from 'node:events';
/**
* 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, '!'));
* // [ 'ping' ]
*/
export function fmt(msg: string, prefix: string): string[] {
return msg.slice(prefix.length).trim().split(/\s+/g);
}
/** /**
* An operator function that processes a message to fetch a command module and prepares context payload. * An operator function that processes a message to fetch a command module and prepares context payload.
* @param defaultPrefix * @param defaultPrefix
@@ -58,7 +71,7 @@ export function makeMessageCreate(
const get = (cb: (ms: ModuleStore) => Processed<CommandModule> | undefined) => { const get = (cb: (ms: ModuleStore) => Processed<CommandModule> | undefined) => {
return modules.get(cb); return modules.get(cb);
}; };
const messageStream$ = fromEvent<Message>(client, 'messageCreate'); const messageStream$ = fromEvent(client, 'messageCreate') as Observable<Message>;
const messageProcessor = createMessageProcessor(defaultPrefix, get); const messageProcessor = createMessageProcessor(defaultPrefix, get);
return messageStream$ return messageStream$
.pipe( .pipe(

View File

@@ -1,32 +1,25 @@
import type { Awaitable, Message } from 'discord.js'; import type { Awaitable, Message } from 'discord.js';
import { concatMap, EMPTY, from, Observable, of, pipe, tap, throwError } from 'rxjs'; import { concatMap, EMPTY, filter, from, Observable, of, tap, throwError } from 'rxjs';
import { Result } from 'ts-results-es'; import { Result } from 'ts-results-es';
import type { CommandModule, EventModule, Module } from '../../types/module'; import type { CommandModule, EventModule, Module } from '../../types/module';
import SernEmitter from '../sernEmitter'; import SernEmitter from '../sernEmitter';
import { callPlugin, everyPluginOk, filterMapTo } from './operators'; import { callPlugin, everyPluginOk, filterMapTo } from './operators';
import type { Processed } from '../../types/handler'; import type { ImportPayload, Processed } from '../../types/handler';
import type { ControlPlugin, VoidResult } from '../../types/plugin'; import type { ControlPlugin, VoidResult } from '../../types/plugin';
function hasPrefix(prefix: string, content: string) {
const prefixInContent = content.slice(0, prefix.length);
return prefixInContent.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0;
}
/** /**
* Ignores messages from any person / bot except itself * Ignores messages from any person / bot except itself
* @param prefix * @param prefix
*/ */
export function ignoreNonBot<T extends Message>(prefix: string) { export function ignoreNonBot(prefix: string) {
return (src: Observable<T>) => const messageFromHumanAndHasPrefix = ({ author, content }: Message) =>
new Observable<T>(subscriber => { !author.bot && hasPrefix(prefix, content);
return src.subscribe({ return filter(messageFromHumanAndHasPrefix);
next(m) {
const messageFromHumanAndHasPrefix =
!m.author.bot &&
m.content
.slice(0, prefix.length)
.localeCompare(prefix, undefined, { sensitivity: 'accent' }) === 0;
if (messageFromHumanAndHasPrefix) {
subscriber.next(m);
}
},
});
});
} }
/** /**
@@ -75,8 +68,8 @@ export function createResultResolver<
Args extends { module: T; [key: string]: unknown }, Args extends { module: T; [key: string]: unknown },
Output, Output,
>(config: { >(config: {
onFailure?: (module: T) => unknown; onStop?: (module: T) => unknown;
onSuccess: (args: Args) => Output; onNext: (args: Args) => Output;
createStream: (args: Args) => Observable<VoidResult>; createStream: (args: Args) => Observable<VoidResult>;
}) { }) {
return (args: Args) => { return (args: Args) => {
@@ -84,49 +77,45 @@ export function createResultResolver<
return task$.pipe( return task$.pipe(
tap(result => { tap(result => {
if (result.err) { if (result.err) {
config.onFailure?.(args.module); config.onStop?.(args.module);
} }
}), }),
everyPluginOk(), everyPluginOk,
filterMapTo(() => config.onSuccess(args)), filterMapTo(() => config.onNext(args)),
); );
}; };
} }
/** /**
* Calls a module's init plugins and checks for Err. If so, call { onFailure } and * Calls a module's init plugins and checks for Err. If so, call { onStop } and
* ignore the module * ignore the module
*/ */
export function callInitPlugins< export function callInitPlugins<
T extends Processed<CommandModule | EventModule>, T extends Processed<CommandModule | EventModule>,
Args extends { module: T; absPath: string }, Args extends ImportPayload<T>,
>(config: { onFailure?: (module: T) => unknown; onSuccess: (module: Args) => T }) { >(config: { onStop?: (module: T) => unknown; onNext: (module: Args) => T }) {
return pipe( return concatMap(
concatMap( createResultResolver({
createResultResolver({ createStream: args => from(args.module.plugins).pipe(callPlugin(args)),
createStream: args => from(args.module.plugins).pipe(callPlugin(args)), ...config,
...config, }),
}),
),
); );
} }
/** /**
* Creates an executable task ( execute the command ) if all control plugins are successful * Creates an executable task ( execute the command ) if all control plugins are successful
* @param onFailure emits a failure response to the SernEmitter * @param onStop emits a failure response to the SernEmitter
*/ */
export function makeModuleExecutor< export function makeModuleExecutor<
M extends Processed<Module>, M extends Processed<Module>,
Args extends { module: M; args: unknown[] }, Args extends { module: M; args: unknown[] },
>(onFailure: (m: M) => unknown) { >(onStop: (m: M) => unknown) {
const onSuccess = ({ args, module }: Args) => ({ task: () => module.execute(...args), module }); const onNext = ({ args, module }: Args) => ({ task: () => module.execute(...args), module });
return pipe( return concatMap(
concatMap( createResultResolver({
createResultResolver({ onStop,
onFailure, createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)),
createStream: ({ args, module }) => from(module.onEvent).pipe(callPlugin(args)), onNext,
onSuccess, }),
}),
),
); );
} }

View File

@@ -10,15 +10,14 @@ import { nameOrFilename } from '../../utilities/functions';
import type { PluginResult, VoidResult } from '../../../types/plugin'; import type { PluginResult, VoidResult } from '../../../types/plugin';
import { guayin } from '../../plugins'; import { guayin } from '../../plugins';
import { controller } from '../../sern'; import { controller } from '../../sern';
import { SernError } from '../../structures';
import { Result } from 'ts-results-es'; import { Result } from 'ts-results-es';
import { ImportPayload, Processed } from '../../../types/handler';
/** /**
* if {src} is true, mapTo V, else ignore * if {src} is true, mapTo V, else ignore
* @param item * @param item
*/ */
export function filterMapTo<V>(item: () => V): OperatorFunction<boolean, V> { export function filterMapTo<V>(item: () => V): OperatorFunction<boolean, V> {
return pipe(concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY))); return concatMap(shouldKeep => (shouldKeep ? of(item()) : EMPTY));
} }
/** /**
@@ -31,35 +30,31 @@ export function callPlugin(args: unknown): OperatorFunction<
}, },
VoidResult VoidResult
> { > {
return pipe( return concatMap(async plugin => {
concatMap(async plugin => { const isNewPlugin = Reflect.has(plugin, guayin);
const isNewPlugin = Reflect.has(plugin, guayin); if (isNewPlugin) {
if (isNewPlugin) { if (Array.isArray(args)) {
if (Array.isArray(args)) { return plugin.execute(...args);
return plugin.execute(...args);
}
return plugin.execute(args);
} else {
return plugin.execute(args, controller);
} }
}), return plugin.execute(args);
); } else {
return plugin.execute(args, controller);
}
});
} }
/** export const arrayifySource = map(src => (Array.isArray(src) ? (src as unknown[]) : [src]));
* operator function that fill the defaults for a module
*/ export const fillDefaults = <T extends AnyModule>({ module, absPath }: ImportPayload<T>) => {
export function defineAllFields<T extends AnyModule>() { return {
const fillFields = ({ module, absPath }: { module: T; absPath: string }) => ({
absPath, absPath,
module: { module: {
name: nameOrFilename(module.name, absPath), name: nameOrFilename(module?.name, absPath),
description: module.description ?? '...', description: module?.description ?? '...',
...module, ...module,
}, },
}); };
return pipe(map(fillFields)); };
}
/** /**
* If the current value in Result stream is an error, calls callback. * If the current value in Result stream is an error, calls callback.
@@ -67,27 +62,21 @@ export function defineAllFields<T extends AnyModule>() {
* @param cb * @param cb
* @returns Observable<{ module: T; absPath: string }> * @returns Observable<{ module: T; absPath: string }>
*/ */
export function errTap<T extends AnyModule>( export function errTap<Ok, Err>(cb: (err: Err) => void): OperatorFunction<Result<Ok, Err>, Ok> {
cb: (err: SernError) => void return concatMap(result => {
): OperatorFunction<Result<{ module: T; absPath: string}, SernError>, { module: T; absPath: string }> { if (result.ok) {
return pipe(
concatMap(result => {
if(result.ok) {
return of(result.val); return of(result.val);
} else { } else {
cb(result.val); cb(result.val as Err);
return EMPTY; return EMPTY;
} }
}) });
);
} }
/** /**
* Checks if the stream of results is all ok. * Checks if the stream of results is all ok.
*/ */
export function everyPluginOk(): OperatorFunction<VoidResult, boolean> { export const everyPluginOk: OperatorFunction<VoidResult, boolean> = pipe(
return pipe( every(result => result.ok),
every(result => result.ok), defaultIfEmpty(true),
defaultIfEmpty(true), );
);
}

View File

@@ -1,4 +1,4 @@
import { fromEvent, pipe, switchMap, take } from 'rxjs'; import { fromEvent, map, pipe, switchMap, take } from 'rxjs';
import * as Files from '../module-loading/readFile'; import * as Files from '../module-loading/readFile';
import { callInitPlugins } from './observableHandling'; import { callInitPlugins } from './observableHandling';
import { CommandType, type ModuleStore, SernError } from '../structures'; import { CommandType, type ModuleStore, SernError } from '../structures';
@@ -8,21 +8,17 @@ import type { CommandModule } from '../../types/module';
import type { Processed } from '../../types/handler'; import type { Processed } from '../../types/handler';
import type { ErrorHandling, Logging, ModuleManager } from '../contracts'; import type { ErrorHandling, Logging, ModuleManager } from '../contracts';
import { err, ok } from '../utilities/functions'; import { err, ok } from '../utilities/functions';
import { defineAllFields, errTap } from './operators'; import { errTap, fillDefaults } from './operators';
import SernEmitter from '../sernEmitter'; import SernEmitter from '../sernEmitter';
import type { EventEmitter } from 'node:events'; import type { EventEmitter } from 'node:events';
function buildCommandModules(commandDir: string, sernEmitter: SernEmitter) {
function buildCommandModules(
commandDir: string,
sernEmitter: SernEmitter
) {
return pipe( return pipe(
switchMap(() => Files.buildData<CommandModule>(commandDir)), switchMap(() => Files.buildModuleStream<CommandModule>(commandDir)),
errTap(error => { errTap(error => {
sernEmitter.emit('module.register', SernEmitter.failure(undefined, error)); sernEmitter.emit('module.register', SernEmitter.failure(undefined, error));
}), }),
defineAllFields(), map(fillDefaults),
); );
} }
export function makeReadyEvent( export function makeReadyEvent(
@@ -40,13 +36,13 @@ export function makeReadyEvent(
.pipe( .pipe(
buildCommandModules(commandDir, sEmitter), buildCommandModules(commandDir, sEmitter),
callInitPlugins({ callInitPlugins({
onFailure: module => { onStop: module => {
sEmitter.emit( sEmitter.emit(
'module.register', 'module.register',
SernEmitter.failure(module, SernError.PluginFailure), SernEmitter.failure(module, SernError.PluginFailure),
); );
}, },
onSuccess: ({ module }) => { onNext: ({ module }) => {
sEmitter.emit('module.register', SernEmitter.success(module)); sEmitter.emit('module.register', SernEmitter.success(module));
return module; return module;
}, },
@@ -69,13 +65,15 @@ function registerModule<T extends Processed<CommandModule>>(
const set = Result.wrap(() => manager.set(cb)); const set = Result.wrap(() => manager.set(cb));
return set.ok ? ok() : err(); return set.ok ? ok() : err();
}; };
switch(mod.type) { switch (mod.type) {
case CommandType.Text: { case CommandType.Text: {
mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod))); mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod)));
return insert(ms => ms.TextCommands.set(name, mod)); return insert(ms => ms.TextCommands.set(name, mod));
} }
case CommandType.Slash: case CommandType.Slash:
return insert(ms => ms.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod)); return insert(ms =>
ms.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod),
);
case CommandType.Both: { case CommandType.Both: {
mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod))); mod.alias?.forEach(a => insert(ms => ms.TextCommands.set(a, mod)));
return insert(ms => ms.BothCommands.set(name, mod)); return insert(ms => ms.BothCommands.set(name, mod));
@@ -83,13 +81,17 @@ function registerModule<T extends Processed<CommandModule>>(
case CommandType.CtxUser: case CommandType.CtxUser:
return insert(ms => ms.ApplicationCommands[ApplicationCommandType.User].set(name, mod)); return insert(ms => ms.ApplicationCommands[ApplicationCommandType.User].set(name, mod));
case CommandType.CtxMsg: case CommandType.CtxMsg:
return insert(ms => ms.ApplicationCommands[ApplicationCommandType.Message].set(name, mod)); return insert(ms =>
ms.ApplicationCommands[ApplicationCommandType.Message].set(name, mod),
);
case CommandType.Button: case CommandType.Button:
return insert(ms => ms.InteractionHandlers[ComponentType.Button].set(name, mod)); return insert(ms => ms.InteractionHandlers[ComponentType.Button].set(name, mod));
case CommandType.StringSelect: case CommandType.StringSelect:
return insert(ms => ms.InteractionHandlers[ComponentType.StringSelect].set(name, mod)); return insert(ms => ms.InteractionHandlers[ComponentType.StringSelect].set(name, mod));
case CommandType.MentionableSelect: case CommandType.MentionableSelect:
return insert(ms => ms.InteractionHandlers[ComponentType.MentionableSelect].set(name, mod)); return insert(ms =>
ms.InteractionHandlers[ComponentType.MentionableSelect].set(name, mod),
);
case CommandType.UserSelect: case CommandType.UserSelect:
return insert(ms => ms.InteractionHandlers[ComponentType.UserSelect].set(name, mod)); return insert(ms => ms.InteractionHandlers[ComponentType.UserSelect].set(name, mod));
case CommandType.ChannelSelect: case CommandType.ChannelSelect:
@@ -98,6 +100,7 @@ function registerModule<T extends Processed<CommandModule>>(
return insert(ms => ms.InteractionHandlers[ComponentType.RoleSelect].set(name, mod)); return insert(ms => ms.InteractionHandlers[ComponentType.RoleSelect].set(name, mod));
case CommandType.Modal: case CommandType.Modal:
return insert(ms => ms.ModalSubmit.set(name, mod)); return insert(ms => ms.ModalSubmit.set(name, mod));
default: return err(); default:
return err();
} }
} }

View File

@@ -1,5 +1,5 @@
import { catchError, finalize, map, mergeAll } from 'rxjs'; import { catchError, finalize, map, mergeAll } from 'rxjs';
import { buildData } from '../module-loading/readFile'; import * as Files from '../module-loading/readFile';
import type { Dependencies, Processed } from '../../types/handler'; import type { Dependencies, Processed } from '../../types/handler';
import { callInitPlugins } from './observableHandling'; import { callInitPlugins } from './observableHandling';
import type { CommandModule, EventModule } from '../../types/module'; import type { CommandModule, EventModule } from '../../types/module';
@@ -9,7 +9,7 @@ import type { ErrorHandling, Logging } from '../contracts';
import { SernError, EventType, type Wrapper } from '../structures'; import { SernError, EventType, type Wrapper } from '../structures';
import { eventDispatcher } from './dispatchers'; import { eventDispatcher } from './dispatchers';
import { handleError } from '../contracts/errorHandling'; import { handleError } from '../contracts/errorHandling';
import { defineAllFields, errTap } from './operators'; import { errTap, fillDefaults } from './operators';
import { useContainerRaw } from '../dependencies'; import { useContainerRaw } from '../dependencies';
export function makeEventsHandler( export function makeEventsHandler(
@@ -21,21 +21,28 @@ export function makeEventsHandler(
const eventStream$ = eventObservable(eventsPath, s); const eventStream$ = eventObservable(eventsPath, s);
const eventCreation$ = eventStream$.pipe( const eventCreation$ = eventStream$.pipe(
defineAllFields(), map(fillDefaults),
callInitPlugins({ callInitPlugins({
onFailure: module => s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)), onStop: module =>
onSuccess: ({ module }) => { s.emit('module.register', SernEmitter.failure(module, SernError.PluginFailure)),
onNext: ({ module }) => {
s.emit('module.register', SernEmitter.success(module)); s.emit('module.register', SernEmitter.success(module));
return module; return module;
}, },
}), }),
); );
const intoDispatcher = (e: Processed<EventModule | CommandModule>) => { const intoDispatcher = (e: Processed<EventModule | CommandModule>) => {
switch(e.type) { switch (e.type) {
case EventType.Sern: return eventDispatcher(e, s); case EventType.Sern:
case EventType.Discord: return eventDispatcher(e, client); return eventDispatcher(e, s);
case EventType.External: return eventDispatcher(e, lazy(e.emitter)); case EventType.Discord:
default: err.crash(Error(SernError.InvalidModuleType + ' while creating event handler')); return eventDispatcher(e, client);
case EventType.External:
return eventDispatcher(e, lazy(e.emitter));
default:
return err.crash(
Error(SernError.InvalidModuleType + ' while creating event handler'),
);
} }
}; };
eventCreation$ eventCreation$
@@ -59,7 +66,7 @@ export function makeEventsHandler(
} }
function eventObservable(events: string, emitter: SernEmitter) { function eventObservable(events: string, emitter: SernEmitter) {
return buildData<EventModule>(events).pipe( return Files.buildModuleStream<EventModule>(events).pipe(
errTap(reason => { errTap(reason => {
emitter.emit('module.register', SernEmitter.failure(undefined, reason)); emitter.emit('module.register', SernEmitter.failure(undefined, reason));
}), }),

View File

@@ -1,8 +1,10 @@
import { readdirSync, statSync } from 'fs'; import { readdirSync, statSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
import { type Observable, from, mergeAll } from 'rxjs'; import { type Observable, from, mergeMap } from 'rxjs';
import { SernError } from '../structures/errors'; import { SernError } from '../structures/errors';
import { type Result, Err, Ok } from 'ts-results-es'; import { type Result, Err, Ok } from 'ts-results-es';
import { ImportPayload } from '../../types/handler';
import { pathToFileURL } from 'node:url';
// Courtesy @Townsy45 // Courtesy @Townsy45
function readPath(dir: string, arrayOfFiles: string[] = []): string[] { function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
@@ -19,45 +21,44 @@ function readPath(dir: string, arrayOfFiles: string[] = []): string[] {
return arrayOfFiles; return arrayOfFiles;
} }
export const fmtFileName = (n: string) => n.substring(0, n.length - 3); export const fmtFileName = (n: string) => n.substring(0, n.length - 3);
// export const isLazy = (n: string) => n.indexOf(".lazy.", n.length-9) !== -1;
export async function defaultModuleLoader<T>(
absPath: string,
): Promise<Result<ImportPayload<T>, SernError>> {
// prettier-ignore
let module: T | undefined
/// #if MODE === 'esm'
= (await import(pathToFileURL(absPath).toString())).default
/// #elif MODE === 'cjs'
= require(absPath).default; // eslint-disable-line
/// #endif
if (module === undefined) {
return Err(SernError.UndefinedModule);
}
try {
module = new (module as unknown as new () => T)();
} catch {}
return Ok({ module, absPath });
}
/** /**
* a directory string is converted into a stream of modules. * a directory string is converted into a stream of modules.
* starts the stream of modules that sern needs to process on init * starts the stream of modules that sern needs to process on init
* @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files * @returns {Observable<{ mod: Module; absPath: string; }[]>} data from command files
* @param commandDir * @param commandDir
*/ */
export function buildData<T>(commandDir: string): Observable< export function buildModuleStream<T>(
Result< commandDir: string,
{ ): Observable<Result<ImportPayload<T>, SernError>> {
module: T;
absPath: string;
},
SernError
>
> {
const commands = getCommands(commandDir); const commands = getCommands(commandDir);
return from( return from(commands).pipe(mergeMap(defaultModuleLoader<T>));
Promise.all( }
commands.map(async absPath => {
// prettier-ignore
let module: T | undefined
/// #if MODE === 'esm'
= (await import(`file:///` + absPath)).default
/// #elif MODE === 'cjs'
= require(absPath).default; // eslint-disable-line
/// #endif
if (module === undefined) { export function fullPathFrom(dir: string) {
return Err(SernError.UndefinedModule); return join(process.cwd(), dir);
}
try {
module = new (module as unknown as new () => T)();
} catch {}
return Ok({ module, absPath });
}),
),
).pipe(mergeAll());
} }
export function getCommands(dir: string): string[] { export function getCommands(dir: string): string[] {
return readPath(join(process.cwd(), dir)); return readPath(fullPathFrom(dir));
} }

View File

@@ -13,25 +13,37 @@ export function makePlugin<V extends unknown[]>(
[guayin]: undefined, [guayin]: undefined,
} as Plugin<V>; } as Plugin<V>;
} }
/**
* @since 2.5.0
*
*/
export function EventInitPlugin<I extends EventType>( export function EventInitPlugin<I extends EventType>(
execute: (...args: EventArgs<I, PluginType.Init>) => PluginResult, execute: (...args: EventArgs<I, PluginType.Init>) => PluginResult,
) { ) {
return makePlugin(PluginType.Init, execute); return makePlugin(PluginType.Init, execute);
} }
/**
* @since 2.5.0
*
*/
export function CommandInitPlugin<I extends CommandType>( export function CommandInitPlugin<I extends CommandType>(
execute: (...args: CommandArgs<I, PluginType.Init>) => PluginResult, execute: (...args: CommandArgs<I, PluginType.Init>) => PluginResult,
) { ) {
return makePlugin(PluginType.Init, execute); return makePlugin(PluginType.Init, execute);
} }
/**
* @since 2.5.0
*
*/
export function CommandControlPlugin<I extends CommandType>( export function CommandControlPlugin<I extends CommandType>(
execute: (...args: CommandArgs<I, PluginType.Control>) => PluginResult, execute: (...args: CommandArgs<I, PluginType.Control>) => PluginResult,
) { ) {
return makePlugin(PluginType.Control, execute); return makePlugin(PluginType.Control, execute);
} }
/**
* @since 2.5.0
*
*/
export function EventControlPlugin<I extends EventType>( export function EventControlPlugin<I extends EventType>(
execute: (...args: EventArgs<I, PluginType.Control>) => PluginResult, execute: (...args: EventArgs<I, PluginType.Control>) => PluginResult,
) { ) {
@@ -39,6 +51,7 @@ export function EventControlPlugin<I extends EventType>(
} }
/** /**
* @since 2.5.0
* @Experimental * @Experimental
* A specialized function for creating control plugins with discord.js ClientEvents. * A specialized function for creating control plugins with discord.js ClientEvents.
* Will probably be moved one day! * Will probably be moved one day!

View File

@@ -20,7 +20,7 @@ import { err, ok, partition } from './utilities/functions';
import type { Awaitable, ClientEvents } from 'discord.js'; import type { Awaitable, ClientEvents } from 'discord.js';
/** /**
* * @since 1.0.0
* @param wrapper Options to pass into sern. * @param wrapper Options to pass into sern.
* Function to start the handler up * Function to start the handler up
* @example * @example
@@ -43,14 +43,16 @@ export function init(wrapper: Wrapper) {
if (events !== undefined) { if (events !== undefined) {
makeEventsHandler(requiredDependenciesAnd([]), events, wrapper.containerConfig); makeEventsHandler(requiredDependenciesAnd([]), events, wrapper.containerConfig);
} }
makeReadyEvent(requiredDependenciesAnd(['@sern/modules']), wrapper.commands); const dependencies = requiredDependenciesAnd(['@sern/modules']);
makeMessageCreate(requiredDependenciesAnd(['@sern/modules']), wrapper.defaultPrefix); makeReadyEvent(dependencies, wrapper.commands);
makeInteractionCreate(requiredDependenciesAnd(['@sern/modules'])); makeMessageCreate(dependencies, wrapper.defaultPrefix);
makeInteractionCreate(dependencies);
const endTime = performance.now(); const endTime = performance.now();
logger?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` }); logger?.info({ message: `sern : ${(endTime - startTime).toFixed(2)} ms` });
} }
/** /**
* @since 1.0.0
* The object passed into every plugin to control a command's behavior * The object passed into every plugin to control a command's behavior
*/ */
export const controller = { export const controller = {
@@ -59,6 +61,7 @@ export const controller = {
}; };
/** /**
* @since 1.0.0
* The wrapper function to define command modules for sern * The wrapper function to define command modules for sern
* @param mod * @param mod
*/ */
@@ -74,6 +77,7 @@ export function commandModule(mod: InputCommand): CommandModule {
} as CommandModule; } as CommandModule;
} }
/** /**
* @since 1.0.0
* The wrapper function to define event modules for sern * The wrapper function to define event modules for sern
* @param mod * @param mod
*/ */
@@ -103,6 +107,7 @@ export function discordEvent<T extends keyof ClientEvents>(mod: {
return eventModule({ type: EventType.Discord, ...mod }); return eventModule({ type: EventType.Discord, ...mod });
} }
/** /**
* @since 2.0.0
* @param conf a configuration for creating your project dependencies * @param conf a configuration for creating your project dependencies
*/ */
export function makeDependencies<T extends Dependencies>(conf: DependencyConfiguration<T>) { export function makeDependencies<T extends Dependencies>(conf: DependencyConfiguration<T>) {
@@ -121,7 +126,8 @@ export abstract class CommandExecutable<Type extends CommandType> {
onEvent: ControlPlugin[] = []; onEvent: ControlPlugin[] = [];
abstract execute: CommandModuleDefs[Type]['execute']; abstract execute: CommandModuleDefs[Type]['execute'];
} }
/**@Experimental /**
* @Experimental
* Will be refactored in future * Will be refactored in future
*/ */
export abstract class EventExecutable<Type extends EventType> { export abstract class EventExecutable<Type extends EventType> {

View File

@@ -3,6 +3,9 @@ import type { Payload, SernEventsMapping } from '../types/handler';
import { PayloadType } from './structures'; import { PayloadType } from './structures';
import type { Module } from '../types/module'; import type { Module } from '../types/module';
/**
* @since 1.0.0
*/
class SernEmitter extends EventEmitter { class SernEmitter extends EventEmitter {
/** /**
* Listening to sern events with on. This event stays on until a crash or a normal exit * Listening to sern events with on. This event stays on until a crash or a normal exit

View File

@@ -15,6 +15,7 @@ function safeUnwrap<T>(res: Either<T, T>) {
return res.val; return res.val;
} }
/** /**
* @since 1.0.0
* Provides values shared between * Provides values shared between
* Message and ChatInputCommandInteraction * Message and ChatInputCommandInteraction
*/ */

View File

@@ -1,4 +1,5 @@
/** /**
* @since 1.0.0
* A bitfield that discriminates command modules * A bitfield that discriminates command modules
* @enum { number } * @enum { number }
* @example * @example

View File

@@ -3,6 +3,7 @@ import { ApplicationCommandType, ComponentType } from 'discord.js';
import type { Processed } from '../../types/handler'; import type { Processed } from '../../types/handler';
/** /**
* @since 2.0.0
* Storing all command modules * Storing all command modules
* This dependency is usually injected into ModuleManager * This dependency is usually injected into ModuleManager
*/ */

View File

@@ -1,10 +1,15 @@
import type { Dependencies } from '../../types/handler'; import type { Dependencies } from '../../types/handler';
/** /**
* @since 1.0.0
* An object to be passed into Sern#init() function. * An object to be passed into Sern#init() function.
* @typedef {object} Wrapper * @typedef {object} Wrapper
*/ */
interface Wrapper { interface Wrapper {
/**
* @deprecated
* This will be moved to a new field in 3.0.0
*/
readonly defaultPrefix?: string; readonly defaultPrefix?: string;
readonly commands: string; readonly commands: string;
readonly events?: string; readonly events?: string;

View File

@@ -1,13 +0,0 @@
/**
* 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, '!'));
* // [ 'ping' ]
*/
export function fmt(msg: string, prefix: string): string[] {
return msg.slice(prefix.length).trim().split(/\s+/g);
}

View File

@@ -67,3 +67,5 @@ export interface DependencyConfiguration<T extends Dependencies> {
exclude?: Set<OptionalDependencies>; exclude?: Set<OptionalDependencies>;
build: (root: Container<Omit<Dependencies, '@sern/client'>, {}>) => Container<T, {}>; build: (root: Container<Omit<Dependencies, '@sern/client'>, {}>) => Container<T, {}>;
} }
export type ImportPayload<T> = { module: T; absPath: string };

View File

@@ -19,7 +19,7 @@ import type {
MentionableSelectMenuInteraction, MentionableSelectMenuInteraction,
RoleSelectMenuInteraction, RoleSelectMenuInteraction,
StringSelectMenuInteraction, StringSelectMenuInteraction,
UserSelectMenuInteraction UserSelectMenuInteraction,
} from 'discord.js'; } from 'discord.js';
import { CommandType } from '../handler/structures/enums'; import { CommandType } from '../handler/structures/enums';
import type { Args, SlashOptions } from './handler'; import type { Args, SlashOptions } from './handler';

View File

@@ -6,10 +6,10 @@
"noImplicitAny": true, "noImplicitAny": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"strictNullChecks": true, "strictNullChecks": true,
"importsNotUsedAsValues": "error",
"moduleResolution": "node", "moduleResolution": "node",
"skipLibCheck": true, "skipLibCheck": true,
"declaration": true, "declaration": true,
"preserveSymlinks": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true "forceConsistentCasingInFileNames": true
}, },

3
tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "./tsconfig-esm.json"
}

3171
yarn.lock Normal file

File diff suppressed because it is too large Load Diff