Skip to content

Commit 383bb87

Browse files
committed
refactor: ♻️ restructure to split functions
Signed-off-by: Eric Nemchik <[email protected]> chore: 📦 fix package lock Signed-off-by: Eric Nemchik <[email protected]> feat: 🥅 better error catching Signed-off-by: Eric Nemchik <[email protected]> chore: 🔊 adjust config error wording Signed-off-by: Eric Nemchik <[email protected]> feat: ✨ config path handling Signed-off-by: Eric Nemchik <[email protected]> feat: ✨ configurable log path Signed-off-by: Eric Nemchik <[email protected]> fix: 🐛 set webhook data back to originals Signed-off-by: Eric Nemchik <[email protected]> chore: 🗑️ remove unused ? Signed-off-by: Eric Nemchik <[email protected]>
1 parent cd923f1 commit 383bb87

34 files changed

+939
-531
lines changed

.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CRONITOR_URL=
99
WEBHOOKS=false
1010
TESTING=true
1111
LOG_LEVEL="debug"
12+
LOG_PATH="logs"
1213
UP_PING=false
1314
SC_PING=false
1415
UPTIME_DELAY=4

Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ COPY ./src ./package.json package-lock.json /tsconfig.json /bot/
33
WORKDIR /bot
44
RUN npm install && npm run build && npm ci --omit=dev
55
CMD ["node", "."]
6+
VOLUME [ "/config" ]

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CRONITOR_URL= # Cronitor heartbeat url for uptime checking (optional)
1414
WEBHOOKS= # Send webhooks to the Notifiarr
1515
TESTING= # Testing bot
1616
LOG_LEVEL= # Set how much output you want. Use one of: 'error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'
17+
LOG_PATH="logs" # The path where log files are output, default is logs folder relative to the running application, otherwise a full path should be used
1718
UP_PING= # Send uptime pings to Betteruptime or Cronitor
1819
SC_PING= # Send server count information to Notifiarr for this bot token
1920
UPTIME_DELAY= # How long to wait between uptime pings

package-lock.json

+94-83
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config.ts

+20-22
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,38 @@
11
import dotenv from 'dotenv';
2+
import fs from 'node:fs';
23
import process from 'node:process';
34

4-
dotenv.config();
5-
6-
function evalBool(value: string | undefined): boolean {
7-
if (value === undefined) return false;
8-
if (value.toLocaleLowerCase() === 'false') return false;
9-
if (value.toLocaleLowerCase() === 'true') return true;
10-
11-
return false;
5+
let configPath;
6+
if (fs.existsSync('/config')) {
7+
configPath = fs
8+
.readdirSync('/config')
9+
.map((file) => `/config/${file}`)
10+
.find((file) => file === '/config/.env');
1211
}
1312

14-
function blankUndefined(value: string | undefined): string | undefined {
15-
if (value === undefined) return undefined;
16-
if (value === '') return undefined;
17-
18-
return value;
19-
}
13+
dotenv.config({ path: configPath });
2014

2115
const config = {
2216
botToken: process.env.BOT_TOKEN ?? '',
2317
userApiKey: process.env.USER_API_KEY ?? '',
24-
devDiscordUsers: (process.env.DEV_DISCORD_USERS ?? '').split(',').map(Number),
18+
devDiscordUsers: (process.env.DEV_DISCORD_USERS ?? '')
19+
.split(',')
20+
.map(Number)
21+
.filter((item) => item !== 0),
2522

26-
notifiarrApiUrl: blankUndefined(process.env.NOTIFIARR_API_URL),
27-
betterUptimeUrl: blankUndefined(process.env.BETTER_UPTIME_URL),
28-
cronitorUrl: blankUndefined(process.env.CRONITOR_URL),
23+
notifiarrApiUrl: process.env.NOTIFIARR_API_URL ?? '',
24+
betterUptimeUrl: process.env.BETTER_UPTIME_URL ?? '',
25+
cronitorUrl: process.env.CRONITOR_URL ?? '',
2926

30-
webhooks: evalBool(process.env.WEBHOOKS),
31-
testing: evalBool(process.env.TESTING),
27+
webhooks: Boolean(process.env.WEBHOOKS?.toString().toLocaleLowerCase() === 'true'),
28+
testing: Boolean(process.env.TESTING?.toString().toLocaleLowerCase() === 'true'),
3229
logLevel:
3330
['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'].find((level) =>
3431
new RegExp(`^${process.env.LOG_LEVEL}$`, 'i').exec(level),
3532
) ?? 'error',
36-
upPing: evalBool(process.env.UP_PING),
37-
scPing: evalBool(process.env.SC_PING),
33+
logPath: (process.env.LOG_PATH ?? '').length > 0 ? process.env.LOG_PATH : 'logs',
34+
upPing: Boolean(process.env.UP_PING?.toString().toLocaleLowerCase() === 'true'),
35+
scPing: Boolean(process.env.SC_PING?.toString().toLocaleLowerCase() === 'true'),
3836
uptimeDelay: Number(process.env.UPTIME_DELAY ?? 4),
3937
countDelay: Number(process.env.COUNT_DELAY ?? 10),
4038
};

src/events/error.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Events } from 'discord.js';
2+
import logger from '../functions/logger.js';
3+
import { type EventModule } from '../types.js';
4+
5+
const event: EventModule<Events.Error> = {
6+
name: Events.Error,
7+
execute(message) {
8+
logger.error(message);
9+
},
10+
};
11+
12+
export default event;

