Skip to content

Commit 0d0b9c6

Browse files
committed
Consolidate search commands and fix import/log noise
1 parent 8e87f5a commit 0d0b9c6

6 files changed

Lines changed: 138 additions & 81 deletions

File tree

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,19 @@
1-
import { SlashCommandBuilder, MessageFlags } from 'discord.js';
21
import axios from 'axios';
3-
import { createEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
4-
import { logger } from '../../utils/logger.js';
5-
import { handleInteractionError, replyUserError, ErrorTypes } from '../../utils/errorHandler.js';
6-
import { InteractionHelper } from '../../utils/interactionHelper.js';
7-
import { getColor } from '../../config/bot.js';
2+
import { createEmbed } from '../../../utils/embeds.js';
3+
import { logger } from '../../../utils/logger.js';
4+
import { handleInteractionError, replyUserError, ErrorTypes } from '../../../utils/errorHandler.js';
5+
import { InteractionHelper } from '../../../utils/interactionHelper.js';
86

97
export default {
10-
data: new SlashCommandBuilder()
11-
.setName('define')
12-
.setDescription('Look up a word definition')
13-
.addStringOption(option =>
14-
option.setName('word')
15-
.setDescription('The word to look up')
16-
.setRequired(true)),
178
async execute(interaction) {
189
try {
19-
2010
const deferred = await InteractionHelper.safeDefer(interaction);
2111
if (!deferred) {
2212
return;
2313
}
2414

2515
const word = interaction.options.getString('word');
26-
16+
2717
if (word.length < 2) {
2818
logger.warn('Define command - word too short', {
2919
userId: interaction.user.id,
@@ -32,23 +22,23 @@ export default {
3222
});
3323
return await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: 'Please enter a word with at least 2 characters.' });
3424
}
35-
25+
3626
const response = await axios.get(
3727
`https://api.dictionaryapi.dev/api/v2/entries/en/${encodeURIComponent(word)}`,
3828
{ timeout: 5000 }
3929
);
40-
30+
4131
if (!response.data || response.data.length === 0) {
4232
return await replyUserError(interaction, { type: ErrorTypes.USER_INPUT, message: `No definitions found for "${word}".` });
4333
}
44-
34+
4535
const data = response.data[0];
4636
const embed = createEmbed({
4737
title: data.word,
4838
description: data.phonetic ? `*${data.phonetic}*` : '',
4939
color: 'success'
5040
});
51-
41+
5242
data.meanings.slice(0, 5).forEach(meaning => {
5343
const definitions = meaning.definitions
5444
.slice(0, 3)
@@ -60,7 +50,7 @@ export default {
6050
return text;
6151
})
6252
.join('\n\n');
63-
53+
6454
if (definitions) {
6555
embed.addFields({
6656
name: `**${meaning.partOfSpeech || 'Definition'}**`,
@@ -69,18 +59,18 @@ export default {
6959
});
7060
}
7161
});
72-
62+
7363
embed.setFooter({ text: 'Powered by Free Dictionary API' });
74-
64+
7565
await InteractionHelper.safeEditReply(interaction, { embeds: [embed] });
76-
66+
7767
logger.info('Dictionary definition retrieved', {
7868
userId: interaction.user.id,
7969
word: word,
8070
guildId: interaction.guildId,
8171
commandName: 'define'
8272
});
83-
73+
8474
} catch (error) {
8575
logger.error('Dictionary lookup error', {
8676
error: error.message,
@@ -101,4 +91,4 @@ export default {
10191
}
10292
}
10393
},
104-
};
94+
};
Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,8 @@
1-
import { SlashCommandBuilder, MessageFlags } from 'discord.js';
2-
import { createEmbed, errorEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
3-
import { logger } from '../../utils/logger.js';
4-
import { getColor } from '../../config/bot.js';
1+
import { createEmbed } from '../../../utils/embeds.js';
2+
import { logger } from '../../../utils/logger.js';
3+
import { InteractionHelper } from '../../../utils/interactionHelper.js';
54

6-
import { InteractionHelper } from '../../utils/interactionHelper.js';
75
export default {
8-
data: new SlashCommandBuilder()
9-
.setName('google')
10-
.setDescription('Search Google')
11-
.addStringOption(option =>
12-
option.setName('query')
13-
.setDescription('What would you like to search for?')
14-
.setRequired(true)),
156
async execute(interaction) {
167
const query = interaction.options.getString('query');
178
const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}`;
@@ -32,4 +23,4 @@ export default {
3223
commandName: 'google'
3324
});
3425
},
35-
};
26+
};
Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
1-
import { SlashCommandBuilder, MessageFlags } from 'discord.js';
21
import axios from 'axios';
3-
import { createEmbed, successEmbed, infoEmbed, warningEmbed } from '../../utils/embeds.js';
4-
import { logger } from '../../utils/logger.js';
5-
import { handleInteractionError, replyUserError, ErrorTypes } from '../../utils/errorHandler.js';
6-
import { InteractionHelper } from '../../utils/interactionHelper.js';
7-
import { getColor } from '../../config/bot.js';
2+
import { createEmbed } from '../../../utils/embeds.js';
3+
import { logger } from '../../../utils/logger.js';
4+
import { handleInteractionError, replyUserError, ErrorTypes } from '../../../utils/errorHandler.js';
5+
import { InteractionHelper } from '../../../utils/interactionHelper.js';
86

97
export default {
10-
data: new SlashCommandBuilder()
11-
.setName('urban')
12-
.setDescription('Search Urban Dictionary for definitions')
13-
.addStringOption(option =>
14-
option.setName('term')
15-
.setDescription('The term to look up on Urban Dictionary')
16-
.setRequired(true)),
17-
188
async execute(interaction) {
199
try {
2010
const term = interaction.options.getString('term');
21-
11+
2212
if (term.length < 2) {
2313
logger.warn('Urban command - term too short', {
2414
userId: interaction.user.id,
@@ -45,66 +35,66 @@ export default {
4535
});
4636
});
4737
}, 1500);
48-
38+
4939
const response = await axios.get(
5040
`https://api.urbandictionary.com/v0/define?term=${encodeURIComponent(term)}`,
5141
{ timeout: 5000 }
5242
);
5343
clearDeferTimer();
54-
44+
5545
if (!response.data?.list?.length) {
5646
return await replyUserError(interaction, { type: ErrorTypes.USER_INPUT, message: `No definitions found for "${term}" on Urban Dictionary.` });
5747
}
58-
48+
5949
const definition = response.data.list[0];
6050
const cleanDefinition = definition.definition.replace(/\[|\]/g, '');
6151
const cleanExample = definition.example.replace(/\[|\]/g, '');
62-
52+
6353
const formattedDefinition = cleanDefinition
64-
.replace(/\n\s*\n/g, '\n\n')
54+
.replace(/\n\s*\n/g, '\n\n')
6555
.slice(0, 2000);
66-
56+
6757
const formattedExample = cleanExample
6858
? `*"${cleanExample.replace(/\n/g, ' ').slice(0, 500)}..."*`
6959
: '*No example provided*';
70-
60+
7161
const embed = createEmbed({
7262
title: definition.word,
7363
description: formattedDefinition,
7464
color: 'info'
7565
})
7666
.setURL(definition.permalink)
7767
.addFields(
78-
{
79-
name: 'Example',
68+
{
69+
name: 'Example',
8070
value: formattedExample,
81-
inline: false
71+
inline: false
8272
},
83-
{
84-
name: 'Stats',
73+
{
74+
name: 'Stats',
8575
value: `${definition.thumbs_up.toLocaleString()}${definition.thumbs_down.toLocaleString()}`,
86-
inline: true
76+
inline: true
8777
},
88-
{
89-
name: 'Author',
78+
{
79+
name: 'Author',
9080
value: definition.author || 'Anonymous',
91-
inline: true
81+
inline: true
9282
}
9383
)
94-
.setFooter({
84+
.setFooter({
9585
text: 'Urban Dictionary',
96-
iconURL: 'https://i.imgur.com/8aQrX3a.png'
86+
iconURL: 'https://i.imgur.com/8aQrX3a.png'
9787
});
98-
88+
9989
await InteractionHelper.safeReply(interaction, { embeds: [embed] });
100-
90+
10191
logger.info('Urban Dictionary definition retrieved', {
10292
userId: interaction.user.id,
10393
term: term,
10494
guildId: interaction.guildId,
10595
commandName: 'urban'
10696
});
107-
97+
10898
} catch (error) {
10999
logger.error('Urban Dictionary error', {
110100
error: error.message,
@@ -128,4 +118,4 @@ export default {
128118
}
129119
}
130120
},
131-
};
121+
};

src/commands/Search/search.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { SlashCommandBuilder } from 'discord.js';
2+
import { replyUserError, ErrorTypes } from '../../utils/errorHandler.js';
3+
4+
import searchDefine from './modules/search_define.js';
5+
import searchGoogle from './modules/search_google.js';
6+
import searchUrban from './modules/search_urban.js';
7+
8+
export default {
9+
data: new SlashCommandBuilder()
10+
.setName('search')
11+
.setDescription('Search the web and dictionaries')
12+
.addSubcommand(subcommand =>
13+
subcommand
14+
.setName('define')
15+
.setDescription('Look up a word definition')
16+
.addStringOption(option =>
17+
option.setName('word')
18+
.setDescription('The word to look up')
19+
.setRequired(true))
20+
)
21+
.addSubcommand(subcommand =>
22+
subcommand
23+
.setName('google')
24+
.setDescription('Search Google')
25+
.addStringOption(option =>
26+
option.setName('query')
27+
.setDescription('What would you like to search for?')
28+
.setRequired(true))
29+
)
30+
.addSubcommand(subcommand =>
31+
subcommand
32+
.setName('urban')
33+
.setDescription('Search Urban Dictionary for definitions')
34+
.addStringOption(option =>
35+
option.setName('term')
36+
.setDescription('The term to look up on Urban Dictionary')
37+
.setRequired(true))
38+
),
39+
40+
async execute(interaction, config, client) {
41+
const subcommand = interaction.options.getSubcommand();
42+
43+
switch (subcommand) {
44+
case 'define':
45+
return await searchDefine.execute(interaction, config, client);
46+
case 'google':
47+
return await searchGoogle.execute(interaction, config, client);
48+
case 'urban':
49+
return await searchUrban.execute(interaction, config, client);
50+
default:
51+
return await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: 'Unknown subcommand' });
52+
}
53+
}
54+
};

src/services/music/playerHandler.js

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,22 +72,54 @@ export function setupPlayerHandler(client) {
7272
return;
7373
}
7474

75+
// Lavalink nodes often flap (reconnect -> error -> reconnect). Throttle all
76+
// per-node messages to one line per interval, log the first connect only,
77+
// and skip reconnect noise entirely since it is meaningless during flapping.
78+
const nodeLogState = new Map();
79+
const NODE_LOG_INTERVAL_MS = 5 * 60 * 1000;
80+
81+
const shouldLogNodeEvent = (nodeName) => {
82+
const prev = nodeLogState.get(nodeName) ?? { lastLogAt: 0, hasConnected: false };
83+
const now = Date.now();
84+
if (now - prev.lastLogAt < NODE_LOG_INTERVAL_MS) {
85+
return false;
86+
}
87+
nodeLogState.set(nodeName, { ...prev, lastLogAt: now });
88+
return true;
89+
};
90+
91+
const markNodeConnected = (nodeName) => {
92+
const prev = nodeLogState.get(nodeName) ?? { lastLogAt: 0, hasConnected: false };
93+
nodeLogState.set(nodeName, { ...prev, hasConnected: true });
94+
};
95+
7596
client.riffy.on('nodeConnect', (node) => {
97+
const prev = nodeLogState.get(node.name) ?? { lastLogAt: 0, hasConnected: false };
98+
if (prev.hasConnected) {
99+
return;
100+
}
101+
markNodeConnected(node.name);
76102
logger.info(`Lavalink node "${node.name}" connected.`);
77103
});
78104

105+
client.riffy.on('nodeReconnect', () => {
106+
// Intentionally silent — reconnect spam is not actionable during flapping.
107+
});
108+
79109
client.riffy.on('nodeError', (node, error) => {
80-
logger.error(`Lavalink node "${node.name}" error:`, error?.message || error);
110+
if (!shouldLogNodeEvent(node.name)) {
111+
return;
112+
}
113+
logger.warn(`Lavalink node "${node.name}" error: ${error?.message || error}`);
81114
});
82115

83116
client.riffy.on('nodeDisconnect', (node) => {
117+
if (!shouldLogNodeEvent(node.name)) {
118+
return;
119+
}
84120
logger.warn(`Lavalink node "${node.name}" disconnected.`);
85121
});
86122

87-
client.riffy.on('nodeReconnect', (node) => {
88-
logger.info(`Lavalink node "${node.name}" reconnected.`);
89-
});
90-
91123
client.riffy.on('trackStart', async (player, track) => {
92124
try {
93125
const guildData = getGuildMusicData(player.guildId);

src/utils/moderation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// moderation.js
22

33
import { logEvent as logAuditEvent, EVENT_TYPES } from '../services/loggingService.js';
4-
import { formatLogLine } from './logEmbeds.js';
4+
import { formatLogLine } from './logging/logEmbeds.js';
55
import { logger } from './logger.js';
66
import { getFromDb, setInDb } from './database.js';
77

0 commit comments

Comments
 (0)