Skip to content

Commit

Permalink
3.7.0-dev-1
Browse files Browse the repository at this point in the history
Added slash commands autocomplete for playlist names.
Added /pl-delete command.
  • Loading branch information
AlexInCube committed Aug 17, 2024
1 parent eab8632 commit d1ba342
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 37 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<p align="center">
Cool audiobot for Discord created by <a href="https://vk.com/alexincube"><b>@AlexInCube</b></a></p>

## 🖥️ Setup
- Go to [Wiki Setup Section](https://github.com/AlexInCube/AlCoTest/wiki/Setup)

## 🌟 Features
- Command /alcotest which shows your alcohol count in blood
- Audioplayer based on [Distube](https://github.com/skick1234/DisTube) with buttons
Expand Down
6 changes: 4 additions & 2 deletions src/commands/audio/pl-add.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CommandArgument, ICommand } from '../../CommandTypes.js';
import { GroupAudio } from './AudioTypes.js';
import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js';
import i18next from 'i18next';
import { UserPlaylistNamesAutocomplete } from '../../schemas/SchemaPlaylist.js';

export default function (): ICommand {
return {
Expand All @@ -17,12 +18,13 @@ export default function (): ICommand {
.setDescription(i18next.t('commands:pl-add_desc'))
.addStringOption((option) =>
option
.setName('request')
.setName('playlist_name')
.setDescription(i18next.t('commands:pl-add_link'))
.setAutocomplete(true)
.setRequired(true)
),
execute: async (interaction) => {}
execute: async (interaction) => {},

Check warning on line 26 in src/commands/audio/pl-add.command.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

'interaction' is defined but never used
autocomplete: UserPlaylistNamesAutocomplete
},
group: GroupAudio,
guild_data: {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/audio/pl-create.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export default function (): ICommand {
.setDescription(i18next.t('commands:pl-create_desc'))
.addStringOption((option) =>
option
.setName('name')
.setName('playlist_name')
.setDescription(i18next.t('commands:pl_arg_name'))
.setRequired(true)
.setMinLength(PlaylistNameMinLength)
.setMaxLength(PlaylistNameMaxLength)
),
execute: async (interaction) => {
const playlistName = interaction.options.getString('name')!;
const playlistName = interaction.options.getString('playlist_name')!;

await plCreateAndReply(playlistName, interaction, interaction.user.id);
}
Expand Down
62 changes: 52 additions & 10 deletions src/commands/audio/pl-delete.command.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,76 @@
import { CommandArgument, ICommand } from '../../CommandTypes.js';
import { GroupAudio } from './AudioTypes.js';
import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js';
import { ChatInputCommandInteraction, Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js';
import i18next from 'i18next';
import {
PlaylistIsNotExists,
UserPlaylistDelete,
UserPlaylistNamesAutocomplete
} from '../../schemas/SchemaPlaylist.js';
import { generateSimpleEmbed } from '../../utilities/generateSimpleEmbed.js';
import { generateErrorEmbed } from '../../utilities/generateErrorEmbed.js';
import { ENV } from '../../EnvironmentVariables.js';
import { loggerError } from '../../utilities/logger.js';

export default function (): ICommand {
return {
text_data: {
name: 'pl-delete',
description: i18next.t('commands:pl-delete_desc'),
arguments: [new CommandArgument(i18next.t('commands:pl-delete_link'), true)],
execute: async (message: Message) => {}
arguments: [new CommandArgument(i18next.t('commands:pl_arg_name'), true)],
execute: async (message: Message, args: Array<string>) => {
const playlistsName = args[0];

await plDeleteAndReply(message, message.author.id, playlistsName);
}
},
slash_data: {
slash_builder: new SlashCommandBuilder()
.setName('pl-delete')
.setDescription(i18next.t('commands:pl-delete_desc'))
.addStringOption((option) =>
option
.setName('request')
.setDescription(i18next.t('commands:pl-delete_link'))
.setName('playlist_name')
.setDescription(i18next.t('commands:pl_arg_name'))
.setAutocomplete(true)
.setRequired(true)
),
execute: async (interaction) => {}
execute: async (interaction) => {
const playlistsName = interaction.options.getString('playlist_name')!;

await plDeleteAndReply(interaction, interaction.user.id, playlistsName);
},
autocomplete: UserPlaylistNamesAutocomplete
},
group: GroupAudio,
guild_data: {
guild_only: true,
voice_required: true
},
bot_permissions: [PermissionsBitField.Flags.SendMessages]
};
}

async function plDeleteAndReply(ctx: Message | ChatInputCommandInteraction, userID: string, playlistName: string) {
try {
await UserPlaylistDelete(userID, playlistName);

await ctx.reply({
embeds: [generateSimpleEmbed(i18next.t('commands:pl-delete_embed_deleted', { name: playlistName }))],
ephemeral: true
});
} catch (e) {
if (e instanceof PlaylistIsNotExists) {
await ctx.reply({
embeds: [
generateErrorEmbed(
i18next.t('commands:pl_error_playlist_not_exists', {
name: playlistName,
interpolation: { escapeValue: false }
})
)
],
ephemeral: true
});
}

await ctx.reply({ embeds: [generateErrorEmbed(i18next.t('commands:pl-delete_error'))], ephemeral: true });
if (ENV.BOT_VERBOSE_LOGGING) loggerError(e);
}
}
22 changes: 13 additions & 9 deletions src/commands/audio/pl-display.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@ import { CommandArgument, ICommand } from '../../CommandTypes.js';
import { GroupAudio } from './AudioTypes.js';
import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js';
import i18next from 'i18next';
import {
PlaylistNameMaxLength,
PlaylistNameMinLength,
UserPlaylistNamesAutocomplete
} from '../../schemas/SchemaPlaylist.js';

export default function (): ICommand {
return {
text_data: {
name: 'pl-display',
description: i18next.t('commands:pl-display_desc'),
arguments: [new CommandArgument(i18next.t('commands:pl-display_link'), true)],
arguments: [new CommandArgument(i18next.t('commands:pl_arg_name'), true)],
execute: async (message: Message) => {}

Check warning on line 17 in src/commands/audio/pl-display.command.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

'message' is defined but never used
},
slash_data: {
Expand All @@ -17,18 +22,17 @@ export default function (): ICommand {
.setDescription(i18next.t('commands:pl-display_desc'))
.addStringOption((option) =>
option
.setName('request')
.setDescription(i18next.t('commands:pl-display_link'))
.setAutocomplete(true)
.setName('playlist_name')
.setDescription(i18next.t('commands:pl_arg_name'))
.setRequired(true)
.setAutocomplete(true)
.setMinLength(PlaylistNameMinLength)
.setMaxLength(PlaylistNameMaxLength)
),
execute: async (interaction) => {}
execute: async (interaction) => {},

Check warning on line 32 in src/commands/audio/pl-display.command.ts

View workflow job for this annotation

GitHub Actions / Run ESLint

'interaction' is defined but never used
autocomplete: UserPlaylistNamesAutocomplete
},
group: GroupAudio,
guild_data: {
guild_only: true,
voice_required: true
},
bot_permissions: [PermissionsBitField.Flags.SendMessages]
};
}
6 changes: 4 additions & 2 deletions src/commands/audio/pl-play.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CommandArgument, ICommand } from '../../CommandTypes.js';
import { GroupAudio } from './AudioTypes.js';
import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js';
import i18next from 'i18next';
import { UserPlaylistNamesAutocomplete } from '../../schemas/SchemaPlaylist.js';

export default function (): ICommand {
return {
Expand All @@ -17,12 +18,13 @@ export default function (): ICommand {
.setDescription(i18next.t('commands:pl-play_desc'))
.addStringOption((option) =>
option
.setName('request')
.setName('playlist_name')
.setDescription(i18next.t('commands:pl-play_arg'))
.setAutocomplete(true)
.setRequired(true)
),
execute: async (interaction) => {}
execute: async (interaction) => {},
autocomplete: UserPlaylistNamesAutocomplete
},
group: GroupAudio,
guild_data: {
Expand Down
6 changes: 4 additions & 2 deletions src/commands/audio/pl-remove.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CommandArgument, ICommand } from '../../CommandTypes.js';
import { GroupAudio } from './AudioTypes.js';
import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js';
import i18next from 'i18next';
import { UserPlaylistNamesAutocomplete } from '../../schemas/SchemaPlaylist.js';

export default function (): ICommand {
return {
Expand All @@ -17,12 +18,13 @@ export default function (): ICommand {
.setDescription(i18next.t('commands:pl-remove_desc'))
.addStringOption((option) =>
option
.setName('request')
.setName('playlist_name')
.setDescription(i18next.t('commands:pl-remove_link'))
.setAutocomplete(true)
.setRequired(true)
),
execute: async (interaction) => {}
execute: async (interaction) => {},
autocomplete: UserPlaylistNamesAutocomplete
},
group: GroupAudio,
guild_data: {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/audio/play.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export async function songSearchAutocomplete(interaction: AutocompleteInteractio
type: SearchResultType.VIDEO
});

const finalResult = choices.items.map((video: ytsr.Video) => {
const finalResult: Array<ApplicationCommandOptionChoiceData> = choices.items.map((video: ytsr.Video) => {
const duration = video.isLive ? liveText : video.duration;
let choiceString = `${duration} | ${truncateString(video.author?.name ?? ' ', 20)} | `;
choiceString += truncateString(video.name, 100 - choiceString.length);
Expand All @@ -142,7 +142,7 @@ export async function songSearchAutocomplete(interaction: AutocompleteInteractio
};
});

await interaction.respond(finalResult as Array<ApplicationCommandOptionChoiceData>);
await interaction.respond(finalResult);
return;
}

Expand Down
9 changes: 8 additions & 1 deletion src/locales/en/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,17 @@
"history_embed_no_songs": "No songs have been played on this server yet, be the first who play the song",
"history_embed_title": "Songs history for server",
"pl_arg_name": "playlist name",
"pl_error_playlist_not_exists": "Playlist ``{{name}}`` is not exists",
"pl-create_desc": "Create a empty playlist",
"pl-create_success": "Playlist ``{{name}}`` created",
"pl-create_error_duplicate": "Playlist with name ``{{name}}`` already exists",
"pl-create_error_max_playlists_count": "You already created maximum playlists ({{count}}), delete previous playlists to create new",
"pl-create_error_validation": "Name must have length from {{min}} to {{max}}",
"pl-create_error": "Error when creating playlists"
"pl-create_error": "Unknown error when creating playlists",
"pl-my_desc": "List of created playlists",
"pl-my_embed_title": "List of your playlists",
"pl-my_embed_error": "You dont have any playlists, use /pl-create to create your first playlist",
"pl-delete_desc": "Delete playlist by its name",
"pl-delete_embed_deleted": "Playlist {{name}} is deleted",
"pl-delete_error": "Unknown error when deleting playlist"
}
8 changes: 6 additions & 2 deletions src/locales/ru/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,17 @@
"history_embed_no_songs": "На этом сервере ещё не было отыграно ещё ни одной песни, станьте первым!",
"history_embed_title": "История песен для сервера",
"pl_arg_name": "имя плейлиста",
"pl_error_playlist_not_exists": "Плейлист ``{{name}}`` не существует",
"pl-create_desc": "Создание пустого плейлиста",
"pl-create_success": "Плейлист ``{{name}}`` создан",
"pl-create_error_duplicate": "Плейлист с именем ``{{name}}`` уже существует",
"pl-create_error_max_playlists_count": "Вы уже создали максимальное количество плейлистов ({{count}}), удалите прошлые плейлисты чтобы создать новые",
"pl-create_error_validation": "Название должно иметь длину от {{min}} до {{max}}",
"pl-create_error": "Ошибка при создании плейлиста",
"pl-create_error": "Неизвестная ошибка при создании плейлиста",
"pl-my_desc": "Список созданных плейлистов",
"pl-my_embed_title": "Список ваших плейлистов",
"pl-my_embed_error": "У вас нет никаких плейлистов, используйте /pl-create чтобы создать ваш первый плейлист"
"pl-my_embed_error": "У вас нет никаких плейлистов, используйте /pl-create чтобы создать ваш первый плейлист",
"pl-delete_desc": "Удаляет плейлист по его имени",
"pl-delete_embed_deleted": "Плейлист {{name}} удалён",
"pl-delete_error": "Неизвестная ошибка при удалении плейлиста"
}
38 changes: 33 additions & 5 deletions src/schemas/SchemaPlaylist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Document, model, Schema } from 'mongoose';
import { Song } from 'distube';
import { ENV } from '../EnvironmentVariables.js';
import { getOrCreateUser } from './SchemaUser.js';
import { ApplicationCommandOptionChoiceData, AutocompleteInteraction } from 'discord.js';

interface ISchemaSongPlaylistUnit {
name: string;
Expand Down Expand Up @@ -32,7 +33,7 @@ export const PlaylistNameMaxLength = 50;
export const SchemaPlaylist = new Schema<ISchemaPlaylist>(
{
name: { type: String, required: true, maxlength: PlaylistNameMaxLength, minlength: PlaylistNameMinLength },
songs: { type: [SchemaSongPlaylistUnit], default: [] }
songs: { type: [SchemaSongPlaylistUnit], default: [], select: false }
},
{
timestamps: {
Expand All @@ -54,9 +55,10 @@ export class PlaylistAlreadyExists extends Error {
}

export class PlaylistIsNotExists extends Error {
constructor() {
constructor(playlistName: string) {
super();
this.name = 'PlaylistIsNotExistsError';
this.message = `This playlist is not exists ${playlistName}`;
}
}

Expand Down Expand Up @@ -114,13 +116,21 @@ export async function UserPlaylistGetPlaylists(userID: string): Promise<Array<Pl
}

export async function UserPlaylistDelete(userID: string, name: string): Promise<void> {
const user = await (await getOrCreateUser(userID)).populate('playlists');
const playlist = await UserPlaylistGet(userID, name);
playlist?.deleteOne();

if (!playlist || !user.playlists) throw new PlaylistIsNotExists(name);

await playlist.deleteOne({ name });

user.playlists = user.playlists.filter((playlist) => playlist.name !== name);

await user.save();
}

export async function UserPlaylistAddSong(userID: string, name: string, song: Song): Promise<void> {
const playlist = await UserPlaylistGet(userID, name);
if (!playlist) throw new Error(`Playlist is not exists: ${playlist}`);
if (!playlist) throw new PlaylistIsNotExists(name);

if (playlist.songs.length > ENV.BOT_MAX_SONGS_IN_USER_PLAYLIST) {
throw new Error(`Playlists can contain only ${ENV.BOT_MAX_SONGS_IN_USER_PLAYLIST}`);
Expand All @@ -133,9 +143,27 @@ export async function UserPlaylistAddSong(userID: string, name: string, song: So

export async function UserPlaylistRemoveSong(userID: string, name: string, index: number): Promise<void> {
const playlist = await UserPlaylistGet(userID, name);
if (!playlist) throw new Error(`Playlist is not exists: ${playlist}`);
if (!playlist) throw new PlaylistIsNotExists(name);

playlist.songs.splice(index, 1);

await playlist.save();
}

export async function UserPlaylistNamesAutocomplete(interaction: AutocompleteInteraction) {
const focusedValue = interaction.options.getFocused(true);

const playlists = await UserPlaylistGetPlaylists(interaction.user.id);
let finalResult: Array<ApplicationCommandOptionChoiceData> = [];

if (playlists) {
finalResult = playlists?.map((playlists) => {
return {
name: playlists.name,
value: playlists.name
};
});
}

await interaction.respond(finalResult);
}

0 comments on commit d1ba342

Please sign in to comment.