src/events/guildBanAdd.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Events } from 'discord.js';
2+
import logger from '../functions/logger.js';
3+
import notifiarrWebhook from '../functions/notifiarrWebhook.js';
4+
import { type EventModule } from '../types.js';
5+
6+
const event: EventModule<Events.GuildBanAdd> = {
7+
name: Events.GuildBanAdd,
8+
async execute(ban) {
9+
logger.debug(`${this.name}->${ban.guild.id}`);
10+
try {
11+
await notifiarrWebhook({
12+
event: this.name,
13+
botToken: ban.client.token,
14+
server: ban.guild.id,
15+
user: JSON.stringify(ban.user),
16+
});
17+
} catch (error) {
18+
logger.error('caught:', error);
19+
}
20+
},
21+
};
22+
23+
export default event;

src/events/guildBanRemove.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Events } from 'discord.js';
2+
import logger from '../functions/logger.js';
3+
import notifiarrWebhook from '../functions/notifiarrWebhook.js';
4+
import { type EventModule } from '../types.js';
5+
6+
const event: EventModule<Events.GuildBanRemove> = {
7+
name: Events.GuildBanRemove,
8+
async execute(ban) {
9+
logger.debug(`${this.name}->${ban.guild.id}`);
10+
try {
11+
await notifiarrWebhook({
12+
event: this.name,
13+
botToken: ban.client.token,
14+
server: ban.guild.id,
15+
user: JSON.stringify(ban.user),
16+
});
17+
} catch (error) {
18+
logger.error('caught:', error);
19+
}
20+
},
21+
};
22+
23+
export default event;

src/events/guildMemberAdd.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Events } from 'discord.js';
2+
import logger from '../functions/logger.js';
3+
import notifiarrWebhook from '../functions/notifiarrWebhook.js';
4+
import { type EventModule } from '../types.js';
5+
6+
const event: EventModule<Events.GuildMemberAdd> = {
7+
name: Events.GuildMemberAdd,
8+
async execute(member) {
9+
logger.debug(`${this.name}->${member.guild.id}`);
10+
try {
11+
await notifiarrWebhook({
12+
event: this.name,
13+
botToken: member.client.token,
14+
server: member.guild.id,
15+
member: JSON.stringify(member),
16+
memberCount: member.guild.memberCount,
17+
});
18+
} catch (error) {
19+
logger.error('caught:', error);
20+
}
21+
},
22+
};
23+
24+
export default event;

