Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions src/@types/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import type {
} from "discord.js";

type Handler<I> = (interaction: I) => Promise<void>;
type Kind = "chat" | "button" | "select" | "modal";

type SlashCommandData =
| SlashCommandBuilder
| SlashCommandOptionsOnlyBuilder
| SlashCommandSubcommandsOnlyBuilder;

export type Command<I, D, K extends Kind, C = number> = {
kind: K;
export type Command<I, D, C = number> = {
data: D;
execute: Handler<I>;
cooldown: C; // seconds
Expand All @@ -24,10 +22,7 @@ export type Command<I, D, K extends Kind, C = number> = {
export type SlashCommand = Command<
ChatInputCommandInteraction,
SlashCommandData,
"chat",
number
>;

export type DiscordCommand = SlashCommand;

export type CommandCooldowns = Collection<string, Collection<string, number>>;
4 changes: 2 additions & 2 deletions src/@types/discord-augment.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import "discord.js";
import type { Collection } from "discord.js";
import type { DiscordCommand, CommandCooldowns } from "./commands.ts";
import type { CommandCooldowns, SlashCommand } from "./commands.ts";

declare module "discord.js" {
interface Client {
commands: Collection<string, DiscordCommand>;
commands: Collection<string, SlashCommand>;
cooldowns: CommandCooldowns;
}
}
1 change: 0 additions & 1 deletion src/commands/chat-commands/ping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ async function execute(

export const command: SlashCommand = {
cooldown: 10,
kind: "chat",
data: new SlashCommandBuilder()
.setName("ping")
.setDescription("Replies with Pong!"),
Expand Down
49 changes: 45 additions & 4 deletions src/commands/chat-commands/random-song.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from "discord.js";
import {
SlashCommandBuilder,
ChatInputCommandInteraction,
ButtonBuilder,
ButtonStyle,
ActionRowBuilder,
MessageActionRowComponentBuilder,
} from "discord.js";
import type { SlashCommand } from "../../@types/commands.js";
import { logger } from "../../helpers/logger.js";
import { getSongByGenre } from "../../helpers/open-ai.js";
import { SongPick } from "../../@types/open-ai.js";
import { playSongFromYoutube } from "../../helpers/youtube.js";
import { playSongFromYoutube } from "../../helpers/playback.js";

async function execute(
interaction: ChatInputCommandInteraction,
Expand All @@ -18,7 +25,35 @@ async function execute(
const pickedSong = songs[randomIndex]!;

const playResult = await playSongFromYoutube(interaction, pickedSong);
await interaction.editReply(playResult);

const play = new ButtonBuilder()
.setCustomId("play")
.setLabel("Play")
.setStyle(ButtonStyle.Primary)
.setDisabled(true);

const pause = new ButtonBuilder()
.setCustomId("pause")
.setLabel("Pause")
.setStyle(ButtonStyle.Secondary)
.setDisabled(false);

const remove = new ButtonBuilder()
.setCustomId("remove")
.setLabel("Remove")
.setStyle(ButtonStyle.Danger)
.setDisabled(false);

const row =
new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(
remove,
pause,
play,
);
await interaction.editReply({

Copilot AI Aug 13, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interaction.editReply is called twice (lines 28 and 54-57), which will cause an error since the interaction can only be edited once after being deferred.

Copilot uses AI. Check for mistakes.
content: playResult,
components: [row],
});
} catch (err) {
logger.error(`Error in execute function for command [RANDOM]: ${err}`);
await interaction.editReply({
Expand All @@ -29,7 +64,6 @@ async function execute(

export const command: SlashCommand = {
cooldown: 15,
kind: "chat",
data: new SlashCommandBuilder()
.setName("random")
.setDescription(
Expand All @@ -48,6 +82,13 @@ export const command: SlashCommand = {
{ name: "Pop", value: "Pop" },
{ name: "Indie Pop", value: "Indie Pop" },
{ name: "Early 2000s Country", value: "Early 2000s Country" },
{ name: "Metal", value: "Metal" },
{ name: "Synthwave", value: "Synthwave" },
{ name: "Reggaeton", value: "Reggaeton" },
{ name: "Alternative Rock", value: "Alternative Rock" },
{ name: "Funk", value: "Funk" },
{ name: "Lo-fi Hip Hop", value: "Lo-fi Hip Hop" },
{ name: "Classic Soul", value: "Classic Soul" },
),
),
execute,
Expand Down
11 changes: 11 additions & 0 deletions src/commands/chat-commands/vibe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { describe, test, expect } from "@jest/globals";

describe("src/index.ts", () => {
test("sayHello function should return a greeting with the given name", () => {
// Arrange
const name = "Hello World";

// Assert
expect(name).toBe("Hello World");
});
});
44 changes: 44 additions & 0 deletions src/commands/chat-commands/vibe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { SlashCommandBuilder, ChatInputCommandInteraction } from "discord.js";
import type { SlashCommand } from "../../@types/commands.js";
import { logger } from "../../helpers/logger.js";
import { getPlaylistsByVibe } from "../../helpers/open-ai.js";
import { SongPick } from "../../@types/open-ai.js";
// import { playSongFromYoutube } from "../../helpers/playback.js";

async function execute(
interaction: ChatInputCommandInteraction,
): Promise<void> {
try {
await interaction.deferReply();
const songs: SongPick[] = await getPlaylistsByVibe(
interaction.options.getString("vibe")!,
);
logger.info(
`The songs we picked are ${songs.map((song) => song.title).join(", ")}`,
);
} catch (err) {
logger.error(`Error in execute function for command [VIBE]: ${err}`);
await interaction.editReply({
content: "An unexpected error occured, try again later.",
});
}
}

export const command: SlashCommand = {
cooldown: 20,
data: new SlashCommandBuilder()
.setName("vibe")
.setDescription(
"Generates a curated playlist to listen to based on the desired vibe.",
)
.addStringOption((option) =>
option
.setName("vibe")
.setDescription(
"Give a brief description of the vibe you are going for.",
)
.setRequired(true)
.setMaxLength(200),
),
execute,
};
42 changes: 9 additions & 33 deletions src/events/interactionCreate.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,24 @@
import { Events, MessageFlags, Interaction, CacheType } from "discord.js";
import { logger } from "../helpers/logger.js";
import { commandOnCooldown } from "../helpers/commands.js";
import { handleCommandInteraction } from "../helpers/commands.js";
import { handleButtonInteraction } from "../helpers/buttons.js";

const interactionCreateEvent = {
name: Events.InteractionCreate,
async execute(interaction: Interaction<CacheType>) {
try {
if (interaction.isChatInputCommand()) {
const command = interaction.client.commands.get(
interaction.commandName,
);
if (!command) {
logger.error(
`No command matching ${interaction.commandName} was found.`,
);
return;
}
const cooldown = commandOnCooldown(command, interaction);
if (cooldown !== -1) {
if (!interaction.isRepliable()) return;
else {
await interaction.reply({
content: `This command is on cooldown for you, wait ${cooldown} seconds`,
flags: MessageFlags.Ephemeral,
});
return;
}
}
if (command.kind === "chat") await command.execute(interaction);
await handleCommandInteraction(interaction);
} else if (interaction.isButton()) {
await handleButtonInteraction(interaction);
}
} catch (err) {
logger.error(`Error executing command for Amplify: ${err}`);
if (!interaction.isRepliable()) return;
if (interaction.replied || interaction.deferred) {
await interaction.reply({
content: "There was an error while executing this command!",
flags: MessageFlags.Ephemeral,
});
} else {
await interaction.reply({
content: "There was an error while executing this command!",
flags: MessageFlags.Ephemeral,
});
}
await interaction.reply({
content: "There was an error while executing this command!",
flags: MessageFlags.Ephemeral,
});
}
},
};
Expand Down
File renamed without changes.
101 changes: 101 additions & 0 deletions src/helpers/buttons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ButtonInteraction, CacheType } from "discord.js";
import {
pausePlayback,
resumePlayback,
removePlayback,
} from "../helpers/playback.js";

export async function handleButtonInteraction(
interaction: ButtonInteraction<CacheType>,
) {
if (!interaction.guildId) return;
switch (interaction.customId) {
case "play":
await handlePlayButton(interaction);
break;
case "remove":
await handleRemoveButton(interaction);
break;
case "pause":
await handlePauseButton(interaction);
break;
}
}

async function handlePlayButton(interaction: ButtonInteraction<CacheType>) {
const msg = resumePlayback(interaction.guildId!);
await interaction.update({
content: msg,
components: [
{
type: 1,
components: [
{
type: 2,
custom_id: "remove",
label: "Remove",
style: 4,
disabled: false,
},
{
type: 2,
custom_id: "pause",
label: "Pause",
style: 2,
disabled: false,
},
{
type: 2,
custom_id: "play",
label: "Play",
style: 1,
disabled: true,
},
],
},
],
});
}

async function handleRemoveButton(interaction: ButtonInteraction<CacheType>) {
const msg = removePlayback(interaction.guildId!);
await interaction.update({
content: msg,
components: [],
});
}

async function handlePauseButton(interaction: ButtonInteraction<CacheType>) {
const msg = pausePlayback(interaction.guildId!);
await interaction.update({
content: msg,
components: [
{
type: 1,
components: [
{
type: 2,
custom_id: "remove",
label: "Remove",
style: 4,
disabled: false,
},
{
type: 2,
custom_id: "pause",
label: "Pause",
style: 2,
disabled: true,
},
{
type: 2,
custom_id: "play",
label: "Play",
style: 1,
disabled: false,
},
],
},
],
});
}
Loading