meocord 4.0.0-beta.1 → 4.0.0-beta.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # meocord
2
2
 
3
+ ## 4.0.0-beta.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#40](https://github.com/l7aromeo/meocord/pull/40) [`3629edd`](https://github.com/l7aromeo/meocord/commit/3629eddaf20bd0947d24e5582aa167b2c0ca0d47) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Lint `meocord.config.ts`. The shared ESLint config from `meocord/eslint` ignored it, so the config
8
+ alone skipped the rules every other file follows — unused imports, formatting. It is linted like the
9
+ rest now; the typecheck it already gets is unchanged.
10
+
11
+ - [#40](https://github.com/l7aromeo/meocord/pull/40) [`80b8302`](https://github.com/l7aromeo/meocord/commit/80b83021894ef8a964a12ac1610c2ec9b40f344e) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Exit with code 1 when the login fails, whatever the entry point does. `app.start()` now sets
12
+ `process.exitCode = 1` before rejecting, so an existing `main.ts` whose `catch` only logs the error no
13
+ longer exits 0 — no `process.exitCode = 1` needs adding to it, and new applications' `main.ts` no
14
+ longer carries one.
15
+
16
+ ## 4.0.0-beta.2
17
+
18
+ ### Major Changes
19
+
20
+ - [#36](https://github.com/l7aromeo/meocord/pull/36) [`45e564e`](https://github.com/l7aromeo/meocord/commit/45e564e6baff17313037372ec56baa1711a3584c) Thanks [@l7aromeo](https://github.com/l7aromeo)! - **Breaking:** `app.start()` rejects when the login fails. It logged the error and resolved, so a bot
21
+ with an invalid token went on to log "Application started" and exit with code 0, which Docker's
22
+ `restart: on-failure`, systemd and CI all read as success. New applications' `main.ts` sets
23
+ `process.exitCode = 1` when startup fails; add the same to an existing entry point's `catch`. See the
24
+ [migration guide](https://github.com/l7aromeo/meocord/blob/main/docs/MIGRATING.md#4-build-and-start).
25
+
26
+ ### Patch Changes
27
+
28
+ - [#37](https://github.com/l7aromeo/meocord/pull/37) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Type the CommonJS build as CommonJS. Every entry point's `require` condition resolved to the ESM
29
+ declarations, so a CommonJS TypeScript project was told `meocord/core` is an ES module it cannot
30
+ `require`, even with `skipLibCheck`. Each entry now ships `.d.cts` declarations for `require`, and
31
+ `meocord/eslint` types its `module.exports` array as what `require` returns.
32
+
33
+ - [#35](https://github.com/l7aromeo/meocord/pull/35) [`b93a771`](https://github.com/l7aromeo/meocord/commit/b93a771b19664345fec4222d5a2c29dcf5b31a0d) Thanks [@l7aromeo](https://github.com/l7aromeo)! - Generate code that passes a new application's own `lint`. A generated guard failed `tsc` and ESLint
34
+ — `GuardInterface` imported as a value, and an unused `context` parameter — and generated message and
35
+ reaction controllers imported names they never used, which only the application's ESLint, run in the
36
+ background after generating, removed. New applications also typecheck `meocord.config.ts`: the
37
+ template's `tsconfig.json` includes it instead of excluding it, so a type error in the config fails
38
+ `lint`, and editors resolve `paths` aliases imported there. `noEmit` stays on, so `tsc` writes nothing
39
+ beside it.
40
+
3
41
  ## 4.0.0-beta.1
4
42
 
5
43
  ### Major Changes
@@ -106,28 +106,45 @@ class MeoCordApp {
106
106
  }
107
107
  return this.controllerInstancesCache.get(controllerClass);
108
108
  }
109
- async start() {
109
+ /**
110
+ * Registers the Discord event handlers and logs in.
111
+ *
112
+ * Rejects when the login fails -- an invalid token, or Discord unreachable -- and sets the exit
113
+ * code to 1 first. A bot that never came online is a failed start, and a supervisor such as
114
+ * Docker's `restart: on-failure` or systemd can only tell if the process says so. The exit code is
115
+ * set here rather than left to the entry point because an entry point that catches the rejection
116
+ * to log it has handled it, and the process would otherwise end with 0. A later `start()` that
117
+ * logs in -- an entry point retrying -- clears the code again, if this is what set it.
118
+ */ async start() {
119
+ this.logger.log('Starting bot...');
120
+ this.bot.on('clientReady', ()=>this.runListener('clientReady', async ()=>{
121
+ this.activityInterval = setInterval(()=>this.updateActivity(), 10000);
122
+ await this.registerCommands();
123
+ }));
124
+ this.bot.on('interactionCreate', (interaction)=>this.runListener('interactionCreate', ()=>this.handleInteraction(interaction)));
125
+ this.bot.on('messageCreate', (message)=>this.runListener('messageCreate', ()=>this.handleMessage(message)));
126
+ this.bot.on('messageReactionAdd', (reaction, user)=>this.runListener('messageReactionAdd', ()=>this.handleReaction(reaction, {
127
+ user,
128
+ action: enum_index.ReactionHandlerAction.ADD
129
+ })));
130
+ this.bot.on('messageReactionRemove', (reaction, user)=>this.runListener('messageReactionRemove', ()=>this.handleReaction(reaction, {
131
+ user,
132
+ action: enum_index.ReactionHandlerAction.REMOVE
133
+ })));
110
134
  try {
111
- this.logger.log('Starting bot...');
112
- this.bot.on('clientReady', ()=>this.runListener('clientReady', async ()=>{
113
- this.activityInterval = setInterval(()=>this.updateActivity(), 10000);
114
- await this.registerCommands();
115
- }));
116
- this.bot.on('interactionCreate', (interaction)=>this.runListener('interactionCreate', ()=>this.handleInteraction(interaction)));
117
- this.bot.on('messageCreate', (message)=>this.runListener('messageCreate', ()=>this.handleMessage(message)));
118
- this.bot.on('messageReactionAdd', (reaction, user)=>this.runListener('messageReactionAdd', ()=>this.handleReaction(reaction, {
119
- user,
120
- action: enum_index.ReactionHandlerAction.ADD
121
- })));
122
- this.bot.on('messageReactionRemove', (reaction, user)=>this.runListener('messageReactionRemove', ()=>this.handleReaction(reaction, {
123
- user,
124
- action: enum_index.ReactionHandlerAction.REMOVE
125
- })));
126
135
  await this.bot.login(this.discordToken);
127
- this.logger.log('Bot is online!');
128
136
  } catch (error) {
129
- this.logger.error('Error during bot startup:', error);
137
+ if (process.exitCode === undefined || process.exitCode === 0) {
138
+ process.exitCode = 1;
139
+ MeoCordApp.failedLoginSetExitCode = true;
140
+ }
141
+ throw error;
142
+ }
143
+ if (MeoCordApp.failedLoginSetExitCode && process.exitCode === 1) {
144
+ process.exitCode = undefined;
145
+ MeoCordApp.failedLoginSetExitCode = false;
130
146
  }
147
+ this.logger.log('Bot is online!');
131
148
  }
132
149
  async registerCommands() {
133
150
  // Keyed by type and name together, because that pair is what Discord treats as one
@@ -506,6 +523,7 @@ class MeoCordApp {
506
523
  process.on('SIGTERM', ()=>this.gracefulShutdown());
507
524
  }
508
525
  }
526
+ /** Whether a failed login set the process exit code, so a later successful one knows to clear it. */ MeoCordApp.failedLoginSetExitCode = false;
509
527
 
510
528
  /**
511
529
  * File written beside a bundle that carries native addons, naming the platform it was built for.
@@ -25,6 +25,6 @@
25
25
  },
26
26
  "types": ["node"]
27
27
  },
28
- "include": ["src/**/*.ts"],
29
- "exclude": ["meocord.config.ts", "dist", "vitest.config.ts", "node_modules", "src/**/*.spec.ts"]
28
+ "include": ["src/**/*.ts", "meocord.config.ts"],
29
+ "exclude": ["dist", "vitest.config.ts", "node_modules", "src/**/*.spec.ts"]
30
30
  }
@@ -1,5 +1,5 @@
1
- import { MessageHandler, Controller, Command } from 'meocord/decorator'
2
- import { EmbedBuilder, Message } from 'discord.js'
1
+ import { MessageHandler, Controller } from 'meocord/decorator'
2
+ import { Message } from 'discord.js'
3
3
  import { Logger } from 'meocord/common'
4
4
 
5
5
  @Controller()
@@ -1,7 +1,7 @@
1
- import { MessageReaction, User } from 'discord.js'
1
+ import { MessageReaction } from 'discord.js'
2
2
  import { Controller, ReactionHandler } from 'meocord/decorator'
3
3
  import { Logger } from 'meocord/common'
4
- import { CommandType, ReactionHandlerAction } from 'meocord/enum'
4
+ import { ReactionHandlerAction } from 'meocord/enum'
5
5
  import { type ReactionHandlerOptions } from 'meocord/interface'
6
6
 
7
7
  @Controller()
@@ -10,12 +10,12 @@ export class {{className}}ReactionController {
10
10
 
11
11
  @ReactionHandler('😋')
12
12
  async handleReaction(reaction: MessageReaction, { user, action }: ReactionHandlerOptions) {
13
- this.logger.log(`Reaction 😋 ${action === ReactionHandlerAction.ADD ? 'added' : 'removed'}.`);
13
+ this.logger.log(`Reaction 😋 ${action === ReactionHandlerAction.ADD ? 'added' : 'removed'}.`)
14
14
 
15
- if (!reaction.message) return;
15
+ if (!reaction.message) return
16
16
 
17
17
  if (action === ReactionHandlerAction.ADD) {
18
- await reaction.message.reply(`${user.username} reacted with 😋!`);
18
+ await reaction.message.reply(`${user.username} reacted with 😋!`)
19
19
  }
20
20
  }
21
21
 
@@ -1,10 +1,10 @@
1
1
  import { Guard } from 'meocord/decorator'
2
- import { GuardInterface } from 'meocord/interface'
2
+ import { type GuardInterface } from 'meocord/interface'
3
3
  import { BaseInteraction, Message, MessageReaction } from 'discord.js'
4
4
 
5
5
  @Guard()
6
6
  export class {{className}}Guard implements GuardInterface {
7
- async canActivate(context: BaseInteraction | Message | MessageReaction): Promise<boolean> {
7
+ async canActivate(_context: BaseInteraction | Message | MessageReaction): Promise<boolean> {
8
8
  // TODO: Implement the guard logic to determine if the interaction is allowed
9
9
  return true
10
10
  }
@@ -88,28 +88,45 @@ class MeoCordApp {
88
88
  }
89
89
  return this.controllerInstancesCache.get(controllerClass);
90
90
  }
91
- async start() {
91
+ /**
92
+ * Registers the Discord event handlers and logs in.
93
+ *
94
+ * Rejects when the login fails -- an invalid token, or Discord unreachable -- and sets the exit
95
+ * code to 1 first. A bot that never came online is a failed start, and a supervisor such as
96
+ * Docker's `restart: on-failure` or systemd can only tell if the process says so. The exit code is
97
+ * set here rather than left to the entry point because an entry point that catches the rejection
98
+ * to log it has handled it, and the process would otherwise end with 0. A later `start()` that
99
+ * logs in -- an entry point retrying -- clears the code again, if this is what set it.
100
+ */ async start() {
101
+ this.logger.log('Starting bot...');
102
+ this.bot.on('clientReady', ()=>this.runListener('clientReady', async ()=>{
103
+ this.activityInterval = setInterval(()=>this.updateActivity(), 10000);
104
+ await this.registerCommands();
105
+ }));
106
+ this.bot.on('interactionCreate', (interaction)=>this.runListener('interactionCreate', ()=>this.handleInteraction(interaction)));
107
+ this.bot.on('messageCreate', (message)=>this.runListener('messageCreate', ()=>this.handleMessage(message)));
108
+ this.bot.on('messageReactionAdd', (reaction, user)=>this.runListener('messageReactionAdd', ()=>this.handleReaction(reaction, {
109
+ user,
110
+ action: ReactionHandlerAction.ADD
111
+ })));
112
+ this.bot.on('messageReactionRemove', (reaction, user)=>this.runListener('messageReactionRemove', ()=>this.handleReaction(reaction, {
113
+ user,
114
+ action: ReactionHandlerAction.REMOVE
115
+ })));
92
116
  try {
93
- this.logger.log('Starting bot...');
94
- this.bot.on('clientReady', ()=>this.runListener('clientReady', async ()=>{
95
- this.activityInterval = setInterval(()=>this.updateActivity(), 10000);
96
- await this.registerCommands();
97
- }));
98
- this.bot.on('interactionCreate', (interaction)=>this.runListener('interactionCreate', ()=>this.handleInteraction(interaction)));
99
- this.bot.on('messageCreate', (message)=>this.runListener('messageCreate', ()=>this.handleMessage(message)));
100
- this.bot.on('messageReactionAdd', (reaction, user)=>this.runListener('messageReactionAdd', ()=>this.handleReaction(reaction, {
101
- user,
102
- action: ReactionHandlerAction.ADD
103
- })));
104
- this.bot.on('messageReactionRemove', (reaction, user)=>this.runListener('messageReactionRemove', ()=>this.handleReaction(reaction, {
105
- user,
106
- action: ReactionHandlerAction.REMOVE
107
- })));
108
117
  await this.bot.login(this.discordToken);
109
- this.logger.log('Bot is online!');
110
118
  } catch (error) {
111
- this.logger.error('Error during bot startup:', error);
119
+ if (process.exitCode === undefined || process.exitCode === 0) {
120
+ process.exitCode = 1;
121
+ MeoCordApp.failedLoginSetExitCode = true;
122
+ }
123
+ throw error;
124
+ }
125
+ if (MeoCordApp.failedLoginSetExitCode && process.exitCode === 1) {
126
+ process.exitCode = undefined;
127
+ MeoCordApp.failedLoginSetExitCode = false;
112
128
  }
129
+ this.logger.log('Bot is online!');
113
130
  }
114
131
  async registerCommands() {
115
132
  // Keyed by type and name together, because that pair is what Discord treats as one
@@ -488,5 +505,6 @@ class MeoCordApp {
488
505
  process.on('SIGTERM', ()=>this.gracefulShutdown());
489
506
  }
490
507
  }
508
+ /** Whether a failed login set the process exit code, so a later successful one knows to clear it. */ MeoCordApp.failedLoginSetExitCode = false;
491
509
 
492
510
  export { MeoCordApp };
@@ -1,4 +1,4 @@
1
- var version = "4.0.0-beta.1";
1
+ var version = "4.0.0-beta.3";
2
2
  var packageJson = {
3
3
  version: version};
4
4
 
@@ -0,0 +1,53 @@
1
+ import { ColorResolvable } from 'discord.js';
2
+
3
+ declare class Logger {
4
+ private context?;
5
+ private readonly colorMap;
6
+ constructor(context?: string | undefined);
7
+ log(...args: any[]): void;
8
+ info(...args: any[]): void;
9
+ warn(...args: any[]): void;
10
+ error(...args: any[]): void;
11
+ debug(...args: any[]): void;
12
+ verbose(...args: any[]): void;
13
+ private formatMessage;
14
+ private logWithContext;
15
+ }
16
+
17
+ declare class Theme {
18
+ static successColor: ColorResolvable;
19
+ static infoColor: ColorResolvable;
20
+ static errorColor: ColorResolvable;
21
+ static warningColor: ColorResolvable;
22
+ }
23
+
24
+ /**
25
+ * Composes multiple class or method decorators into a single decorator.
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * export const Protected = () => applyDecorators(
30
+ * UseGuard(DefaultGuard, GlobalRateLimiterGuard),
31
+ * )
32
+ *
33
+ * @Controller()
34
+ * @Protected()
35
+ * export class PingController {}
36
+ * ```
37
+ */
38
+ declare function applyDecorators(...decorators: (ClassDecorator | MethodDecorator)[]): ClassDecorator & MethodDecorator;
39
+ /**
40
+ * Attaches arbitrary metadata to a class or method. Use alongside `Reflect.getMetadata` to read it back.
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
45
+ *
46
+ * @Command('admin', CommandType.SLASH)
47
+ * @Roles('admin', 'moderator')
48
+ * async adminCommand(interaction: ChatInputCommandInteraction) {}
49
+ * ```
50
+ */
51
+ declare function SetMetadata<V = any>(metadataKey: string, metadataValue: V): ClassDecorator & MethodDecorator;
52
+
53
+ export { Logger, SetMetadata, Theme, applyDecorators };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The kinds of interaction a `@Command` method can be bound to.
3
+ *
4
+ * Each member names one Discord interaction shape rather than a family of them, so a
5
+ * handler's parameter type follows from its command type alone. That is why the four
6
+ * entity select menus are separate members instead of one `SELECT_MENU`: Discord sends
7
+ * them as distinct component types (5-8) carrying different resolved data, and
8
+ * collapsing them would leave the handler with a union it has to re-narrow by hand.
9
+ */
10
+ declare enum CommandType {
11
+ /** Chat input command, or one subcommand of it. */
12
+ SLASH = "SLASH",
13
+ /** User or message context menu command. */
14
+ CONTEXT_MENU = "CONTEXT_MENU",
15
+ /** Activity launch command (`ApplicationCommandType.PrimaryEntryPoint`). */
16
+ PRIMARY_ENTRY_POINT = "PRIMARY_ENTRY_POINT",
17
+ BUTTON = "BUTTON",
18
+ /** String select menu — the one whose options the application defines itself. */
19
+ SELECT_MENU = "SELECT_MENU",
20
+ USER_SELECT_MENU = "USER_SELECT_MENU",
21
+ ROLE_SELECT_MENU = "ROLE_SELECT_MENU",
22
+ MENTIONABLE_SELECT_MENU = "MENTIONABLE_SELECT_MENU",
23
+ CHANNEL_SELECT_MENU = "CHANNEL_SELECT_MENU",
24
+ MODAL_SUBMIT = "MODAL_SUBMIT"
25
+ }
26
+ /**
27
+ * Enum representing actions that can be performed on a message reaction.
28
+ */
29
+ declare enum ReactionHandlerAction {
30
+ /** Reaction added to a message. */
31
+ ADD = "ADD",
32
+ /** Reaction removed from a message. */
33
+ REMOVE = "REMOVE"
34
+ }
35
+
36
+ export { CommandType as C, ReactionHandlerAction as R };
@@ -0,0 +1,136 @@
1
+ import { Container, ServiceIdentifier } from 'inversify';
2
+ import { Client, ActivityOptions } from 'discord.js';
3
+
4
+ declare class MeoCordApp {
5
+ private readonly controllerClasses;
6
+ private readonly container;
7
+ private readonly discordClient;
8
+ private discordToken;
9
+ private activities?;
10
+ private readonly logger;
11
+ private readonly bot;
12
+ private isShuttingDown;
13
+ private activityInterval;
14
+ private controllerInstancesCache;
15
+ constructor(controllerClasses: (new (...args: any[]) => any)[], container: Container, discordClient: Client, discordToken: string, activities?: ActivityOptions[] | undefined);
16
+ /**
17
+ * Runs an event handler so a failure inside it cannot take the process down.
18
+ *
19
+ * discord.js calls listeners without awaiting them, so a rejection escaping one has
20
+ * nothing left to settle it: Node reports an unhandled rejection, which terminates
21
+ * the process by default. Losing the whole bot because one reaction landed on a
22
+ * deleted message, or one controller could not be resolved, is a worse failure than
23
+ * the one that caused it -- every other user is served by the same process.
24
+ *
25
+ * Nothing is silenced. The error is logged against the event that produced it, so a
26
+ * genuine misconfiguration -- an unbound controller, a missing dependency -- shows
27
+ * up on the very first interaction rather than staying hidden.
28
+ *
29
+ * @param event - The gateway event being handled, named in the log.
30
+ * @param run - The handler to run.
31
+ */
32
+ private runListener;
33
+ /**
34
+ * Rotates the bot's activity.
35
+ *
36
+ * Guarded separately from {@link runListener}: this runs on a timer rather than an
37
+ * event, and a throw from a timer callback is an uncaught exception no listener
38
+ * wrapper can reach.
39
+ */
40
+ private updateActivity;
41
+ private getInstance;
42
+ /** Whether a failed login set the process exit code, so a later successful one knows to clear it. */
43
+ private static failedLoginSetExitCode;
44
+ /**
45
+ * Registers the Discord event handlers and logs in.
46
+ *
47
+ * Rejects when the login fails -- an invalid token, or Discord unreachable -- and sets the exit
48
+ * code to 1 first. A bot that never came online is a failed start, and a supervisor such as
49
+ * Docker's `restart: on-failure` or systemd can only tell if the process says so. The exit code is
50
+ * set here rather than left to the entry point because an entry point that catches the rejection
51
+ * to log it has handled it, and the process would otherwise end with 0. A later `start()` that
52
+ * logs in -- an entry point retrying -- clears the code again, if this is what set it.
53
+ */
54
+ start(): Promise<void>;
55
+ registerCommands(): Promise<void>;
56
+ /**
57
+ * Every pattern-matched route, ordered most specific first.
58
+ *
59
+ * Built once and cached, so dispatch stays a single ordered walk with an early exit
60
+ * rather than paying to rank anything per interaction. Ordering here is what lets
61
+ * `gi-profile/summary/{ownerId}/{uid}` keep the ids it owns when
62
+ * `gi-profile/{uuid}/{uid}` would also match them — without it, the winner would be
63
+ * whichever controller happened to be registered first.
64
+ */
65
+ private componentRoutes?;
66
+ private getComponentRoutes;
67
+ /**
68
+ * Warns rather than throws: an app whose patterns overlap boots and works today, and
69
+ * refusing to start would turn a latent mis-route into an outage on upgrade.
70
+ */
71
+ private reportAmbiguousRoutes;
72
+ /**
73
+ * Every `@Autocomplete` handler, ordered so an option-specific handler is found
74
+ * before a command-wide one.
75
+ *
76
+ * Cached alongside the component table: autocomplete fires on every keystroke, and
77
+ * rebuilding the list per keystroke would put reflection on the hottest path the
78
+ * framework has.
79
+ */
80
+ private autocompleteRoutes?;
81
+ private getAutocompleteRoutes;
82
+ /**
83
+ * Dispatches an interaction, and makes sure a failure anywhere in that still reaches
84
+ * the person who triggered it.
85
+ *
86
+ * {@link executeCommand} already reports what a handler throws, but everything
87
+ * *before* the handler can fail too — resolving a controller through the container
88
+ * is the common case — and a component that fails there would otherwise look dead
89
+ * with nothing said to the user and nothing in the log.
90
+ */
91
+ private handleInteraction;
92
+ private dispatchInteraction;
93
+ /**
94
+ * The names a command interaction can be handled under, most specific first.
95
+ *
96
+ * Empty for anything that is not a registered command, which is how a component
97
+ * whose customId matched no pattern falls through to the unmatched warning instead
98
+ * of being looked up under a name it does not have.
99
+ */
100
+ private resolveNameRoutes;
101
+ /**
102
+ * Answers an autocomplete interaction from the `@Autocomplete` handler that claims it.
103
+ *
104
+ * Discord closes the window after three seconds and shows a loading state until
105
+ * something arrives, so an unclaimed option is answered with an empty list rather
106
+ * than left to time out -- a visibly empty menu is a better failure than a stuck one,
107
+ * and the warning says which option is missing a handler.
108
+ */
109
+ private handleAutocomplete;
110
+ /** Closes an autocomplete window that nothing else answered. */
111
+ private respondEmpty;
112
+ /**
113
+ * Runs a resolved command, shared by both dispatch paths so a pattern-matched
114
+ * component and a named slash command behave identically once the route is chosen.
115
+ */
116
+ private executeCommand;
117
+ /**
118
+ * Tells the user something went wrong, if the interaction can still hear it.
119
+ *
120
+ * A handler that replies and *then* throws is the common shape of a failure, and
121
+ * replying twice throws in turn -- out of the catch block, where nothing is left to
122
+ * handle it. Whatever the interaction's state, reporting an error must not be able
123
+ * to become a second, worse one.
124
+ */
125
+ private replyWithError;
126
+ private handleMessage;
127
+ private handleReaction;
128
+ private gracefulShutdown;
129
+ }
130
+
131
+ declare class MeoCordFactory {
132
+ private static logger;
133
+ static create(target: ServiceIdentifier): MeoCordApp;
134
+ }
135
+
136
+ export { MeoCordFactory };
@@ -39,6 +39,18 @@ declare class MeoCordApp {
39
39
  */
40
40
  private updateActivity;
41
41
  private getInstance;
42
+ /** Whether a failed login set the process exit code, so a later successful one knows to clear it. */
43
+ private static failedLoginSetExitCode;
44
+ /**
45
+ * Registers the Discord event handlers and logs in.
46
+ *
47
+ * Rejects when the login fails -- an invalid token, or Discord unreachable -- and sets the exit
48
+ * code to 1 first. A bot that never came online is a failed start, and a supervisor such as
49
+ * Docker's `restart: on-failure` or systemd can only tell if the process says so. The exit code is
50
+ * set here rather than left to the entry point because an entry point that catches the rejection
51
+ * to log it has handled it, and the process would otherwise end with 0. A later `start()` that
52
+ * logs in -- an entry point retrying -- clears the code again, if this is what set it.
53
+ */
42
54
  start(): Promise<void>;
43
55
  registerCommands(): Promise<void>;
44
56
  /**