src/events/guildMemberRemove.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Events } from 'discord.js';
2+
import logger from '../functions/logger.js';
3+
import notifiarrWebhook from '../functions/notifiarrWebhook.js';
4+
import { type EventModule } from '../types.js';
5+
6+
const event: EventModule<Events.GuildMemberRemove> = {
7+
name: Events.GuildMemberRemove,
8+
async execute(member) {
9+
logger.debug(`${this.name}->${member.guild.id}`);
10+
try {
11+
await notifiarrWebhook({
12+
event: this.name,
13+
botToken: member.client.token,
14+
server: member.guild.id,
15+
member: JSON.stringify(member),
16+
memberCount: member.guild.memberCount,
17+
});
18+
} catch (error) {
19+
logger.error('caught:', error);
20+
}
21+
},
22+
};
23+
24+
export default event;

src/events/interactionCreate.ts

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { Collection, Events, inlineCode, time, type InteractionReplyOptions } from 'discord.js';
2+
import logger from '../functions/logger.js';
3+
import notifiarrWebhook from '../functions/notifiarrWebhook.js';
4+
import { type EventModule } from '../types.js';
5+
6+
const event: EventModule<Events.InteractionCreate> = {
7+
name: Events.InteractionCreate,
8+
async execute(interaction) {
9+
logger.debug(`${this.name}->${interaction.guild?.id}`);
10+
try {
11+
await notifiarrWebhook({
12+
event: this.name,
13+
botToken: interaction.client.token,
14+
server: interaction.guild?.id,
15+
member: interaction.user.id,
16+
channel: interaction.channel?.id,
17+
customId: interaction.isMessageComponent() ? interaction.customId : undefined,
18+
});
19+
} catch (error) {
20+
logger.error('caught:', error);
21+
}
22+
23+
if (interaction.isAutocomplete() || interaction.isChatInputCommand()) {
24+
const slashCommand = interaction.client.slashCommands.get(interaction.commandName);
25+
26+
if (!slashCommand) {
27+
logger.warn(`No command matching ${interaction.commandName} was found.`);
28+
return;
29+
}
30+
31+
if (interaction.isAutocomplete()) {
32+
if (!slashCommand.autocomplete) {
33+
logger.warn(`No autocomplete matching ${interaction.commandName} was found.`);
34+
return;
35+
}
36+
37+
try {
38+
await slashCommand.autocomplete(interaction);
39+
} catch (error) {
40+
logger.error('caught:', error);
41+
}
42+
}
43+
44+
if (interaction.isChatInputCommand()) {
45+
const { cooldowns } = interaction.client;
46+
47+
if (!cooldowns.has(slashCommand.data.name)) {
48+
cooldowns.set(slashCommand.data.name, new Collection());
49+
}
50+
51+
const now = Date.now();
52+
const timestamps = cooldowns.get(slashCommand.data.name) ?? new Collection();
53+
const defaultCooldownDuration = 3;
54+
const cooldownAmount = (slashCommand.cooldown ?? defaultCooldownDuration) * 1000;
55+
56+
if (timestamps.has(interaction.user.id)) {
57+
const expirationTime = Number(timestamps.get(interaction.user.id)) + cooldownAmount;
58+
59+
if (now < expirationTime) {
60+
const expiredTimestamp = Math.round(expirationTime / 1000);
61+
await interaction.reply({
62+
content: `Please wait, you are on a cooldown for ${inlineCode(
63+
slashCommand.data.name,
64+
)}. You can use it again ${time(expiredTimestamp, 'R')}.`,
65+
ephemeral: true,
66+
});
67+
return;
68+
}
69+
}
70+
71+
timestamps.set(interaction.user.id, now);
72+
setTimeout(() => timestamps.delete(interaction.user.id), cooldownAmount);
73+
74+
try {
75+
await slashCommand.execute(interaction);
76+
} catch (error) {
77+
logger.error('caught:', error);
78+
const options: InteractionReplyOptions = {
79+
content: 'There was an error while executing this command!',
80+
ephemeral: true,
81+
};
82+
await (interaction.replied || interaction.deferred
83+
? interaction.followUp(options)
84+
: interaction.reply(options));
85+
}
86+
}
87+
}
88+
89+
if (interaction.isMessageComponent()) {
90+
// Handle message components
91+
}
92+
},
93+
};
94+
95+
export default event;

