@reciple/modules 10.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +160 -0
- package/dist/classes/InteractionListener.d.mts +22 -0
- package/dist/classes/InteractionListener.mjs +34 -0
- package/dist/classes/InteractionListener.mjs.map +1 -0
- package/dist/classes/builders/InteractionListenerBuilder.d.mts +16 -0
- package/dist/classes/builders/InteractionListenerBuilder.mjs +30 -0
- package/dist/classes/builders/InteractionListenerBuilder.mjs.map +1 -0
- package/dist/classes/modules/RecipleAnticrash.d.mts +38 -0
- package/dist/classes/modules/RecipleAnticrash.mjs +78 -0
- package/dist/classes/modules/RecipleAnticrash.mjs.map +1 -0
- package/dist/classes/modules/RecipleInteractionEvents.d.mts +35 -0
- package/dist/classes/modules/RecipleInteractionEvents.mjs +82 -0
- package/dist/classes/modules/RecipleInteractionEvents.mjs.map +1 -0
- package/dist/classes/modules/RecipleRegistryCache.d.mts +54 -0
- package/dist/classes/modules/RecipleRegistryCache.mjs +150 -0
- package/dist/classes/modules/RecipleRegistryCache.mjs.map +1 -0
- package/dist/helpers/constants.d.mts +13 -0
- package/dist/helpers/constants.mjs +15 -0
- package/dist/helpers/constants.mjs.map +1 -0
- package/dist/helpers/types.d.mts +17 -0
- package/dist/helpers/types.mjs +1 -0
- package/dist/index.d.mts +8 -0
- package/dist/index.mjs +8 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://i.imgur.com/C3gWxwc.png" width="50%">
|
|
3
|
+
<p align="center">
|
|
4
|
+
<b>A Discord.js framework that just works.</b>
|
|
5
|
+
</p>
|
|
6
|
+
<br>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<h3 align="center">
|
|
10
|
+
<a href="https://discord.gg/KxfPZYuTGV">
|
|
11
|
+
<img src="https://img.shields.io/discord/1453743492722458708?color=5865F2&logo=discord&logoColor=white">
|
|
12
|
+
</a>
|
|
13
|
+
<a href="https://npmjs.org/package/@reciple/modules">
|
|
14
|
+
<img src="https://img.shields.io/npm/v/@reciple/modules?label=npm">
|
|
15
|
+
</a>
|
|
16
|
+
<a href="https://github.com/reciplejs/reciple/tree/main/packages/modules">
|
|
17
|
+
<img src="https://img.shields.io/npm/dt/@reciple/modules?maxAge=3600">
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://www.codefactor.io/repository/github/reciplejs/reciple">
|
|
20
|
+
<img src="https://www.codefactor.io/repository/github/reciplejs/reciple/badge">
|
|
21
|
+
</a>
|
|
22
|
+
<br>
|
|
23
|
+
<div style="padding-top: 1rem">
|
|
24
|
+
<a href="https://discord.gg/KxfPZYuTGV">
|
|
25
|
+
<img src="http://invidget.switchblade.xyz/KxfPZYuTGV">
|
|
26
|
+
</a>
|
|
27
|
+
</div>
|
|
28
|
+
</h3>
|
|
29
|
+
|
|
30
|
+
## About
|
|
31
|
+
|
|
32
|
+
`@reciple/modules` A JSX wrapper for [Discord.js](https://discord.js.org/) builders.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install @reciple/modules
|
|
38
|
+
yarn add @reciple/modules
|
|
39
|
+
pnpm add @reciple/modules
|
|
40
|
+
bun install @reciple/modules
|
|
41
|
+
deno install npm:@reciple/modules
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Usage
|
|
45
|
+
|
|
46
|
+
Create a new reciple module file then create a new module instance as the default export.
|
|
47
|
+
|
|
48
|
+
### Anticrash
|
|
49
|
+
|
|
50
|
+
A module that catches process and client errors that cause the bot to crash then logs them to a channel if specified.
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
// example/src/addons/antiCrash.js
|
|
54
|
+
import { RecipleAnticrash } from '@reciple/modules';
|
|
55
|
+
|
|
56
|
+
export default new RecipleAnticrash({
|
|
57
|
+
// The channels to report errors to
|
|
58
|
+
reportChannels: ['000000000000000000'],
|
|
59
|
+
|
|
60
|
+
// The base options for the report message
|
|
61
|
+
baseReportMessageOptions: {
|
|
62
|
+
content: 'An error has occurred!',
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Registry Cache
|
|
68
|
+
|
|
69
|
+
A module that caches the application commands to prevent unnecessary requests to the Discord API when registering commands that haven't changed.
|
|
70
|
+
|
|
71
|
+
```js
|
|
72
|
+
// example/src/addons/registryCache.js
|
|
73
|
+
import { RecipleRegistryCache } from '@reciple/modules';
|
|
74
|
+
|
|
75
|
+
export default new RecipleRegistryCache({
|
|
76
|
+
// Cache duration in milliseconds
|
|
77
|
+
maxCacheAgeMs: 24 * 60 * 60 * 1000,
|
|
78
|
+
|
|
79
|
+
// The directory to store the cache in
|
|
80
|
+
cacheDir: '.cache/reciple-registry/',
|
|
81
|
+
|
|
82
|
+
// The environment variable to check if the cache is enabled
|
|
83
|
+
cacheEnabledEnv: 'RECIPLE_REGISTRY_CACHE',
|
|
84
|
+
|
|
85
|
+
// Create a cache entry even if the cache is disabled
|
|
86
|
+
createCacheEntryEvenIfDisabled: true
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Interaction Events
|
|
91
|
+
|
|
92
|
+
A module that handles interaction events and executes listeners based on the interaction type. The listeners are resolved from the specified module property.
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
// example/src/addons/interactionEvents.js
|
|
96
|
+
import { RecipleInteractionEvents } from '@reciple/modules';
|
|
97
|
+
|
|
98
|
+
export default new RecipleInteractionEvents({
|
|
99
|
+
// Filters the modules to resolve listeners from
|
|
100
|
+
ignoredModules: (module) => module.id === 'ignored_id',
|
|
101
|
+
|
|
102
|
+
// The module property to resolve listeners from
|
|
103
|
+
moduleEventListenersProperty: ['interactions']
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```js
|
|
108
|
+
// example/src/commands/ping.js
|
|
109
|
+
import { InteractionListenerBuilder, InteractionListenerType } from '@reciple/modules';
|
|
110
|
+
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
|
111
|
+
import { SlashCommandBuilder, SlashCommandModule, type SlashCommand } from 'reciple';
|
|
112
|
+
|
|
113
|
+
export class ButtonPingCommand extends SlashCommandModule {
|
|
114
|
+
data = new SlashCommandBuilder()
|
|
115
|
+
.setName('ping')
|
|
116
|
+
.setDescription('Test ping command')
|
|
117
|
+
.toJSON();
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @type {InteractionListenerBuilder[]}
|
|
121
|
+
*/
|
|
122
|
+
interactions = [
|
|
123
|
+
new InteractionListenerBuilder()
|
|
124
|
+
.setType(InteractionListenerType.Button)
|
|
125
|
+
.setFilter(interaction => interaction.customId === 'ping')
|
|
126
|
+
.setExecute(async interaction => {
|
|
127
|
+
await interaction.reply({
|
|
128
|
+
content: 'Pong!',
|
|
129
|
+
ephemeral: true
|
|
130
|
+
});
|
|
131
|
+
})
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {SlashCommand.ExecuteData} data
|
|
136
|
+
*/
|
|
137
|
+
async execute(data) {
|
|
138
|
+
await data.interaction.reply({
|
|
139
|
+
components: [
|
|
140
|
+
new ActionRowBuilder()
|
|
141
|
+
.addComponents(
|
|
142
|
+
new ButtonBuilder()
|
|
143
|
+
.setCustomId('ping')
|
|
144
|
+
.setLabel('Ping')
|
|
145
|
+
.setStyle(ButtonStyle.Primary)
|
|
146
|
+
)
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default new ButtonPingCommand();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Links
|
|
156
|
+
|
|
157
|
+
- [Website](https://reciple.js.org)
|
|
158
|
+
- [Discord](https://discord.gg/KxfPZYuTGV)
|
|
159
|
+
- [Github](https://github.com/reciplejs/reciple/tree/main/packages/modules)
|
|
160
|
+
- [NPM](https://npmjs.org/package/@reciple/modules)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { InteractionListenerType } from "../helpers/constants.mjs";
|
|
2
|
+
import { InteractionFromListenerType, InteractionListenerData } from "../helpers/types.mjs";
|
|
3
|
+
import { JSONEncodable } from "discord.js";
|
|
4
|
+
|
|
5
|
+
//#region src/classes/InteractionListener.d.ts
|
|
6
|
+
declare class InteractionListener<T extends InteractionListenerType> implements InteractionListenerData<T> {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly moduleId?: string;
|
|
9
|
+
readonly type: T;
|
|
10
|
+
readonly once?: boolean;
|
|
11
|
+
constructor(data: InteractionListener.Resolvable<T>);
|
|
12
|
+
filter(interaction: InteractionFromListenerType<T>): boolean;
|
|
13
|
+
execute(interaction: InteractionFromListenerType<T>): Promise<void>;
|
|
14
|
+
toJSON(): InteractionListenerData<T>;
|
|
15
|
+
static from<T extends InteractionListenerType>(data: InteractionListener.Resolvable<T>): InteractionListener<T>;
|
|
16
|
+
}
|
|
17
|
+
declare namespace InteractionListener {
|
|
18
|
+
type Resolvable<T extends InteractionListenerType> = InteractionListener<T> | InteractionListenerData<T> | JSONEncodable<InteractionListenerData<T>>;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { InteractionListener };
|
|
22
|
+
//# sourceMappingURL=InteractionListener.d.mts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isJSONEncodable } from "discord.js";
|
|
2
|
+
import { DiscordSnowflake } from "@sapphire/snowflake";
|
|
3
|
+
|
|
4
|
+
//#region src/classes/InteractionListener.ts
|
|
5
|
+
var InteractionListener = class InteractionListener {
|
|
6
|
+
id = DiscordSnowflake.generate().toString();
|
|
7
|
+
moduleId;
|
|
8
|
+
type;
|
|
9
|
+
once;
|
|
10
|
+
constructor(data) {
|
|
11
|
+
Object.assign(this, isJSONEncodable(data) ? data.toJSON() : data);
|
|
12
|
+
}
|
|
13
|
+
filter(interaction) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
execute(interaction) {
|
|
17
|
+
return Promise.resolve();
|
|
18
|
+
}
|
|
19
|
+
toJSON() {
|
|
20
|
+
return {
|
|
21
|
+
type: this.type,
|
|
22
|
+
once: this.once,
|
|
23
|
+
filter: this.filter,
|
|
24
|
+
execute: this.execute
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
static from(data) {
|
|
28
|
+
return data instanceof InteractionListener ? data : new InteractionListener(data);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { InteractionListener };
|
|
34
|
+
//# sourceMappingURL=InteractionListener.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InteractionListener.mjs","names":[],"sources":["../../src/classes/InteractionListener.ts"],"sourcesContent":["import { isJSONEncodable, type JSONEncodable } from 'discord.js';\nimport type { InteractionListenerType } from '../helpers/constants.js';\nimport type { InteractionListenerData, InteractionFromListenerType } from '../helpers/types.js';\nimport { DiscordSnowflake } from '@sapphire/snowflake';\n\nexport class InteractionListener<T extends InteractionListenerType> implements InteractionListenerData<T> {\n public readonly id: string = DiscordSnowflake.generate().toString();\n public readonly moduleId?: string;\n public readonly type!: T;\n public readonly once?: boolean;\n\n constructor(data: InteractionListener.Resolvable<T>) {\n Object.assign(this, isJSONEncodable(data) ? data.toJSON() : data);\n }\n\n public filter(interaction: InteractionFromListenerType<T>): boolean {\n return true;\n }\n\n public execute(interaction: InteractionFromListenerType<T>): Promise<void> {\n return Promise.resolve();\n }\n\n public toJSON(): InteractionListenerData<T> {\n return {\n type: this.type,\n once: this.once,\n filter: this.filter,\n execute: this.execute\n };\n }\n\n public static from<T extends InteractionListenerType>(data: InteractionListener.Resolvable<T>): InteractionListener<T> {\n return data instanceof InteractionListener ? data : new InteractionListener(data);\n }\n}\n\nexport namespace InteractionListener {\n export type Resolvable<T extends InteractionListenerType> = InteractionListener<T>|InteractionListenerData<T>|JSONEncodable<InteractionListenerData<T>>;\n}\n"],"mappings":";;;;AAKA,IAAa,sBAAb,MAAa,oBAA6F;CACtG,AAAgB,KAAa,iBAAiB,UAAU,CAAC,UAAU;CACnE,AAAgB;CAChB,AAAgB;CAChB,AAAgB;CAEhB,YAAY,MAAyC;AACjD,SAAO,OAAO,MAAM,gBAAgB,KAAK,GAAG,KAAK,QAAQ,GAAG,KAAK;;CAGrE,AAAO,OAAO,aAAsD;AAChE,SAAO;;CAGX,AAAO,QAAQ,aAA4D;AACvE,SAAO,QAAQ,SAAS;;CAG5B,AAAO,SAAqC;AACxC,SAAO;GACH,MAAM,KAAK;GACX,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,SAAS,KAAK;GACjB;;CAGL,OAAc,KAAwC,MAAiE;AACnH,SAAO,gBAAgB,sBAAsB,OAAO,IAAI,oBAAoB,KAAK"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { InteractionListenerType } from "../../helpers/constants.mjs";
|
|
2
|
+
import { InteractionListenerData } from "../../helpers/types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/classes/builders/InteractionListenerBuilder.d.ts
|
|
5
|
+
declare class InteractionListenerBuilder<T extends InteractionListenerType> {
|
|
6
|
+
readonly data: Partial<InteractionListenerData<T>>;
|
|
7
|
+
constructor(data?: Partial<InteractionListenerData<T>>);
|
|
8
|
+
setType<Type extends T>(type: Type): InteractionListenerBuilder<Type>;
|
|
9
|
+
setOnce(once: boolean): this;
|
|
10
|
+
setFilter(filter: InteractionListenerData<T>['filter']): this;
|
|
11
|
+
setExecute(execute: InteractionListenerData<T>['execute']): this;
|
|
12
|
+
toJSON(): InteractionListenerData<T>;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { InteractionListenerBuilder };
|
|
16
|
+
//# sourceMappingURL=InteractionListenerBuilder.d.mts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/classes/builders/InteractionListenerBuilder.ts
|
|
2
|
+
var InteractionListenerBuilder = class {
|
|
3
|
+
data = {};
|
|
4
|
+
constructor(data) {
|
|
5
|
+
if (data) this.data = data;
|
|
6
|
+
}
|
|
7
|
+
setType(type) {
|
|
8
|
+
this.data.type = type;
|
|
9
|
+
return this;
|
|
10
|
+
}
|
|
11
|
+
setOnce(once) {
|
|
12
|
+
this.data.once = once;
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
setFilter(filter) {
|
|
16
|
+
this.data.filter = filter;
|
|
17
|
+
return this;
|
|
18
|
+
}
|
|
19
|
+
setExecute(execute) {
|
|
20
|
+
this.data.execute = execute;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
toJSON() {
|
|
24
|
+
return this.data;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { InteractionListenerBuilder };
|
|
30
|
+
//# sourceMappingURL=InteractionListenerBuilder.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InteractionListenerBuilder.mjs","names":[],"sources":["../../../src/classes/builders/InteractionListenerBuilder.ts"],"sourcesContent":["import { InteractionListenerType } from '../../helpers/constants.js';\nimport type { InteractionListenerData } from '../../helpers/types.js';\n\nexport class InteractionListenerBuilder<T extends InteractionListenerType> {\n public readonly data: Partial<InteractionListenerData<T>> = {};\n\n public constructor(data?: Partial<InteractionListenerData<T>>) {\n if (data) this.data = data;\n }\n\n public setType<Type extends T>(type: Type): InteractionListenerBuilder<Type> {\n this.data.type = type;\n return this as any;\n }\n\n public setOnce(once: boolean): this {\n this.data.once = once;\n return this;\n }\n\n public setFilter(filter: InteractionListenerData<T>['filter']): this {\n this.data.filter = filter;\n return this;\n }\n\n public setExecute(execute: InteractionListenerData<T>['execute']): this {\n this.data.execute = execute;\n return this;\n }\n\n public toJSON(): InteractionListenerData<T> {\n return this.data as InteractionListenerData<T>;\n }\n}\n"],"mappings":";AAGA,IAAa,6BAAb,MAA2E;CACvE,AAAgB,OAA4C,EAAE;CAE9D,AAAO,YAAY,MAA4C;AAC3D,MAAI,KAAM,MAAK,OAAO;;CAG1B,AAAO,QAAwB,MAA8C;AACzE,OAAK,KAAK,OAAO;AACjB,SAAO;;CAGX,AAAO,QAAQ,MAAqB;AAChC,OAAK,KAAK,OAAO;AACjB,SAAO;;CAGX,AAAO,UAAU,QAAoD;AACjE,OAAK,KAAK,SAAS;AACnB,SAAO;;CAGX,AAAO,WAAW,SAAsD;AACpE,OAAK,KAAK,UAAU;AACpB,SAAO;;CAGX,AAAO,SAAqC;AACxC,SAAO,KAAK"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Awaitable, BaseMessageOptions as BaseMessageOptions$1, Message, SendableChannels } from "discord.js";
|
|
2
|
+
import { BaseModule } from "reciple";
|
|
3
|
+
|
|
4
|
+
//#region src/classes/modules/RecipleAnticrash.d.ts
|
|
5
|
+
declare class RecipleAnticrash extends BaseModule implements RecipleAnticrash.Options {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
baseReportMessageOptions?: RecipleAnticrash.Options['baseReportMessageOptions'];
|
|
8
|
+
reportChannels: string[];
|
|
9
|
+
private readonly logger;
|
|
10
|
+
constructor(options?: RecipleAnticrash.Options);
|
|
11
|
+
onEnable(data: BaseModule.EventData<boolean>): Promise<void>;
|
|
12
|
+
onReady(data: BaseModule.EventData<true>): Promise<void>;
|
|
13
|
+
onDisable(data: BaseModule.EventData<boolean>): Promise<void>;
|
|
14
|
+
report(reason: any): Promise<RecipleAnticrash.Report[]>;
|
|
15
|
+
createReportMessageOptions(reason: any, stack: string): Promise<BaseMessageOptions$1>;
|
|
16
|
+
resolveChannel(resolvable: Exclude<RecipleAnticrash.Options['reportChannels'], undefined>[0]): Promise<SendableChannels | null>;
|
|
17
|
+
private _captureErrorEvent;
|
|
18
|
+
}
|
|
19
|
+
declare namespace RecipleAnticrash {
|
|
20
|
+
interface Options {
|
|
21
|
+
baseReportMessageOptions?: BaseMessageOptions | ((reason: any) => Awaitable<BaseMessageOptions>);
|
|
22
|
+
reportChannels?: (string | {
|
|
23
|
+
id: string;
|
|
24
|
+
} | SendableChannels | (() => Awaitable<SendableChannels>))[];
|
|
25
|
+
}
|
|
26
|
+
interface Report {
|
|
27
|
+
message?: Message;
|
|
28
|
+
stackTrace: string;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
}
|
|
31
|
+
interface BaseMessageOptions extends BaseMessageOptions$1 {
|
|
32
|
+
replaceEmbeds?: 'merge' | boolean;
|
|
33
|
+
replaceFiles?: 'merge' | boolean;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { RecipleAnticrash };
|
|
38
|
+
//# sourceMappingURL=RecipleAnticrash.d.mts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AttachmentBuilder, BaseChannel, Colors, EmbedBuilder, codeBlock, escapeCodeBlock } from "discord.js";
|
|
2
|
+
import { inspect } from "node:util";
|
|
3
|
+
import { BaseModule } from "reciple";
|
|
4
|
+
|
|
5
|
+
//#region src/classes/modules/RecipleAnticrash.ts
|
|
6
|
+
var RecipleAnticrash = class extends BaseModule {
|
|
7
|
+
id = "org.reciple.js.anticrash";
|
|
8
|
+
baseReportMessageOptions;
|
|
9
|
+
reportChannels = [];
|
|
10
|
+
logger = useLogger().clone({ label: "Anticrash" });
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super();
|
|
13
|
+
Object.assign(this, options ?? {});
|
|
14
|
+
this._captureErrorEvent = this._captureErrorEvent.bind(this);
|
|
15
|
+
}
|
|
16
|
+
async onEnable(data) {
|
|
17
|
+
this.client.on("error", this._captureErrorEvent);
|
|
18
|
+
this.client.on("shardError", this._captureErrorEvent);
|
|
19
|
+
process.on("uncaughtException", this._captureErrorEvent);
|
|
20
|
+
process.on("uncaughtExceptionMonitor", this._captureErrorEvent);
|
|
21
|
+
process.on("unhandledRejection", this._captureErrorEvent);
|
|
22
|
+
}
|
|
23
|
+
async onReady(data) {}
|
|
24
|
+
async onDisable(data) {
|
|
25
|
+
this.client.off("error", this._captureErrorEvent);
|
|
26
|
+
this.client.off("shardError", this._captureErrorEvent);
|
|
27
|
+
process.off("uncaughtException", this._captureErrorEvent);
|
|
28
|
+
process.off("uncaughtExceptionMonitor", this._captureErrorEvent);
|
|
29
|
+
process.off("unhandledRejection", this._captureErrorEvent);
|
|
30
|
+
}
|
|
31
|
+
async report(reason) {
|
|
32
|
+
const reports = [];
|
|
33
|
+
const stack = inspect(reason, { colors: false });
|
|
34
|
+
this.logger.err(reason);
|
|
35
|
+
for (const channelId of this.reportChannels) {
|
|
36
|
+
const channel = await this.resolveChannel(channelId);
|
|
37
|
+
if (!channel) continue;
|
|
38
|
+
const message = await channel.send(await this.createReportMessageOptions(reason, stack)).catch(() => null);
|
|
39
|
+
if (message) reports.push({
|
|
40
|
+
message,
|
|
41
|
+
stackTrace: stack,
|
|
42
|
+
timestamp: message.createdTimestamp
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return reports;
|
|
46
|
+
}
|
|
47
|
+
async createReportMessageOptions(reason, stack) {
|
|
48
|
+
const base = typeof this.baseReportMessageOptions === "function" ? await Promise.resolve(this.baseReportMessageOptions(reason)).catch(() => null) : this.baseReportMessageOptions ?? {};
|
|
49
|
+
const embed = new EmbedBuilder().setAuthor({ name: `Anticrash report` }).setTitle(String(reason).substring(0, 100)).setColor(Colors.Red).setTimestamp();
|
|
50
|
+
let files = [...base?.files ?? []];
|
|
51
|
+
if (stack.length < 1950) embed.setDescription(codeBlock(escapeCodeBlock(stack)));
|
|
52
|
+
else {
|
|
53
|
+
const file = new AttachmentBuilder(Buffer.from(stack, "utf-8"), { name: "report.log" });
|
|
54
|
+
if (base?.replaceFiles) files = [file];
|
|
55
|
+
else if (base?.replaceFiles === void 0 || base?.replaceFiles === "merge") files.push(file);
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
...base,
|
|
59
|
+
embeds: base?.replaceEmbeds ? [embed] : base?.replaceEmbeds === void 0 || base?.replaceEmbeds === "merge" ? [...base?.embeds ?? [], embed] : base.embeds,
|
|
60
|
+
files
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async resolveChannel(resolvable) {
|
|
64
|
+
if (resolvable instanceof BaseChannel) return resolvable;
|
|
65
|
+
if (typeof resolvable === "function") return Promise.resolve(resolvable()).catch(() => null);
|
|
66
|
+
const id = typeof resolvable === "string" ? resolvable : resolvable.id;
|
|
67
|
+
const channel = await this.client.channels.fetch(id).catch(() => null);
|
|
68
|
+
if (channel) return channel.isSendable() ? channel : null;
|
|
69
|
+
return (await this.client.users.fetch(id).catch(() => null))?.dmChannel ?? null;
|
|
70
|
+
}
|
|
71
|
+
async _captureErrorEvent(...args) {
|
|
72
|
+
if (args[0]) await this.report(args[0]);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { RecipleAnticrash };
|
|
78
|
+
//# sourceMappingURL=RecipleAnticrash.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecipleAnticrash.mjs","names":[],"sources":["../../../src/classes/modules/RecipleAnticrash.ts"],"sourcesContent":["import { AttachmentBuilder, BaseChannel, codeBlock, Colors, EmbedBuilder, escapeCodeBlock, type Awaitable, type BaseMessageOptions as DJSBaseMessageOptions, type Message, type SendableChannels } from 'discord.js';\nimport { inspect } from 'node:util';\nimport { BaseModule } from 'reciple';\n\nexport class RecipleAnticrash extends BaseModule implements RecipleAnticrash.Options {\n public readonly id: string = 'org.reciple.js.anticrash';\n\n public baseReportMessageOptions?: RecipleAnticrash.Options['baseReportMessageOptions'];\n public reportChannels: string[] = [];\n\n private readonly logger = useLogger().clone({ label: 'Anticrash' });\n\n constructor(options?: RecipleAnticrash.Options) {\n super();\n\n Object.assign(this, options ?? {});\n\n this._captureErrorEvent = this._captureErrorEvent.bind(this);\n }\n\n public async onEnable(data: BaseModule.EventData<boolean>): Promise<void> {\n this.client.on('error', this._captureErrorEvent);\n this.client.on('shardError', this._captureErrorEvent);\n process.on('uncaughtException', this._captureErrorEvent);\n process.on('uncaughtExceptionMonitor', this._captureErrorEvent);\n process.on('unhandledRejection', this._captureErrorEvent);\n }\n\n public async onReady(data: BaseModule.EventData<true>): Promise<void> {}\n\n public async onDisable(data: BaseModule.EventData<boolean>): Promise<void> {\n this.client.off('error', this._captureErrorEvent);\n this.client.off('shardError', this._captureErrorEvent);\n process.off('uncaughtException', this._captureErrorEvent);\n process.off('uncaughtExceptionMonitor', this._captureErrorEvent);\n process.off('unhandledRejection', this._captureErrorEvent);\n }\n\n public async report(reason: any): Promise<RecipleAnticrash.Report[]> {\n const reports: RecipleAnticrash.Report[] = [];\n const stack = inspect(reason, { colors: false });\n\n this.logger.err(reason);\n\n for (const channelId of this.reportChannels) {\n const channel = await this.resolveChannel(channelId);\n if (!channel) continue;\n\n const message = await channel.send(await this.createReportMessageOptions(reason, stack)).catch(() => null);\n if (message) reports.push({ message, stackTrace: stack, timestamp: message.createdTimestamp });\n }\n\n return reports;\n }\n\n public async createReportMessageOptions(reason: any, stack: string): Promise<DJSBaseMessageOptions> {\n const base = typeof this.baseReportMessageOptions === 'function'\n ? await Promise.resolve(this.baseReportMessageOptions(reason)).catch(() => null)\n : this.baseReportMessageOptions ?? {};\n\n const embed = new EmbedBuilder()\n .setAuthor({ name: `Anticrash report` })\n .setTitle(String(reason).substring(0, 100))\n .setColor(Colors.Red)\n .setTimestamp();\n\n let files: Exclude<DJSBaseMessageOptions['files'], undefined>[0][] = [...(base?.files ?? [])];\n\n if (stack.length < 1950) {\n embed.setDescription(codeBlock(escapeCodeBlock(stack)))\n } else {\n const file = new AttachmentBuilder(Buffer.from(stack, 'utf-8'), { name: 'report.log' });\n\n if (base?.replaceFiles) {\n files = [file];\n } else if (base?.replaceFiles === undefined || base?.replaceFiles === 'merge') {\n files.push(file);\n }\n }\n\n return {\n ...base,\n embeds: base?.replaceEmbeds\n ? [embed]\n : base?.replaceEmbeds === undefined || base?.replaceEmbeds === 'merge'\n ? [...(base?.embeds ?? []), embed]\n : base.embeds,\n files\n };\n }\n\n public async resolveChannel(resolvable: Exclude<RecipleAnticrash.Options['reportChannels'], undefined>[0]): Promise<SendableChannels|null> {\n if (resolvable instanceof BaseChannel) return resolvable;\n if (typeof resolvable === 'function') return Promise.resolve(resolvable()).catch(() => null);\n\n const id = typeof resolvable === 'string' ? resolvable : resolvable.id;\n const channel = await this.client.channels.fetch(id).catch(() => null);\n\n if (channel) {\n return channel.isSendable() ? channel : null;\n }\n\n const userDm = await this.client.users.fetch(id).catch(() => null);\n\n return userDm?.dmChannel ?? null;\n }\n\n private async _captureErrorEvent(...args: any[]): Promise<void> {\n if (args[0]) await this.report(args[0]);\n }\n}\n\nexport namespace RecipleAnticrash {\n export interface Options {\n baseReportMessageOptions?: BaseMessageOptions|((reason: any) => Awaitable<BaseMessageOptions>);\n reportChannels?: (string|{ id: string; }|SendableChannels|(() => Awaitable<SendableChannels>))[];\n }\n\n export interface Report {\n message?: Message;\n stackTrace: string;\n timestamp: number;\n }\n\n export interface BaseMessageOptions extends DJSBaseMessageOptions {\n replaceEmbeds?: 'merge'|boolean;\n replaceFiles?: 'merge'|boolean;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { InteractionListenerType } from "../../helpers/constants.mjs";
|
|
2
|
+
import { InteractionListener } from "../InteractionListener.mjs";
|
|
3
|
+
import { Awaitable, CacheType, Collection, Interaction } from "discord.js";
|
|
4
|
+
import { AnyModule, ClientEventModule } from "reciple";
|
|
5
|
+
|
|
6
|
+
//#region src/classes/modules/RecipleInteractionEvents.d.ts
|
|
7
|
+
declare class RecipleInteractionEvents extends ClientEventModule<'interactionCreate'> implements RecipleInteractionEvents.Options {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
event: "interactionCreate";
|
|
10
|
+
moduleEventListenersProperty: string | string[];
|
|
11
|
+
ignoredModules?: (module: AnyModule) => Awaitable<boolean>;
|
|
12
|
+
listeners: Collection<string, InteractionListener<InteractionListenerType>>;
|
|
13
|
+
private readonly logger;
|
|
14
|
+
constructor(options?: RecipleInteractionEvents.Options);
|
|
15
|
+
onReady(): Promise<void>;
|
|
16
|
+
onEvent(interaction: Interaction<CacheType>): Promise<void>;
|
|
17
|
+
resolveListeners(customModules?: AnyModule[]): Promise<InteractionListener<InteractionListenerType>[]>;
|
|
18
|
+
}
|
|
19
|
+
declare namespace RecipleInteractionEvents {
|
|
20
|
+
interface Options {
|
|
21
|
+
/**
|
|
22
|
+
* The property that is scanned from modules to get interaction listeners.
|
|
23
|
+
*/
|
|
24
|
+
moduleEventListenersProperty?: string | string[];
|
|
25
|
+
/**
|
|
26
|
+
* Filter modules that should be ignored.
|
|
27
|
+
*/
|
|
28
|
+
ignoredModules?: (module: AnyModule) => Awaitable<boolean>;
|
|
29
|
+
}
|
|
30
|
+
type ListenerModule<P extends string, T extends InteractionListenerType> = AnyModule & Record<P, InteractionListener.Resolvable<T>[]>;
|
|
31
|
+
function getInteractionTypeFromInteraction(interaction: Interaction): InteractionListenerType;
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { RecipleInteractionEvents };
|
|
35
|
+
//# sourceMappingURL=RecipleInteractionEvents.d.mts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { InteractionListenerType } from "../../helpers/constants.mjs";
|
|
2
|
+
import { InteractionListener } from "../InteractionListener.mjs";
|
|
3
|
+
import { Collection } from "discord.js";
|
|
4
|
+
import { ClientEventModule } from "reciple";
|
|
5
|
+
|
|
6
|
+
//#region src/classes/modules/RecipleInteractionEvents.ts
|
|
7
|
+
var RecipleInteractionEvents = class RecipleInteractionEvents extends ClientEventModule {
|
|
8
|
+
id = "org.reciple.js.interaction-events";
|
|
9
|
+
event = "interactionCreate";
|
|
10
|
+
moduleEventListenersProperty = "interactions";
|
|
11
|
+
ignoredModules;
|
|
12
|
+
listeners = new Collection();
|
|
13
|
+
logger = useLogger().clone({ label: "InteractionEvents" });
|
|
14
|
+
constructor(options = {}) {
|
|
15
|
+
super();
|
|
16
|
+
Object.assign(this, options);
|
|
17
|
+
}
|
|
18
|
+
async onReady() {
|
|
19
|
+
this.client.modules.on("readyModules", async (modules) => {
|
|
20
|
+
this.logger.debug(`Resolving listeners for ready modules...`);
|
|
21
|
+
for (const listener of await this.resolveListeners(modules)) this.listeners.set(listener.id, listener);
|
|
22
|
+
});
|
|
23
|
+
this.client.modules.on("disabledModules", async (modules) => {
|
|
24
|
+
this.logger.debug(`Sweeping listeners for disabled modules...`);
|
|
25
|
+
for (const module of modules) this.listeners.sweep((listener) => listener.moduleId === module.id);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async onEvent(interaction) {
|
|
29
|
+
const type = RecipleInteractionEvents.getInteractionTypeFromInteraction(interaction);
|
|
30
|
+
const listeners = this.listeners.filter((listener) => listener.type === type);
|
|
31
|
+
this.logger.debug(`Triggered (${listeners.size}) listeners for interaction type "${InteractionListenerType[type]}".`);
|
|
32
|
+
for (const [id, listener] of listeners) {
|
|
33
|
+
if (!await Promise.resolve(listener.filter(interaction))) continue;
|
|
34
|
+
await Promise.resolve(listener.execute(interaction));
|
|
35
|
+
if (listener.once) {
|
|
36
|
+
this.logger.debug(`Removed listener "${id}" for interaction type "${InteractionListenerType[type]}".`);
|
|
37
|
+
this.listeners.delete(id);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async resolveListeners(customModules) {
|
|
42
|
+
this.logger.debug(`Resolving listeners from (${customModules?.length ?? this.client.modules.cache.size}) modules...`);
|
|
43
|
+
const modules = customModules ?? this.client.modules.cache.map((m) => m);
|
|
44
|
+
const listenersProperty = Array.isArray(this.moduleEventListenersProperty) ? this.moduleEventListenersProperty : [this.moduleEventListenersProperty];
|
|
45
|
+
const listeners = [];
|
|
46
|
+
for (const module of modules) {
|
|
47
|
+
if (module.id === this.id) continue;
|
|
48
|
+
this.logger.debug(`Resolving listeners from module "${module.id}"...`);
|
|
49
|
+
propertyLoop: for (const listenerProperty of listenersProperty) {
|
|
50
|
+
if (!(listenerProperty in module)) continue propertyLoop;
|
|
51
|
+
if (!Array.isArray(module[listenerProperty]) || !module[listenerProperty].length) continue;
|
|
52
|
+
if (typeof this.ignoredModules === "function" && await Promise.resolve(this.ignoredModules(module))) continue;
|
|
53
|
+
const moduleListeners = module[listenerProperty];
|
|
54
|
+
this.logger.debug(`Resolving listeners from module "${module.id}" with property "${listenerProperty}"...`);
|
|
55
|
+
for (const data of moduleListeners) {
|
|
56
|
+
const listener = InteractionListener.from(data);
|
|
57
|
+
Reflect.set(listener, "moduleId", module.id);
|
|
58
|
+
listeners.push(listener);
|
|
59
|
+
}
|
|
60
|
+
this.logger.debug(`Resolved (${moduleListeners.length}) listeners from module "${module.id}" with property "${listenerProperty}".`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return listeners;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
(function(_RecipleInteractionEvents) {
|
|
67
|
+
function getInteractionTypeFromInteraction(interaction) {
|
|
68
|
+
if (interaction.isAutocomplete()) return InteractionListenerType.Autocomplete;
|
|
69
|
+
else if (interaction.isChatInputCommand()) return InteractionListenerType.ChatInput;
|
|
70
|
+
else if (interaction.isContextMenuCommand()) return InteractionListenerType.ContextMenu;
|
|
71
|
+
else if (interaction.isModalSubmit()) return InteractionListenerType.ModalSubmit;
|
|
72
|
+
else if (interaction.isButton()) return InteractionListenerType.Button;
|
|
73
|
+
else if (interaction.isAnySelectMenu()) return InteractionListenerType.SelectMenu;
|
|
74
|
+
else if (interaction.isPrimaryEntryPointCommand()) return InteractionListenerType.PrimaryEntryPoint;
|
|
75
|
+
throw new Error(`Unknown interaction type.`);
|
|
76
|
+
}
|
|
77
|
+
_RecipleInteractionEvents.getInteractionTypeFromInteraction = getInteractionTypeFromInteraction;
|
|
78
|
+
})(RecipleInteractionEvents || (RecipleInteractionEvents = {}));
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
export { RecipleInteractionEvents };
|
|
82
|
+
//# sourceMappingURL=RecipleInteractionEvents.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecipleInteractionEvents.mjs","names":[],"sources":["../../../src/classes/modules/RecipleInteractionEvents.ts"],"sourcesContent":["import { type Interaction, type CacheType, type Awaitable, Collection } from 'discord.js';\nimport { ClientEventModule, type AnyModule } from 'reciple';\nimport { InteractionListener } from '../InteractionListener.js';\nimport { InteractionListenerType } from '../../helpers/constants.js';\n\nexport class RecipleInteractionEvents extends ClientEventModule<'interactionCreate'> implements RecipleInteractionEvents.Options {\n public readonly id: string = 'org.reciple.js.interaction-events';\n public event = 'interactionCreate' as const;\n public moduleEventListenersProperty: string|string[] = 'interactions';\n public ignoredModules?: (module: AnyModule) => Awaitable<boolean>;\n public listeners: Collection<string, InteractionListener<InteractionListenerType>> = new Collection();\n\n private readonly logger = useLogger().clone({ label: 'InteractionEvents' });\n\n constructor(options: RecipleInteractionEvents.Options = {}) {\n super();\n\n Object.assign(this, options);\n }\n\n public async onReady(): Promise<void> {\n this.client.modules.on('readyModules', async (modules: AnyModule[]) => {\n this.logger.debug(`Resolving listeners for ready modules...`);\n\n for (const listener of await this.resolveListeners(modules)) {\n this.listeners.set(listener.id, listener);\n }\n });\n\n this.client.modules.on('disabledModules', async (modules: AnyModule[]) => {\n this.logger.debug(`Sweeping listeners for disabled modules...`);\n\n for (const module of modules) {\n this.listeners.sweep(listener => listener.moduleId === module.id)\n }\n });\n }\n\n public async onEvent(interaction: Interaction<CacheType>): Promise<void> {\n const type = RecipleInteractionEvents.getInteractionTypeFromInteraction(interaction);\n const listeners = this.listeners.filter(listener => listener.type === type);\n\n this.logger.debug(`Triggered (${listeners.size}) listeners for interaction type \"${InteractionListenerType[type]}\".`);\n\n for (const [id, listener] of listeners) {\n if (!await Promise.resolve(listener.filter(interaction))) continue;\n\n await Promise.resolve(listener.execute(interaction));\n\n if (listener.once) {\n this.logger.debug(`Removed listener \"${id}\" for interaction type \"${InteractionListenerType[type]}\".`);\n this.listeners.delete(id);\n }\n }\n }\n\n public async resolveListeners(customModules?: AnyModule[]) {\n this.logger.debug(`Resolving listeners from (${customModules?.length ?? this.client.modules.cache.size}) modules...`);\n\n const modules = (customModules ?? this.client.modules.cache.map(m => m)) as RecipleInteractionEvents.ListenerModule<string, InteractionListenerType>[];\n const listenersProperty = Array.isArray(this.moduleEventListenersProperty) ? this.moduleEventListenersProperty : [this.moduleEventListenersProperty];\n const listeners: InteractionListener<InteractionListenerType>[] = [];\n\n for (const module of modules) {\n if (module.id === this.id) continue;\n\n this.logger.debug(`Resolving listeners from module \"${module.id}\"...`);\n\n propertyLoop: for (const listenerProperty of listenersProperty) {\n if (!(listenerProperty in module)) continue propertyLoop;\n if (!Array.isArray(module[listenerProperty]) || !module[listenerProperty].length) continue;\n if (typeof this.ignoredModules === 'function' && await Promise.resolve(this.ignoredModules(module))) continue;\n\n const moduleListeners = module[listenerProperty];\n\n this.logger.debug(`Resolving listeners from module \"${module.id}\" with property \"${listenerProperty}\"...`);\n\n for (const data of moduleListeners) {\n const listener = InteractionListener.from(data);\n\n Reflect.set(listener, 'moduleId', module.id);\n listeners.push(listener);\n }\n\n this.logger.debug(`Resolved (${moduleListeners.length}) listeners from module \"${module.id}\" with property \"${listenerProperty}\".`);\n }\n }\n\n return listeners;\n }\n}\n\nexport namespace RecipleInteractionEvents {\n export interface Options {\n /**\n * The property that is scanned from modules to get interaction listeners.\n */\n moduleEventListenersProperty?: string|string[];\n /**\n * Filter modules that should be ignored.\n */\n ignoredModules?: (module: AnyModule) => Awaitable<boolean>;\n }\n\n export type ListenerModule<P extends string, T extends InteractionListenerType> = AnyModule & Record<P, InteractionListener.Resolvable<T>[]>;\n\n export function getInteractionTypeFromInteraction(interaction: Interaction): InteractionListenerType {\n if (interaction.isAutocomplete()) {\n return InteractionListenerType.Autocomplete;\n } else if (interaction.isChatInputCommand()) {\n return InteractionListenerType.ChatInput;\n } else if (interaction.isContextMenuCommand()) {\n return InteractionListenerType.ContextMenu;\n } else if (interaction.isModalSubmit()) {\n return InteractionListenerType.ModalSubmit;\n } else if (interaction.isButton()) {\n return InteractionListenerType.Button;\n } else if (interaction.isAnySelectMenu()) {\n return InteractionListenerType.SelectMenu;\n } else if (interaction.isPrimaryEntryPointCommand()) {\n return InteractionListenerType.PrimaryEntryPoint;\n }\n\n throw new Error(`Unknown interaction type.`);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AnyCommandData, BaseModule, Config, MessageCommand } from "reciple";
|
|
2
|
+
|
|
3
|
+
//#region src/classes/modules/RecipleRegistryCache.d.ts
|
|
4
|
+
declare class RecipleRegistryCache extends BaseModule implements RecipleRegistryCache.Options {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
createCacheEntryEvenIfDisabled: boolean;
|
|
7
|
+
cacheDir: string;
|
|
8
|
+
maxCacheAgeMs?: number;
|
|
9
|
+
cacheEnabledEnv: string;
|
|
10
|
+
cached: boolean;
|
|
11
|
+
private readonly logger;
|
|
12
|
+
constructor(options?: RecipleRegistryCache.Options);
|
|
13
|
+
get cachePath(): string;
|
|
14
|
+
onReady(): Promise<void>;
|
|
15
|
+
readCacheEntry(): Promise<RecipleRegistryCache.CacheEntry | null>;
|
|
16
|
+
writeCacheEntry(entry?: RecipleRegistryCache.CacheEntry): Promise<void>;
|
|
17
|
+
clearCacheEntry(): Promise<void>;
|
|
18
|
+
createCacheEntry(): Promise<RecipleRegistryCache.CacheEntry>;
|
|
19
|
+
isCacheHit(cached: RecipleRegistryCache.CacheEntry, current: RecipleRegistryCache.CacheEntry): boolean;
|
|
20
|
+
private isEnabled;
|
|
21
|
+
}
|
|
22
|
+
declare namespace RecipleRegistryCache {
|
|
23
|
+
interface Options {
|
|
24
|
+
/**
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
createCacheEntryEvenIfDisabled?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* @default '.cache/reciple-registry/'
|
|
30
|
+
*/
|
|
31
|
+
cacheDir?: string;
|
|
32
|
+
/**
|
|
33
|
+
* @default 86400000 // 24 hours in milliseconds
|
|
34
|
+
*/
|
|
35
|
+
maxCacheAgeMs?: number;
|
|
36
|
+
/**
|
|
37
|
+
* @default 'RECIPLE_REGISTRY_CACHE'
|
|
38
|
+
*/
|
|
39
|
+
cacheEnabledEnv?: string;
|
|
40
|
+
}
|
|
41
|
+
interface CacheEntry {
|
|
42
|
+
commandsHash: string;
|
|
43
|
+
configHash: string;
|
|
44
|
+
createdAt: number;
|
|
45
|
+
}
|
|
46
|
+
type CommandData = Exclude<AnyCommandData, MessageCommand.Data>;
|
|
47
|
+
function checkClientIfEnabled(config: Config): boolean;
|
|
48
|
+
function createCommandsHash(commands: Exclude<AnyCommandData, MessageCommand.Data>[]): string;
|
|
49
|
+
function createConfigHash(config: Config): string;
|
|
50
|
+
function stringifyValue(object: any): string;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { RecipleRegistryCache };
|
|
54
|
+
//# sourceMappingURL=RecipleRegistryCache.d.mts.map
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { BaseModule, CommandType } from "reciple";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region src/classes/modules/RecipleRegistryCache.ts
|
|
7
|
+
var RecipleRegistryCache = class RecipleRegistryCache extends BaseModule {
|
|
8
|
+
id = "org.reciple.js.registry-cache";
|
|
9
|
+
createCacheEntryEvenIfDisabled = true;
|
|
10
|
+
cacheDir = path.join(process.cwd(), ".cache/reciple-registry/");
|
|
11
|
+
maxCacheAgeMs;
|
|
12
|
+
cacheEnabledEnv = "RECIPLE_REGISTRY_CACHE";
|
|
13
|
+
cached = false;
|
|
14
|
+
logger = useLogger().clone({ label: "RegistryCache" });
|
|
15
|
+
constructor(options) {
|
|
16
|
+
super();
|
|
17
|
+
Object.assign(this, options ?? {});
|
|
18
|
+
}
|
|
19
|
+
get cachePath() {
|
|
20
|
+
return path.join(this.cacheDir, this.client.application?.id ?? "cache.json");
|
|
21
|
+
}
|
|
22
|
+
async onReady() {
|
|
23
|
+
const enabled = this.isEnabled();
|
|
24
|
+
if (enabled || this.createCacheEntryEvenIfDisabled) this.client.commands?.once("applicationCommandsRegister", async (commands, guildId) => {
|
|
25
|
+
await this.writeCacheEntry();
|
|
26
|
+
});
|
|
27
|
+
if (!enabled) {
|
|
28
|
+
this.client.modules.on("readyModules", async () => {
|
|
29
|
+
this.logger.warn(`Registry cache is disabled. Cache is not used.`);
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.logger.debug("Looking for a application commands cache entry...");
|
|
34
|
+
const cachedEntry = await this.readCacheEntry();
|
|
35
|
+
this.logger.debug("Creating current application commands cache entry...");
|
|
36
|
+
const currentEntry = await this.createCacheEntry();
|
|
37
|
+
if (cachedEntry && this.isCacheHit(cachedEntry, currentEntry)) {
|
|
38
|
+
this.client.config.applicationCommandsRegister = {
|
|
39
|
+
...this.client.config.applicationCommandsRegister,
|
|
40
|
+
registerGlobally: false,
|
|
41
|
+
registerToGuilds: false
|
|
42
|
+
};
|
|
43
|
+
this.cached = true;
|
|
44
|
+
}
|
|
45
|
+
this.client.modules.on("readyModules", async () => {
|
|
46
|
+
this.logger.warn(`Application commands are ${this.cached ? "" : "not "}cached. ${this.cached ? "Skipping" : "Proceeding with"} registration.`);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async readCacheEntry() {
|
|
50
|
+
if (!await stat(this.cachePath).catch(() => null)) return null;
|
|
51
|
+
return await readFile(this.cachePath, "utf-8").then(JSON.parse).catch(() => null);
|
|
52
|
+
}
|
|
53
|
+
async writeCacheEntry(entry) {
|
|
54
|
+
entry ??= await this.createCacheEntry();
|
|
55
|
+
this.logger.debug("Writing application commands cache entry...");
|
|
56
|
+
await mkdir(this.cacheDir, { recursive: true });
|
|
57
|
+
await writeFile(this.cachePath, JSON.stringify(entry, null, 4), "utf-8");
|
|
58
|
+
this.logger.debug(`Application commands cache entry written to "${this.cachePath}".`);
|
|
59
|
+
}
|
|
60
|
+
async clearCacheEntry() {
|
|
61
|
+
await rm(this.cachePath).catch(() => null);
|
|
62
|
+
}
|
|
63
|
+
async createCacheEntry() {
|
|
64
|
+
this.logger.debug("Creating current application commands cache entry...");
|
|
65
|
+
const commands = Array.from(this.client.commands?.cache.filter((cmd) => cmd.type !== CommandType.Message).map((cmd) => cmd.toJSON()).values() ?? []);
|
|
66
|
+
for (const command of commands) Object.assign(command, { id: "[cache_value]" });
|
|
67
|
+
const commandsHash = RecipleRegistryCache.createCommandsHash(commands);
|
|
68
|
+
const configHash = RecipleRegistryCache.createConfigHash(this.client.config?.applicationCommandsRegister ?? {});
|
|
69
|
+
this.logger.debug(`Commands hash: ${commandsHash}`);
|
|
70
|
+
this.logger.debug(`Config hash: ${configHash}`);
|
|
71
|
+
this.logger.debug("Current application commands cache entry created.");
|
|
72
|
+
return {
|
|
73
|
+
createdAt: Date.now(),
|
|
74
|
+
commandsHash,
|
|
75
|
+
configHash
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
isCacheHit(cached, current) {
|
|
79
|
+
this.logger.debug("Comparing cache entry with current application commands and configuration...");
|
|
80
|
+
if (this.maxCacheAgeMs) {
|
|
81
|
+
const age = Date.now() - cached.createdAt;
|
|
82
|
+
if (age > this.maxCacheAgeMs) {
|
|
83
|
+
this.logger.debug(`Cache entry is too old (age: ${age}ms, max age: ${this.maxCacheAgeMs}ms).`);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (cached.commandsHash !== current.commandsHash) {
|
|
88
|
+
this.logger.debug(`Cache entry commands hash doesn't match (cached: ${cached.commandsHash}, current: ${current.commandsHash}).`);
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (cached.configHash !== current.configHash) {
|
|
92
|
+
this.logger.debug(`Cache entry config hash doesn't match (cached: ${cached.configHash}, current: ${current.configHash}).`);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
this.logger.debug("Cache entry matches current application commands and configuration.");
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
isEnabled() {
|
|
99
|
+
if (process.env[this.cacheEnabledEnv] !== "false" && process.env[this.cacheEnabledEnv] !== "0") return RecipleRegistryCache.checkClientIfEnabled(this.client.config ?? {});
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
(function(_RecipleRegistryCache) {
|
|
104
|
+
function checkClientIfEnabled(config) {
|
|
105
|
+
return config.applicationCommandsRegister?.registerGlobally || config.applicationCommandsRegister?.registerToGuilds === true || Array.isArray(config.applicationCommandsRegister?.registerToGuilds) && config.applicationCommandsRegister?.registerToGuilds.length > 0;
|
|
106
|
+
}
|
|
107
|
+
_RecipleRegistryCache.checkClientIfEnabled = checkClientIfEnabled;
|
|
108
|
+
function createCommandsHash(commands) {
|
|
109
|
+
const hash = createHash("sha256");
|
|
110
|
+
hash.update(stringifyValue(commands));
|
|
111
|
+
return hash.digest("hex");
|
|
112
|
+
}
|
|
113
|
+
_RecipleRegistryCache.createCommandsHash = createCommandsHash;
|
|
114
|
+
function createConfigHash(config) {
|
|
115
|
+
const hash = createHash("sha256");
|
|
116
|
+
const relevantConfig = { applicationCommandsRegister: config.applicationCommandsRegister };
|
|
117
|
+
hash.update(stringifyValue(relevantConfig));
|
|
118
|
+
return hash.digest("hex");
|
|
119
|
+
}
|
|
120
|
+
_RecipleRegistryCache.createConfigHash = createConfigHash;
|
|
121
|
+
function stringifyValue(object) {
|
|
122
|
+
switch (typeof object) {
|
|
123
|
+
case "string": return `"${object}"`;
|
|
124
|
+
case "number":
|
|
125
|
+
case "bigint":
|
|
126
|
+
case "boolean":
|
|
127
|
+
case "symbol":
|
|
128
|
+
case "function": return `"${object.toString()}"`;
|
|
129
|
+
case "undefined": return "undefined";
|
|
130
|
+
case "object":
|
|
131
|
+
if (object === null) return "null";
|
|
132
|
+
if (Array.isArray(object)) return `[${object.map((s) => stringifyValue(s)).join(", ")}]`;
|
|
133
|
+
if (object instanceof Map) return `{${Array.from(object.entries()).map(([k, v]) => `${stringifyValue(k)}: ${stringifyValue(v)}`).join(", ")}}`;
|
|
134
|
+
if (object instanceof Set) return `{${Array.from(object.values()).map((v) => stringifyValue(v)).join(", ")}}`;
|
|
135
|
+
if (object instanceof Date) return `"${object.toISOString()}"`;
|
|
136
|
+
try {
|
|
137
|
+
const newObject = {};
|
|
138
|
+
for (const [key, value] of Object.entries(object)) newObject[key] = stringifyValue(value);
|
|
139
|
+
return JSON.stringify(newObject);
|
|
140
|
+
} catch {
|
|
141
|
+
return String(object);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
_RecipleRegistryCache.stringifyValue = stringifyValue;
|
|
146
|
+
})(RecipleRegistryCache || (RecipleRegistryCache = {}));
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
export { RecipleRegistryCache };
|
|
150
|
+
//# sourceMappingURL=RecipleRegistryCache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecipleRegistryCache.mjs","names":[],"sources":["../../../src/classes/modules/RecipleRegistryCache.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { BaseModule, CommandType, type AnyCommandData, type Config, type MessageCommand } from 'reciple';\n\nexport class RecipleRegistryCache extends BaseModule implements RecipleRegistryCache.Options {\n public readonly id: string = 'org.reciple.js.registry-cache';\n\n public createCacheEntryEvenIfDisabled: boolean = true;\n public cacheDir: string = path.join(process.cwd(), '.cache/reciple-registry/');\n public maxCacheAgeMs?: number;\n public cacheEnabledEnv: string = 'RECIPLE_REGISTRY_CACHE';\n public cached: boolean = false;\n\n private readonly logger = useLogger().clone({ label: 'RegistryCache' });\n\n constructor(options?: RecipleRegistryCache.Options) {\n super();\n Object.assign(this, options ?? {});\n }\n\n get cachePath(): string {\n return path.join(this.cacheDir, this.client.application?.id ?? 'cache.json');\n }\n\n public async onReady(): Promise<void> {\n const enabled = this.isEnabled();\n\n if (enabled || this.createCacheEntryEvenIfDisabled) {\n this.client.commands?.once('applicationCommandsRegister', async (commands, guildId) => {\n await this.writeCacheEntry();\n });\n }\n\n if (!enabled) {\n this.client.modules.on('readyModules', async () => {\n this.logger.warn(`Registry cache is disabled. Cache is not used.`);\n });\n\n return;\n }\n\n this.logger.debug('Looking for a application commands cache entry...');\n const cachedEntry = await this.readCacheEntry();\n\n this.logger.debug('Creating current application commands cache entry...');\n const currentEntry = await this.createCacheEntry();\n\n if (cachedEntry && this.isCacheHit(cachedEntry, currentEntry)) {\n this.client.config!.applicationCommandsRegister = {\n ...this.client.config!.applicationCommandsRegister,\n registerGlobally: false,\n registerToGuilds: false\n }\n\n this.cached = true;\n }\n\n this.client.modules.on('readyModules', async () => {\n this.logger.warn(`Application commands are ${this.cached ? '' : 'not '}cached. ${this.cached ? 'Skipping' : 'Proceeding with'} registration.`);\n });\n }\n\n public async readCacheEntry(): Promise<RecipleRegistryCache.CacheEntry|null> {\n const stats = await stat(this.cachePath).catch(() => null);\n if (!stats) return null;\n\n return await readFile(this.cachePath, 'utf-8').then(JSON.parse).catch(() => null);\n }\n\n public async writeCacheEntry(entry?: RecipleRegistryCache.CacheEntry): Promise<void> {\n entry ??= await this.createCacheEntry();\n\n this.logger.debug('Writing application commands cache entry...');\n await mkdir(this.cacheDir, { recursive: true });\n await writeFile(this.cachePath, JSON.stringify(entry, null, 4), 'utf-8');\n\n this.logger.debug(`Application commands cache entry written to \"${this.cachePath}\".`);\n }\n\n public async clearCacheEntry(): Promise<void> {\n await rm(this.cachePath).catch(() => null);\n }\n\n public async createCacheEntry(): Promise<RecipleRegistryCache.CacheEntry> {\n this.logger.debug('Creating current application commands cache entry...');\n const commands = Array.from(this.client.commands?.cache.filter(cmd => cmd.type !== CommandType.Message).map(cmd => cmd.toJSON()).values() ?? []);\n\n for (const command of commands) {\n Object.assign(command, { id: \"[cache_value]\" });\n }\n\n const commandsHash = RecipleRegistryCache.createCommandsHash(commands);\n const configHash = RecipleRegistryCache.createConfigHash(this.client.config?.applicationCommandsRegister ?? {});\n\n this.logger.debug(`Commands hash: ${commandsHash}`);\n this.logger.debug(`Config hash: ${configHash}`);\n this.logger.debug('Current application commands cache entry created.');\n\n return {\n createdAt: Date.now(),\n commandsHash,\n configHash\n };\n }\n\n public isCacheHit(cached: RecipleRegistryCache.CacheEntry, current: RecipleRegistryCache.CacheEntry): boolean {\n this.logger.debug('Comparing cache entry with current application commands and configuration...');\n if (this.maxCacheAgeMs) {\n const age = Date.now() - cached.createdAt;\n if (age > this.maxCacheAgeMs) {\n this.logger.debug(`Cache entry is too old (age: ${age}ms, max age: ${this.maxCacheAgeMs}ms).`);\n return false;\n }\n }\n\n if (cached.commandsHash !== current.commandsHash) {\n this.logger.debug(`Cache entry commands hash doesn't match (cached: ${cached.commandsHash}, current: ${current.commandsHash}).`);\n return false;\n }\n if (cached.configHash !== current.configHash) {\n this.logger.debug(`Cache entry config hash doesn't match (cached: ${cached.configHash}, current: ${current.configHash}).`);\n return false;\n }\n\n this.logger.debug('Cache entry matches current application commands and configuration.');\n return true;\n }\n\n private isEnabled(): boolean {\n if (process.env[this.cacheEnabledEnv] !== 'false' && process.env[this.cacheEnabledEnv] !== '0') {\n return RecipleRegistryCache.checkClientIfEnabled(this.client.config ?? {});\n }\n\n return false;\n }\n}\n\nexport namespace RecipleRegistryCache {\n export interface Options {\n /**\n * @default true\n */\n createCacheEntryEvenIfDisabled?: boolean;\n /**\n * @default '.cache/reciple-registry/'\n */\n cacheDir?: string;\n /**\n * @default 86400000 // 24 hours in milliseconds\n */\n maxCacheAgeMs?: number;\n /**\n * @default 'RECIPLE_REGISTRY_CACHE'\n */\n cacheEnabledEnv?: string;\n }\n\n export interface CacheEntry {\n commandsHash: string;\n configHash: string;\n createdAt: number;\n }\n\n export type CommandData = Exclude<AnyCommandData, MessageCommand.Data>;\n\n export function checkClientIfEnabled(config: Config): boolean {\n return config.applicationCommandsRegister?.registerGlobally\n || config.applicationCommandsRegister?.registerToGuilds === true\n || (\n Array.isArray(config.applicationCommandsRegister?.registerToGuilds)\n && config.applicationCommandsRegister?.registerToGuilds.length > 0\n );\n }\n\n export function createCommandsHash(commands: Exclude<AnyCommandData, MessageCommand.Data>[]): string {\n const hash = createHash('sha256');\n\n hash.update(stringifyValue(commands));\n\n return hash.digest('hex');\n }\n\n export function createConfigHash(config: Config): string {\n const hash = createHash('sha256');\n const relevantConfig = { applicationCommandsRegister: config.applicationCommandsRegister };\n\n hash.update(stringifyValue(relevantConfig));\n\n return hash.digest('hex');\n }\n\n export function stringifyValue(object: any): string {\n switch (typeof object) {\n case 'string':\n return `\"${object}\"`;\n case 'number':\n case 'bigint':\n case 'boolean':\n case 'symbol':\n case 'function':\n return `\"${object.toString()}\"`;\n case 'undefined':\n return 'undefined';\n case 'object':\n if (object === null) {\n return 'null';\n }\n\n if (Array.isArray(object)) {\n return `[${object.map(s => stringifyValue(s)).join(', ')}]`;\n }\n\n if (object instanceof Map) {\n return `{${Array.from(object.entries()).map(([k, v]) => `${stringifyValue(k)}: ${stringifyValue(v)}`).join(', ')}}`;\n }\n\n if (object instanceof Set) {\n return `{${Array.from(object.values()).map(v => stringifyValue(v)).join(', ')}}`;\n }\n\n if (object instanceof Date) {\n return `\"${object.toISOString()}\"`;\n }\n\n try {\n const newObject: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(object)) {\n newObject[key] = stringifyValue(value);\n }\n\n return JSON.stringify(newObject);\n } catch {\n return String(object);\n }\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/helpers/constants.d.ts
|
|
2
|
+
declare enum InteractionListenerType {
|
|
3
|
+
Autocomplete = 1,
|
|
4
|
+
ContextMenu = 2,
|
|
5
|
+
ChatInput = 3,
|
|
6
|
+
Button = 4,
|
|
7
|
+
ModalSubmit = 5,
|
|
8
|
+
SelectMenu = 6,
|
|
9
|
+
PrimaryEntryPoint = 7
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { InteractionListenerType };
|
|
13
|
+
//# sourceMappingURL=constants.d.mts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/helpers/constants.ts
|
|
2
|
+
let InteractionListenerType = /* @__PURE__ */ function(InteractionListenerType) {
|
|
3
|
+
InteractionListenerType[InteractionListenerType["Autocomplete"] = 1] = "Autocomplete";
|
|
4
|
+
InteractionListenerType[InteractionListenerType["ContextMenu"] = 2] = "ContextMenu";
|
|
5
|
+
InteractionListenerType[InteractionListenerType["ChatInput"] = 3] = "ChatInput";
|
|
6
|
+
InteractionListenerType[InteractionListenerType["Button"] = 4] = "Button";
|
|
7
|
+
InteractionListenerType[InteractionListenerType["ModalSubmit"] = 5] = "ModalSubmit";
|
|
8
|
+
InteractionListenerType[InteractionListenerType["SelectMenu"] = 6] = "SelectMenu";
|
|
9
|
+
InteractionListenerType[InteractionListenerType["PrimaryEntryPoint"] = 7] = "PrimaryEntryPoint";
|
|
10
|
+
return InteractionListenerType;
|
|
11
|
+
}({});
|
|
12
|
+
|
|
13
|
+
//#endregion
|
|
14
|
+
export { InteractionListenerType };
|
|
15
|
+
//# sourceMappingURL=constants.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.mjs","names":[],"sources":["../../src/helpers/constants.ts"],"sourcesContent":["export enum InteractionListenerType {\n Autocomplete = 1,\n ContextMenu,\n ChatInput,\n Button,\n ModalSubmit,\n SelectMenu,\n PrimaryEntryPoint\n}\n"],"mappings":";AAAA,IAAY,4EAAL;AACH;AACA;AACA;AACA;AACA;AACA;AACA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { InteractionListenerType } from "./constants.mjs";
|
|
2
|
+
import { AnySelectMenuInteraction, AutocompleteInteraction, Awaitable, ButtonInteraction, ChatInputCommandInteraction, ContextMenuCommandInteraction, Interaction, ModalSubmitInteraction, PrimaryEntryPointCommandInteraction } from "discord.js";
|
|
3
|
+
|
|
4
|
+
//#region src/helpers/types.d.ts
|
|
5
|
+
interface InteractionListenerData<T extends InteractionListenerType = InteractionListenerType> {
|
|
6
|
+
type: T;
|
|
7
|
+
once?: boolean;
|
|
8
|
+
filter?: (interaction: InteractionFromListenerType<T>) => Awaitable<boolean>;
|
|
9
|
+
execute: (interaction: InteractionFromListenerType<T>) => Awaitable<void>;
|
|
10
|
+
}
|
|
11
|
+
type AnyCommandInteraction = AutocompleteInteraction | ChatInputCommandInteraction | ContextMenuCommandInteraction | PrimaryEntryPointCommandInteraction;
|
|
12
|
+
type AnyComponentInteraction = ButtonInteraction | ModalSubmitInteraction | AnySelectMenuInteraction;
|
|
13
|
+
type InteractionFromListenerType<T extends InteractionListenerType> = T extends InteractionListenerType.Autocomplete ? AutocompleteInteraction : T extends InteractionListenerType.ChatInput ? ChatInputCommandInteraction : T extends InteractionListenerType.ContextMenu ? ContextMenuCommandInteraction : T extends InteractionListenerType.Button ? ButtonInteraction : T extends InteractionListenerType.ModalSubmit ? ModalSubmitInteraction : T extends InteractionListenerType.SelectMenu ? AnySelectMenuInteraction : T extends InteractionListenerType.PrimaryEntryPoint ? PrimaryEntryPointCommandInteraction : Interaction;
|
|
14
|
+
type ListenerTypeFromInteraction<T extends InteractionFromListenerType<InteractionListenerType>> = T extends AutocompleteInteraction ? InteractionListenerType.Autocomplete : T extends ChatInputCommandInteraction ? InteractionListenerType.ChatInput : T extends ContextMenuCommandInteraction ? InteractionListenerType.ContextMenu : T extends ButtonInteraction ? InteractionListenerType.Button : T extends ModalSubmitInteraction ? InteractionListenerType.ModalSubmit : T extends AnySelectMenuInteraction ? InteractionListenerType.SelectMenu : T extends PrimaryEntryPointCommandInteraction ? InteractionListenerType.PrimaryEntryPoint : InteractionListenerType;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { AnyCommandInteraction, AnyComponentInteraction, InteractionFromListenerType, InteractionListenerData, ListenerTypeFromInteraction };
|
|
17
|
+
//# sourceMappingURL=types.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { InteractionListenerType } from "./helpers/constants.mjs";
|
|
2
|
+
import { AnyCommandInteraction, AnyComponentInteraction, InteractionFromListenerType, InteractionListenerData, ListenerTypeFromInteraction } from "./helpers/types.mjs";
|
|
3
|
+
import { InteractionListener } from "./classes/InteractionListener.mjs";
|
|
4
|
+
import { InteractionListenerBuilder } from "./classes/builders/InteractionListenerBuilder.mjs";
|
|
5
|
+
import { RecipleAnticrash } from "./classes/modules/RecipleAnticrash.mjs";
|
|
6
|
+
import { RecipleInteractionEvents } from "./classes/modules/RecipleInteractionEvents.mjs";
|
|
7
|
+
import { RecipleRegistryCache } from "./classes/modules/RecipleRegistryCache.mjs";
|
|
8
|
+
export { AnyCommandInteraction, AnyComponentInteraction, InteractionFromListenerType, InteractionListener, InteractionListenerBuilder, InteractionListenerData, InteractionListenerType, ListenerTypeFromInteraction, RecipleAnticrash, RecipleInteractionEvents, RecipleRegistryCache };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { InteractionListenerType } from "./helpers/constants.mjs";
|
|
2
|
+
import { InteractionListenerBuilder } from "./classes/builders/InteractionListenerBuilder.mjs";
|
|
3
|
+
import { RecipleAnticrash } from "./classes/modules/RecipleAnticrash.mjs";
|
|
4
|
+
import { InteractionListener } from "./classes/InteractionListener.mjs";
|
|
5
|
+
import { RecipleInteractionEvents } from "./classes/modules/RecipleInteractionEvents.mjs";
|
|
6
|
+
import { RecipleRegistryCache } from "./classes/modules/RecipleRegistryCache.mjs";
|
|
7
|
+
|
|
8
|
+
export { InteractionListener, InteractionListenerBuilder, InteractionListenerType, RecipleAnticrash, RecipleInteractionEvents, RecipleRegistryCache };
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reciple/modules",
|
|
3
|
+
"version": "10.0.1",
|
|
4
|
+
"license": "LGPL-3.0-only",
|
|
5
|
+
"description": "A collection of useful modules for reciple",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.mts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.mts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clean": "rimraf ./dist",
|
|
21
|
+
"build": "tsdown --config-loader unrun",
|
|
22
|
+
"docs": "deno doc --json --sloppy-imports ./src/index.ts > docs.json",
|
|
23
|
+
"check": "tsc --noEmit",
|
|
24
|
+
"prepack": "bun run build"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/thenorthsolution/reciple",
|
|
29
|
+
"directory": "packages/modules"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"discord.js": "^14.25.1",
|
|
33
|
+
"reciple": "^10.0.1"
|
|
34
|
+
},
|
|
35
|
+
"peerDependenciesMeta": {
|
|
36
|
+
"discord.js": {
|
|
37
|
+
"optional": false
|
|
38
|
+
},
|
|
39
|
+
"reciple": {
|
|
40
|
+
"optional": false
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@sapphire/snowflake": "^3.5.5"
|
|
48
|
+
}
|
|
49
|
+
}
|