Skip to content

Commit 0bc4998

Browse files
committed
feat: add channel logger utility
1 parent 76321e3 commit 0bc4998

1 file changed

Lines changed: 392 additions & 0 deletions

File tree

src/v2/utils/channel-logger.ts

Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
import {
2+
Client,
3+
TextChannel,
4+
EmbedBuilder,
5+
MessageCreateOptions,
6+
ChannelType,
7+
} from 'discord.js';
8+
9+
// Types for different log content structures
10+
export interface SimpleLogContent {
11+
type: 'simple';
12+
message: string;
13+
}
14+
15+
export interface EmbedLogContent {
16+
type: 'embed';
17+
embed: EmbedBuilder | EmbedBuilder[];
18+
content?: string;
19+
}
20+
21+
export interface CustomLogContent {
22+
type: 'custom';
23+
options: MessageCreateOptions;
24+
}
25+
26+
export type LogContent = SimpleLogContent | EmbedLogContent | CustomLogContent;
27+
28+
export interface LoggerOptions {
29+
client: Client;
30+
channelId: string;
31+
content: LogContent;
32+
fallbackChannelId?: string;
33+
silent?: boolean;
34+
}
35+
36+
/**
37+
* Get channel by ID with proper type checking
38+
*/
39+
async function getChannel(
40+
client: Client,
41+
channelId: string,
42+
): Promise<TextChannel | null> {
43+
try {
44+
const channel = await client.channels.fetch(channelId);
45+
46+
if (!channel || channel.type !== ChannelType.GuildText) {
47+
return null;
48+
}
49+
50+
return channel as TextChannel;
51+
} catch {
52+
return null;
53+
}
54+
}
55+
56+
/**
57+
* Send message based on content type
58+
*/
59+
async function sendMessage(
60+
channel: TextChannel,
61+
content: LogContent,
62+
): Promise<boolean> {
63+
try {
64+
let messageOptions: MessageCreateOptions;
65+
66+
switch (content.type) {
67+
case 'simple':
68+
messageOptions = { content: content.message };
69+
break;
70+
71+
case 'embed':
72+
messageOptions = {
73+
embeds: Array.isArray(content.embed)
74+
? content.embed
75+
: [content.embed],
76+
content: content.content || undefined,
77+
};
78+
break;
79+
80+
case 'custom':
81+
messageOptions = content.options;
82+
break;
83+
84+
default:
85+
throw new Error('Invalid content type provided');
86+
}
87+
88+
await channel.send(messageOptions);
89+
return true;
90+
} catch (error) {
91+
console.error('Error sending message:', error);
92+
return false;
93+
}
94+
}
95+
96+
/**
97+
* Main logging function - sends a message to the specified channel
98+
*/
99+
export async function logToChannel(options: LoggerOptions): Promise<boolean> {
100+
try {
101+
const channel = await getChannel(options.client, options.channelId);
102+
103+
if (!channel) {
104+
// Try fallback channel if provided
105+
if (options.fallbackChannelId) {
106+
const fallbackChannel = await getChannel(
107+
options.client,
108+
options.fallbackChannelId,
109+
);
110+
if (fallbackChannel) {
111+
return await sendMessage(fallbackChannel, options.content);
112+
}
113+
}
114+
115+
if (!options.silent) {
116+
throw new Error(
117+
`Channel with ID ${options.channelId} not found or not accessible`,
118+
);
119+
}
120+
return false;
121+
}
122+
123+
return await sendMessage(channel, options.content);
124+
} catch (error) {
125+
if (!options.silent) {
126+
console.error('Channel Logger Error:', error);
127+
throw error;
128+
}
129+
return false;
130+
}
131+
}
132+
133+
/**
134+
* Quick function for simple text logging
135+
*/
136+
export async function logSimple(
137+
client: Client,
138+
channelId: string,
139+
message: string,
140+
silent: boolean = false,
141+
): Promise<boolean> {
142+
return logToChannel({
143+
client,
144+
channelId,
145+
content: { type: 'simple', message },
146+
silent,
147+
});
148+
}
149+
150+
/**
151+
* Quick function for embed logging
152+
*/
153+
export async function logEmbed(
154+
client: Client,
155+
channelId: string,
156+
embed: EmbedBuilder | EmbedBuilder[],
157+
content?: string,
158+
silent: boolean = false,
159+
): Promise<boolean> {
160+
return logToChannel({
161+
client,
162+
channelId,
163+
content: { type: 'embed', embed, content },
164+
silent,
165+
});
166+
}
167+
168+
/**
169+
* Quick function for custom message logging
170+
*/
171+
export async function logCustom(
172+
client: Client,
173+
channelId: string,
174+
options: MessageCreateOptions,
175+
fallbackChannelId?: string,
176+
silent: boolean = false,
177+
): Promise<boolean> {
178+
return logToChannel({
179+
client,
180+
channelId,
181+
content: { type: 'custom', options },
182+
fallbackChannelId,
183+
silent,
184+
});
185+
}
186+
187+
// Template functions for common log scenarios
188+
189+
/**
190+
* Create a moderation action embed
191+
*/
192+
export function createModerationEmbed(options: {
193+
action: string;
194+
moderator: string;
195+
target: string;
196+
reason?: string;
197+
duration?: string;
198+
color?: number;
199+
}): EmbedBuilder {
200+
const embed = new EmbedBuilder()
201+
.setTitle(`🔨 ${options.action}`)
202+
.setColor(options.color || 0xff6b6b)
203+
.addFields(
204+
{ name: 'Moderator', value: options.moderator, inline: true },
205+
{ name: 'Target', value: options.target, inline: true },
206+
)
207+
.setTimestamp();
208+
209+
if (options.reason) {
210+
embed.addFields({ name: 'Reason', value: options.reason, inline: false });
211+
}
212+
213+
if (options.duration) {
214+
embed.addFields({
215+
name: 'Duration',
216+
value: options.duration,
217+
inline: true,
218+
});
219+
}
220+
221+
return embed;
222+
}
223+
224+
/**
225+
* Create a user join/leave embed
226+
*/
227+
export function createUserEventEmbed(options: {
228+
type: 'join' | 'leave';
229+
user: string;
230+
memberCount?: number;
231+
color?: number;
232+
}): EmbedBuilder {
233+
const isJoin = options.type === 'join';
234+
const embed = new EmbedBuilder()
235+
.setTitle(`${isJoin ? '📥' : '📤'} User ${isJoin ? 'Joined' : 'Left'}`)
236+
.setColor(options.color || (isJoin ? 0x57f287 : 0xfaa61a))
237+
.addFields({ name: 'User', value: options.user, inline: true })
238+
.setTimestamp();
239+
240+
if (options.memberCount) {
241+
embed.addFields({
242+
name: 'Member Count',
243+
value: options.memberCount.toString(),
244+
inline: true,
245+
});
246+
}
247+
248+
return embed;
249+
}
250+
251+
/**
252+
* Create a message deletion embed
253+
*/
254+
export function createMessageDeletedEmbed(options: {
255+
author: string;
256+
channel: string;
257+
content?: string;
258+
attachments?: number;
259+
}): EmbedBuilder {
260+
const embed = new EmbedBuilder()
261+
.setTitle('🗑️ Message Deleted')
262+
.setColor(0xed4245)
263+
.addFields(
264+
{ name: 'Author', value: options.author, inline: true },
265+
{ name: 'Channel', value: options.channel, inline: true },
266+
)
267+
.setTimestamp();
268+
269+
if (options.content) {
270+
embed.addFields({
271+
name: 'Content',
272+
value:
273+
options.content.length > 1024
274+
? options.content.substring(0, 1021) + '...'
275+
: options.content,
276+
inline: false,
277+
});
278+
}
279+
280+
if (options.attachments && options.attachments > 0) {
281+
embed.addFields({
282+
name: 'Attachments',
283+
value: options.attachments.toString(),
284+
inline: true,
285+
});
286+
}
287+
288+
return embed;
289+
}
290+
291+
/**
292+
* Convenience function: Log a moderation action
293+
*/
294+
export async function logModerationAction(
295+
client: Client,
296+
channelId: string,
297+
options: {
298+
action: string;
299+
moderator: string;
300+
target: string;
301+
reason?: string;
302+
duration?: string;
303+
color?: number;
304+
},
305+
silent: boolean = false,
306+
): Promise<boolean> {
307+
const embed = createModerationEmbed(options);
308+
return logEmbed(client, channelId, embed, undefined, silent);
309+
}
310+
311+
/**
312+
* Convenience function: Log a user join/leave event
313+
*/
314+
export async function logUserEvent(
315+
client: Client,
316+
channelId: string,
317+
options: {
318+
type: 'join' | 'leave';
319+
user: string;
320+
memberCount?: number;
321+
color?: number;
322+
},
323+
silent: boolean = false,
324+
): Promise<boolean> {
325+
const embed = createUserEventEmbed(options);
326+
return logEmbed(client, channelId, embed, undefined, silent);
327+
}
328+
329+
/**
330+
* Convenience function: Log a message deletion
331+
*/
332+
export async function logMessageDeleted(
333+
client: Client,
334+
channelId: string,
335+
options: {
336+
author: string;
337+
channel: string;
338+
content?: string;
339+
attachments?: number;
340+
},
341+
silent: boolean = false,
342+
): Promise<boolean> {
343+
const embed = createMessageDeletedEmbed(options);
344+
return logEmbed(client, channelId, embed, undefined, silent);
345+
}
346+
347+
// Example usage patterns:
348+
/*
349+
import {
350+
logSimple,
351+
logEmbed,
352+
logToChannel,
353+
logModerationAction,
354+
createModerationEmbed
355+
} from './utils/channel-logger';
356+
357+
// Simple text log
358+
await logSimple(client, 'CHANNEL_ID', 'User performed an action');
359+
360+
// Embed log
361+
const embed = new EmbedBuilder()
362+
.setTitle('Test Log')
363+
.setDescription('This is a test')
364+
.setColor(0x00ff00);
365+
366+
await logEmbed(client, 'CHANNEL_ID', embed);
367+
368+
// Moderation action (using convenience function)
369+
await logModerationAction(client, 'MOD_LOG_CHANNEL', {
370+
action: 'Ban',
371+
moderator: `<@${interaction.user.id}>`,
372+
target: `<@${targetUser.id}>`,
373+
reason: 'Spam',
374+
color: 0xff0000
375+
});
376+
377+
// Custom complex message
378+
await logToChannel({
379+
client,
380+
channelId: 'ADMIN_CHANNEL',
381+
content: {
382+
type: 'custom',
383+
options: {
384+
content: `<@&MODERATOR_ROLE> Attention needed!`,
385+
embeds: [embed1, embed2],
386+
allowedMentions: { roles: ['MODERATOR_ROLE'] }
387+
}
388+
},
389+
fallbackChannelId: 'BACKUP_CHANNEL',
390+
silent: true
391+
});
392+
*/

0 commit comments

Comments
 (0)