src/events/messageCreate.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Events } from 'discord.js';
2+
import config from '../config.js';
3+
import logger from '../functions/logger.js';
4+
import notifiarrWebhook from '../functions/notifiarrWebhook.js';
5+
import { type EventModule } from '../types.js';
6+
7+
const event: EventModule<Events.MessageCreate> = {
8+
name: Events.MessageCreate,
9+
async execute(message) {
10+
if (message.author.bot) {
11+
return;
12+
}
13+
14+
if (!message.inGuild()) {
15+
return;
16+
}
17+
18+
if (config.testing && !config.devDiscordUsers.includes(Number(message.author.id))) {
19+
logger.debug(`Ignoring non allowed user ${message.author.username} (${message.author.id})`);
20+
return;
21+
}
22+
23+
logger.debug(`${this.name}->${message.guild.id}`);
24+
try {
25+
const messages = await message.channel.messages.fetch({ before: message.id, limit: 15 });
26+
await notifiarrWebhook({
27+
// eslint-disable-next-line @typescript-eslint/naming-convention
28+
mediarequest_eventtype: 'ping',
29+
botToken: message.client.token,
30+
server: message.guild.id,
31+
channel: message.channel.id,
32+
message: JSON.stringify(message),
33+
previousMessage: JSON.stringify(messages),
34+
authorRoles: [...(message.member?.roles.cache.keys() ?? [])],
35+
});
36+
} catch (error) {
37+
logger.error('caught:', error);
38+
}
39+
},
40+
};
41+
42+
export default event;

src/events/messageDelete.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Events } from 'discord.js';
2+
import logger from '../functions/logger.js';
3+
import notifiarrWebhook from '../functions/notifiarrWebhook.js';
4+
import { type EventModule } from '../types.js';
5+
6+
const event: EventModule<Events.MessageDelete> = {
7+
name: Events.MessageDelete,
8+
async execute(message) {
9+
if (message.author?.bot) {
10+
return;
11+
}
12+
13+
if (!message.inGuild()) {
14+
return;
15+
}
16+
17+
logger.debug(`${this.name}->${message.guild.id}`);
18+
try {
19+
await notifiarrWebhook({
20+
event: this.name,
21+
botToken: message.client.token,
22+
server: message.guild?.id,
23+
message: JSON.stringify(message),
24+
});
25+
} catch (error) {
26+
logger.error('caught:', error);
27+
}
28+
},
29+
};
30+
31+
export default event;

src/events/messageUpdate.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Events } from 'discord.js';
2+
import logger from '../functions/logger.js';
3+
import notifiarrWebhook from '../functions/notifiarrWebhook.js';
4+
import { type EventModule } from '../types.js';
5+
6+
const event: EventModule<Events.MessageUpdate> = {
7+
name: Events.MessageUpdate,
8+
async execute(oldMessage, newMessage) {
9+
if (newMessage.author?.bot) {
10+
return;
11+
}
12+
13+
if (!newMessage.inGuild()) {
14+
return;
15+
}
16+
17+
logger.debug(`${this.name}->${newMessage.guild.id}`);
18+
try {
19+
await notifiarrWebhook({
20+
event: this.name,
21+
botToken: newMessage.client.token,
22+
server: newMessage.guild.id,
23+
newMessage: JSON.stringify(newMessage),
24+
oldMessage: JSON.stringify(oldMessage),
25+
});
26+
} catch (error) {
27+
logger.error('caught:', error);
28+
}
29+
},
30+
};
31+
32+
export default event;

0 commit comments

Comments
 (0)