Skip to content

Commit 95aaf40

Browse files
authored
feat: showcase logs and starter post (#61)
* chore: bump biome to 2.5.0 * feat: add SHOWCASE_LOG_CHANNEL_ID env * feat: add send showcase pinned post command * feat: add wrapInDiffBlock util * feat: add toDiscordDiff * feat: update pinned post to send raw message * feat: add showcase logs * feat: add showcase logs channel id to env
1 parent 03f829b commit 95aaf40

14 files changed

Lines changed: 433 additions & 65 deletions

File tree

.env.production

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ GUIDES_CHANNEL_ID=1429492053825290371
1010
REPEL_LOG_CHANNEL_ID=1403558160144531589
1111
ADVENT_OF_CODE_CHANNEL_ID=1047623689488830495
1212
SHOWCASE_CHANNEL_ID=1517161718818541658
13+
SHOWCASE_LOG_CHANNEL_ID=1517565847982444634
1314

1415
# Role IDs (from your dev server)
1516
REPEL_ROLE_ID=1002411741776461844

.env.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ GUIDES_CHANNEL_ID=your-guide-channel-id
1717
ADVENT_OF_CODE_CHANNEL_ID=your_advent_of_code_forum_channel_id_here
1818
REPEL_LOG_CHANNEL_ID=your-repel-log-channel-id
1919
SHOWCASE_CHANNEL_ID=your-showcase-forum-channel-id
20+
SHOWCASE_LOG_CHANNEL_ID=your-showcase-log-channel-id
2021

2122
# Role IDs (from your dev server)
2223
REPEL_ROLE_ID=your-repel-role-id

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"web-features": "^3.9.2"
3333
},
3434
"devDependencies": {
35-
"@biomejs/biome": "2.2.4",
35+
"@biomejs/biome": "2.5.0",
3636
"@types/node": "^24.5.2",
3737
"@types/node-cron": "^3.0.11",
3838
"husky": "^9.1.7",

pnpm-lock.yaml

Lines changed: 38 additions & 38 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { repelCommand } from '@/features/moderation/repel.js';
55
import { pingCommand } from '@/features/ping/index.js';
66
import { publicGuidesCommand } from '@/features/public-guides/index.js';
77
import { createShowcaseCommand } from '@/features/showcase/create-showcase.js';
8+
import { sendShowcasePinnedMessage } from '@/features/showcase/send-pinned-message.js';
89
import { tipsCommands } from '@/features/tips/index.js';
910
import type { Command } from './types.js';
1011

@@ -18,6 +19,7 @@ export const commands = new Map<string, Command>(
1819
cacheMessages,
1920
publicGuidesCommand,
2021
createShowcaseCommand,
22+
sendShowcasePinnedMessage,
2123
]
2224
.flat()
2325
.map((command) => [command.data.name, command])

src/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const config = {
3939
guides: requireEnv('GUIDES_CHANNEL_ID'),
4040
adventOfCode: requireEnv('ADVENT_OF_CODE_CHANNEL_ID'),
4141
showcase: requireEnv('SHOWCASE_CHANNEL_ID'),
42+
showcaseLogs: requireEnv('SHOWCASE_LOG_CHANNEL_ID'),
4243
},
4344
onboarding: {
4445
channelId: optionalEnv('ONBOARDING_CHANNEL_ID'),

src/features/showcase/create-showcase.ts

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,33 @@
11
import {
22
ButtonBuilder,
3+
type ButtonInteraction,
34
ButtonStyle,
45
ChannelType,
6+
type ChatInputCommandInteraction,
7+
Colors,
58
ContainerBuilder,
9+
EmbedBuilder,
610
MessageFlags,
711
TextDisplayBuilder,
812
} from 'discord.js';
913
import { createSlashCommand } from '@/common/commands/create-commands.js';
10-
import { registerButtonSubmitInteraction } from '@/common/interactions/button-interaction.js';
14+
import {
15+
type ButtonSubmitInteraction,
16+
registerButtonSubmitInteraction,
17+
} from '@/common/interactions/button-interaction.js';
1118
import {
1219
type ModalSubmitInteraction,
1320
registerModalSubmitInteraction,
1421
} from '@/common/interactions/modal-interaction.js';
1522
import { config } from '@/env.js';
23+
import { logToChannel } from '@/util/channel-logging.js';
1624
import { customId } from '@/util/custom-id.js';
1725
import { deleteShowcase } from './delete-showcase.js';
1826
import { editShowcaseInteraction } from './edit-showcase.js';
19-
import { buildShowcaseModal } from './util.js';
27+
import { buildShowcaseModal, getShowcaseLogChannel } from './util.js';
2028

21-
export const createShowcaseCommand = createSlashCommand({
22-
data: {
23-
name: 'showcase',
24-
description: 'Showcase a project in the showcase channel',
25-
},
26-
execute: async (interaction) => {
29+
export const showModal = async (interaction: ButtonInteraction | ChatInputCommandInteraction) => {
30+
try {
2731
const channel = interaction.guild?.channels.cache.get(config.channelIds.showcase);
2832
if (channel === undefined || channel.type !== ChannelType.GuildForum) {
2933
await interaction.reply({
@@ -40,9 +44,30 @@ export const createShowcaseCommand = createSlashCommand({
4044
});
4145

4246
await interaction.showModal(modal);
47+
} catch (error) {
48+
console.error('Error showing showcase modal:', error);
49+
await interaction.reply({
50+
content: 'There was an error showing the showcase modal. Please try again later.',
51+
flags: MessageFlags.Ephemeral,
52+
});
53+
}
54+
};
55+
56+
export const createShowcaseCommand = createSlashCommand({
57+
data: {
58+
name: 'showcase',
59+
description: 'Showcase a project in the showcase channel',
4360
},
61+
execute: showModal,
4462
});
4563

64+
const createShowcaseButtonHandler: ButtonSubmitInteraction = {
65+
commandName: 'create_showcase',
66+
handler: showModal,
67+
};
68+
69+
registerButtonSubmitInteraction(createShowcaseButtonHandler);
70+
4671
const modalHandler: ModalSubmitInteraction = {
4772
commandName: 'showcase',
4873
handler: async (interaction) => {
@@ -104,6 +129,34 @@ const modalHandler: ModalSubmitInteraction = {
104129
),
105130
],
106131
});
132+
133+
try {
134+
const logChannel = getShowcaseLogChannel(interaction.guild);
135+
const author = {
136+
name: interaction.user.tag,
137+
iconURL: interaction.user.displayAvatarURL(),
138+
};
139+
140+
const embed = new EmbedBuilder()
141+
.setAuthor(author)
142+
.setTitle('Showcase Created')
143+
.addFields(
144+
{ name: 'Project Name', value: projectName || '—', inline: false },
145+
{ name: 'Author', value: `<@${interaction.user.id}>`, inline: true },
146+
{ name: 'Thread', value: thread.url ?? '—', inline: true }
147+
)
148+
.setColor(Colors.Green)
149+
.setTimestamp();
150+
151+
await logToChannel({
152+
channel: logChannel,
153+
content: { type: 'embed', embed },
154+
silent: true,
155+
});
156+
} catch (error) {
157+
console.error('Failed to log showcase creation:', error);
158+
}
159+
107160
await interaction.editReply({
108161
content: `Your project has been showcased successfully! You can view it here: ${thread.url}`,
109162
});

src/features/showcase/delete-showcase.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { MessageFlags } from 'discord.js';
1+
import { ChannelType, Colors, EmbedBuilder, MessageFlags } from 'discord.js';
22
import type { ButtonSubmitInteraction } from '@/common/interactions/button-interaction.js';
3+
import { logToChannel } from '@/util/channel-logging.js';
34
import { parseCustomId } from '@/util/custom-id.js';
45
import { isUserInServer, isUserModerator } from '@/util/member.js';
6+
import { getShowcaseLogChannel, parseShowcaseMessage } from './util.js';
57

68
export const deleteShowcase: ButtonSubmitInteraction = {
79
commandName: 'delete_showcase',
@@ -26,7 +28,48 @@ export const deleteShowcase: ButtonSubmitInteraction = {
2628
}
2729

2830
try {
31+
const forumPost =
32+
interaction.channel?.type === ChannelType.PublicThread ? interaction.channel : null;
33+
if (forumPost === null) {
34+
await interaction.reply({
35+
content: '❌ This command can only be used in a forum post.',
36+
flags: MessageFlags.Ephemeral,
37+
});
38+
return;
39+
}
40+
if (forumPost === null) {
41+
await interaction.reply({
42+
content: '❌ This command can only be used in a forum post.',
43+
flags: MessageFlags.Ephemeral,
44+
});
45+
return;
46+
}
47+
48+
const message = await forumPost.fetchStarterMessage();
49+
if (!message) {
50+
await interaction.reply({
51+
content: '❌ Could not find the showcase message to delete.',
52+
flags: MessageFlags.Ephemeral,
53+
});
54+
return;
55+
}
56+
57+
const { projectName } = parseShowcaseMessage(message.content);
2958
await interaction.channel?.delete();
59+
const logChannel = getShowcaseLogChannel(interaction.guild);
60+
await logToChannel({
61+
channel: logChannel,
62+
content: {
63+
type: 'embed',
64+
embed: new EmbedBuilder()
65+
.setTitle('Showcase Deleted')
66+
.setDescription(
67+
`**Project Name:** ${projectName}\n**Deleted By:** <@${interactionUser.id}>`
68+
)
69+
.setColor(Colors.Red)
70+
.setAuthor({ name: interactionUser.tag, iconURL: interactionUser.displayAvatarURL() }),
71+
},
72+
});
3073
} catch (error) {
3174
console.error('Error deleting showcase:', error);
3275
await interaction.reply({

0 commit comments

Comments
 (0)