From 08b40f052e9bc32d291a9cccf77a9aad17799dc4 Mon Sep 17 00:00:00 2001 From: AlexInCube Date: Tue, 9 Jul 2024 13:11:03 +0300 Subject: [PATCH] 3.1.0 Added /lyrics command and button in audioplayer which fetch lyrics for the current playing song. Commands now can be disabled during loading. --- .env | 4 +- icons/audioplayer/player/lyrics.png | Bin 0 -> 11240 bytes package.json | 3 +- pnpm-lock.yaml | 27 +++++++--- src/CommandTypes.ts | 23 ++++++--- src/EnvironmentVariables.ts | 4 +- src/audioplayer/AudioPlayerCore.ts | 22 +++++++++ src/audioplayer/AudioPlayerTypes.ts | 3 +- src/audioplayer/LoadPlugins.ts | 2 +- src/audioplayer/Lyrics.ts | 40 +++++++++++++++ .../MessagePlayerButtonsHandler.ts | 17 ++++++- src/commands/audio/lyrics.command.ts | 43 ++++++++++++++++ src/commands/audio/play.command.ts | 4 +- src/handlers/Command.handler.ts | 8 ++- src/locales/en/commands.json | 6 ++- src/locales/ru/commands.json | 6 ++- wiki/API-Configure.md | 20 ++++++-- wiki/Setup.md | 46 ++++++++++-------- 18 files changed, 229 insertions(+), 49 deletions(-) create mode 100644 icons/audioplayer/player/lyrics.png create mode 100644 src/audioplayer/Lyrics.ts create mode 100644 src/commands/audio/lyrics.command.ts diff --git a/.env b/.env index 6806c45..ceeb839 100644 --- a/.env +++ b/.env @@ -7,8 +7,6 @@ BOT_DISCORD_TOKEN=undefined BOT_DISCORD_CLIENT_ID=undefined BOT_DISCORD_OVERPOWERED_ID=undefined -BOT_YOUTUBE_COOKIE=undefined - BOT_SPOTIFY_CLIENT_SECRET=undefined BOT_SPOTIFY_CLIENT_ID=undefined @@ -18,5 +16,7 @@ BOT_SOUNDCLOUD_TOKEN=undefined BOT_YANDEXMUSIC_TOKEN=undefined BOT_YANDEXMUSIC_UID=undefined +BOT_GENIUS_TOKEN=undefined + MONGO_URI=undefined MONGO_DATABASE_NAME=undefined diff --git a/icons/audioplayer/player/lyrics.png b/icons/audioplayer/player/lyrics.png new file mode 100644 index 0000000000000000000000000000000000000000..4b39bff7b300d714ecbd5e8638f414d086e42550 GIT binary patch literal 11240 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 V3++8!#=m=T^iQ)x1qKdL{|kwA?>GPe literal 0 HcmV?d00001 diff --git a/package.json b/package.json index f889ea4..510680d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "aicbot", - "version": "3.0.1", + "version": "3.1.0", "description": "Discord Bot for playing music", "main": "build/main.js", "scripts": { @@ -29,6 +29,7 @@ "distube-apple-music": "^0.1.0", "distube-yandex-music-plugin": "^1.0.4", "dotenv": "^16.4.5", + "genius-lyrics": "^4.4.7", "i18next": "^22.5.1", "i18next-fs-backend": "^2.3.1", "mongoose": "^7.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 13bf14e..df3fb9b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,6 +56,9 @@ importers: dotenv: specifier: ^16.4.5 version: 16.4.5 + genius-lyrics: + specifier: ^4.4.7 + version: 4.4.7 i18next: specifier: ^22.5.1 version: 22.5.1 @@ -649,8 +652,8 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -758,6 +761,9 @@ packages: engines: {node: '>=10'} deprecated: This package is no longer supported. + genius-lyrics@4.4.7: + resolution: {integrity: sha512-cgO5nSeFqtLZAUyWB+8XWMRBIRzPUSUC42N3CoDGRgKX1anGAyDUhM6/RVIJXCNnQa6XHZHswKcKgHaRiyl+GQ==} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -1176,8 +1182,8 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.12.2: - resolution: {integrity: sha512-x+NLUpx9SYrcwXtX7ob1gnkSems4i/mGZX5SlYxwIau6RrUSODO89TR/XDGGpn5RPWSYIB+aSfuSlV5+CmbTBg==} + qs@6.12.3: + resolution: {integrity: sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==} engines: {node: '>=0.6'} querystringify@2.2.0: @@ -2112,7 +2118,7 @@ snapshots: eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - esquery: 1.5.0 + esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 @@ -2142,7 +2148,7 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.12.1) eslint-visitor-keys: 3.4.3 - esquery@1.5.0: + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -2252,6 +2258,11 @@ snapshots: wide-align: 1.1.5 optional: true + genius-lyrics@4.4.7: + dependencies: + node-html-parser: 6.1.13 + undici: 6.19.2 + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -2622,7 +2633,7 @@ snapshots: punycode@2.3.1: {} - qs@6.12.2: + qs@6.12.3: dependencies: side-channel: 1.0.6 @@ -2764,7 +2775,7 @@ snapshots: formidable: 1.2.6 methods: 1.1.2 mime: 2.6.0 - qs: 6.12.2 + qs: 6.12.3 readable-stream: 3.6.2 semver: 7.6.2 transitivePeerDependencies: diff --git a/src/CommandTypes.ts b/src/CommandTypes.ts index 33e0e6e..26f4d60 100644 --- a/src/CommandTypes.ts +++ b/src/CommandTypes.ts @@ -19,13 +19,22 @@ export type SlashBuilder = | SlashCommandSubcommandsOnlyBuilder | Omit; export interface ICommand { - text_data: ITextCommandData; // Related to text commands - slash_data?: ISlashCommandData; // Related to slash commands - group: ICommandGroup; // Group for better orientation in /help - user_permissions?: Array; // Permissions for user, to allow executing commands - bot_permissions: Array; // Permissions for bot, to try to execute commands - hidden?: boolean; // Hidden from everything (disable slash_data property if true) - guild_data?: IGuildData; // Guild related data such as voice settings + // Disable command registering + disable?: boolean | false; + // Related to text commands + text_data: ITextCommandData; + // Related to slash commands + slash_data?: ISlashCommandData; + // Group for better orientation in /help + group: ICommandGroup; + // Permissions for user, to allow executing commands + user_permissions?: Array; + // Permissions for bot, to try to execute commands + bot_permissions: Array; + // Hidden from everything (disable slash_data property if true) + hidden?: boolean; + // Guild related data such as voice settings + guild_data?: IGuildData; } interface ITextCommandData { diff --git a/src/EnvironmentVariables.ts b/src/EnvironmentVariables.ts index 3ebdc9f..5a816ec 100644 --- a/src/EnvironmentVariables.ts +++ b/src/EnvironmentVariables.ts @@ -41,7 +41,9 @@ const envVariables = z.object({ BOT_SPOTIFY_CLIENT_ID: z.string().optional(), BOT_YANDEXMUSIC_TOKEN: z.string().optional(), - BOT_YANDEXMUSIC_UID: z.coerce.number().optional() + BOT_YANDEXMUSIC_UID: z.coerce.number().optional(), + + BOT_GENIUS_TOKEN: z.string().optional() }); export const ENV = envVariables.parse(process.env); diff --git a/src/audioplayer/AudioPlayerCore.ts b/src/audioplayer/AudioPlayerCore.ts index b16a8db..a86a364 100644 --- a/src/audioplayer/AudioPlayerCore.ts +++ b/src/audioplayer/AudioPlayerCore.ts @@ -11,6 +11,7 @@ import { LoadPlugins } from './LoadPlugins.js'; import { generateAddedPlaylistMessage } from './util/generateAddedPlaylistMessage.js'; import { generateAddedSongMessage } from './util/generateAddedSongMessage.js'; import { + ButtonInteraction, Client, CommandInteraction, Embed, @@ -22,6 +23,7 @@ import { } from 'discord.js'; import { joinVoiceChannel } from '@discordjs/voice'; import { generateWarningEmbed } from '../utilities/generateWarningEmbed.js'; +import { generateLyricsEmbed } from './Lyrics.js'; export const queueSongsLimit = 500; @@ -218,6 +220,26 @@ export class AudioPlayerCore { } } + async showLyrics(interaction: ButtonInteraction) { + if (!interaction.guild) return; + const queue = this.distube.getQueue(interaction.guild); + if (!queue) { + return; + } + + const song = queue.songs[0]; + + let lyricsQuery: string; + + if (song.source === 'youtube') { + lyricsQuery = song.name!; + } else { + lyricsQuery = `${song.name} - ${song.uploader.name}`; + } + + await interaction.reply({ embeds: [await generateLyricsEmbed(lyricsQuery)] }); + } + async showQueue(interaction: Interaction) { if (!interaction.guild) return; const queue = this.distube.getQueue(interaction.guild); diff --git a/src/audioplayer/AudioPlayerTypes.ts b/src/audioplayer/AudioPlayerTypes.ts index 55581f3..4126c2e 100644 --- a/src/audioplayer/AudioPlayerTypes.ts +++ b/src/audioplayer/AudioPlayerTypes.ts @@ -9,7 +9,8 @@ export enum AudioPlayerIcons { previous = '<:previousbutton:1092107334542696512>', skip = '<:skipbutton:1092107438234275900>', shuffle = '<:shufflebutton:1092107651384614912>', - list = '<:songlistwhite:1014551771705782405>' + list = '<:songlistwhite:1014551771705782405>', + lyrics = '<:lyrics:1260156581794811974>' } export enum AudioSourceIcons { diff --git a/src/audioplayer/LoadPlugins.ts b/src/audioplayer/LoadPlugins.ts index 14ec0cb..9730d5c 100644 --- a/src/audioplayer/LoadPlugins.ts +++ b/src/audioplayer/LoadPlugins.ts @@ -2,7 +2,6 @@ import { ExtractorPlugin, InfoExtractorPlugin, PlayableExtractorPlugin } from 'd import { BOT_YOUTUBE_COOKIE, ENV } from '../EnvironmentVariables.js'; import { loggerSend, loggerWarn } from '../utilities/logger.js'; import { SpotifyPlugin } from '@distube/spotify'; -import { SoundCloudPlugin } from '@distube/soundcloud'; import { YtDlpPlugin } from '@distube/yt-dlp'; import { loggerPrefixAudioplayer } from './AudioPlayerCore.js'; import { YouTubePlugin } from '@distube/youtube'; @@ -10,6 +9,7 @@ import { DirectLinkPlugin } from '@distube/direct-link'; import { FilePlugin } from '@distube/file'; import { AppleMusicPlugin } from 'distube-apple-music'; import { YandexMusicPlugin } from 'distube-yandex-music-plugin'; +import { SoundCloudPlugin } from '@distube/soundcloud'; export type DistubePlugin = ExtractorPlugin | InfoExtractorPlugin | PlayableExtractorPlugin; diff --git a/src/audioplayer/Lyrics.ts b/src/audioplayer/Lyrics.ts new file mode 100644 index 0000000..b61e0d9 --- /dev/null +++ b/src/audioplayer/Lyrics.ts @@ -0,0 +1,40 @@ +import Genius from 'genius-lyrics'; +import { ENV } from '../EnvironmentVariables.js'; +import { Colors, EmbedBuilder } from 'discord.js'; +import i18next from 'i18next'; +import { loggerWarn } from '../utilities/logger.js'; +import { generateErrorEmbed } from '../utilities/generateErrorEmbed.js'; +const Lyrics = new Genius.Client(ENV.BOT_GENIUS_TOKEN); + +if (!ENV.BOT_GENIUS_TOKEN) { + loggerWarn('BOT_GENIUS_TOKEN is not provided, lyrics module disabled', 'Lyrics'); +} + +export async function getLyricsSong(searchQuery: string) { + const geniusSearch = await Lyrics.songs.search(searchQuery); + + if (geniusSearch.length === 0) { + return undefined; + } + + return geniusSearch[0]; +} + +export async function generateLyricsEmbed(songQuery: string) { + const geniusSong = await getLyricsSong(songQuery); + + if (!geniusSong) { + return generateErrorEmbed(i18next.t('commands:lyrics_embed_lyrics_not_found')); + } + + const lyrics = await geniusSong.lyrics(); + + const lyricsText = lyrics.slice(0, 4096); + + return new EmbedBuilder() + .setTitle(geniusSong.title) + .setURL(geniusSong.url) + .setDescription(lyricsText) + .setColor(Colors.Yellow) + .setFooter({ text: i18next.t('commands:lyrics_embed_text_not_correct') }); +} diff --git a/src/audioplayer/MessagePlayerButtonsHandler.ts b/src/audioplayer/MessagePlayerButtonsHandler.ts index 3e17bae..34965ce 100644 --- a/src/audioplayer/MessagePlayerButtonsHandler.ts +++ b/src/audioplayer/MessagePlayerButtonsHandler.ts @@ -24,6 +24,7 @@ import { generateEmbedAudioPlayerPrevious, generateEmbedAudioPlayerPreviousFailure } from '../commands/audio/previous.command.js'; +import { ENV } from '../EnvironmentVariables.js'; enum ButtonIDs { stopMusic = 'stopMusic', @@ -33,7 +34,8 @@ enum ButtonIDs { skipSong = 'skipSong', //downloadSong = 'downloadSong', shuffle = 'shuffle', - showQueue = 'showQueue' + showQueue = 'showQueue', + lyrics = 'lyrics' } const rowPrimary = new ActionRowBuilder().addComponents( @@ -94,6 +96,15 @@ const rowSecondary = new ActionRowBuilder().addComponents( .setEmoji(AudioPlayerIcons.list) ); +if (ENV.BOT_GENIUS_TOKEN) { + rowSecondary.addComponents( + new ButtonBuilder() + .setCustomId(ButtonIDs.lyrics) + .setStyle(ButtonStyle.Secondary) + .setEmoji(AudioPlayerIcons.lyrics) + ); +} + const rowWithOnlyStop = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId(ButtonIDs.stopMusic) @@ -210,6 +221,10 @@ export class MessagePlayerButtonsHandler { } break; } + + case ButtonIDs.lyrics: { + await this.client.audioPlayer.showLyrics(ButtonInteraction); + } } } catch (e) { loggerError(e); diff --git a/src/commands/audio/lyrics.command.ts b/src/commands/audio/lyrics.command.ts new file mode 100644 index 0000000..e8a97c9 --- /dev/null +++ b/src/commands/audio/lyrics.command.ts @@ -0,0 +1,43 @@ +import { CommandArgument, ICommand } from '../../CommandTypes.js'; +import { Message, PermissionsBitField, SlashCommandBuilder } from 'discord.js'; +import i18next from 'i18next'; +import { services } from './play.command.js'; +import { GroupAudio } from './AudioTypes.js'; +import { generateLyricsEmbed } from '../../audioplayer/Lyrics.js'; +import { ENV } from '../../EnvironmentVariables.js'; + +export default function (): ICommand { + return { + disable: !ENV.BOT_GENIUS_TOKEN, + text_data: { + name: 'lyrics', + description: i18next.t('commands:lyrics_desc'), + arguments: [ + new CommandArgument(i18next.t('commands:lyrics_arg_query', { services: services }), true) + ], + execute: async (message: Message, args: string[]) => { + const songQuery = args.join(' '); + + await message.reply({ embeds: [await generateLyricsEmbed(songQuery)] }); + } + }, + slash_data: { + slash_builder: new SlashCommandBuilder() + .setName('lyrics') + .setDescription(i18next.t('commands:lyrics_desc')) + .addStringOption((option) => + option + .setName('request') + .setDescription(i18next.t('commands:lyrics_arg_query', { services: services })) + .setRequired(true) + ), + execute: async (interaction) => { + const songQuery = interaction.options.getString('request')!; + + await interaction.reply({ embeds: [await generateLyricsEmbed(songQuery)] }); + } + }, + group: GroupAudio, + bot_permissions: [PermissionsBitField.Flags.SendMessages, PermissionsBitField.Flags.ViewChannel] + }; +} diff --git a/src/commands/audio/play.command.ts b/src/commands/audio/play.command.ts index 9629c7e..d202c8b 100644 --- a/src/commands/audio/play.command.ts +++ b/src/commands/audio/play.command.ts @@ -20,7 +20,7 @@ import ytsr from '@distube/ytsr'; import { queueSongsLimit } from '../../audioplayer/AudioPlayerCore.js'; import { generateWarningEmbed } from '../../utilities/generateWarningEmbed.js'; -export const services = 'Youtube, Spotify, Soundcloud, Yandex Music, HTTP-stream'; +export const services = 'Youtube, Spotify, Soundcloud, Yandex Music, Apple Music, HTTP-stream'; export default function (): ICommand { return { text_data: { @@ -30,6 +30,8 @@ export default function (): ICommand { new CommandArgument(i18next.t('commands:play_arg_link', { services: services }), true) ], execute: async (message: Message, args: string[]) => { + // Play command accept only one arg is a query string. + // In text command system we need to merge all words for request in one string const songQuery = args.join(' '); const member = message.member as GuildMember; diff --git a/src/handlers/Command.handler.ts b/src/handlers/Command.handler.ts index 19e160c..4466572 100644 --- a/src/handlers/Command.handler.ts +++ b/src/handlers/Command.handler.ts @@ -1,5 +1,5 @@ import { Client, Collection, REST, Routes } from 'discord.js'; -import { loggerError, loggerSend } from '../utilities/logger.js'; +import { loggerError, loggerSend, loggerWarn } from '../utilities/logger.js'; import { ICommand, ICommandGroup, SlashBuilder } from '../CommandTypes.js'; import * as fs from 'fs'; import * as path from 'path'; @@ -30,6 +30,12 @@ const handler = async (client: Client) => { const commandModule = await import(importPath); const command: ICommand = commandModule.default(); + + if (command.disable) { + loggerWarn(`Command is disabled: ${importPath}`, loggerPrefixCommandHandler); + continue; + } + const group: ICommandGroup = command.group; commands.set(command.text_data.name, command); diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index 763f7f0..71c52e8 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -65,5 +65,9 @@ "status_embed_cpu": "CPU", "status_embed_cpu_usage": "CPU Usage", "status_embed_ram_usage": "RAM Usage", - "status_embed_guilds_count": "Servers count" + "status_embed_guilds_count": "Servers count", + "lyrics_desc": "Searching lyrics", + "lyrics_arg_query": "title of song", + "lyrics_embed_text_not_correct": "The text may not be correct", + "lyrics_embed_lyrics_not_found": "Lyrics not found" } diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json index 7e64d87..3485a38 100644 --- a/src/locales/ru/commands.json +++ b/src/locales/ru/commands.json @@ -65,5 +65,9 @@ "status_embed_cpu": "Процессор", "status_embed_cpu_usage": "Нагрузка на процессор", "status_embed_ram_usage": "Используется ОЗУ", - "status_embed_guilds_count": "Количество серверов" + "status_embed_guilds_count": "Количество серверов", + "lyrics_desc": "Ищет текст песни", + "lyrics_arg_query": "Название песни", + "lyrics_embed_text_not_correct": "Текст может быть неверным", + "lyrics_embed_lyrics_not_found": "Текст песни не найден" } diff --git a/wiki/API-Configure.md b/wiki/API-Configure.md index 4c13f4c..df37740 100644 --- a/wiki/API-Configure.md +++ b/wiki/API-Configure.md @@ -22,6 +22,7 @@ You can edit your application's name, description, and avatar here. Once you've ![discord-dev-enable-intents](images/api-configure/discord-dev-enable-intents.png) # YouTube Cookie (optional) + Preferable to provide cookies for YouTube. This will allow you to play 18+ videos and bypass YouTube rate limiting error (429 Error). I highly recommend that you create a new Google account from which you can get the cookie. @@ -38,23 +39,27 @@ I highly recommend that you create a new Google account from which you can get t 5. Create file yt-cookies.json and paste cookie in this file # Yandex Music (optional) + If you do not provide token and UID, Yandex Music will not work at all. > [!WARNING] > If your bot is outside Russia VDS, you must have a Yandex Plus subscription to play songs. ## Token -1. Login into [Yandex](https://passport.yandex.ru/auth) account. + +1. Login into [Yandex](https://passport.yandex.ru/auth) account. 2. Download [browser extension](https://chromewebstore.google.com/detail/yandex-music-token/lcbjeookjibfhjjopieifgjnhlegmkib) -This must look like this ![yandex-extension](images/api-configure/yandex-music-extension.png) + This must look like this ![yandex-extension](images/api-configure/yandex-music-extension.png) 3. Click "Скопировать токен" button. ## UID + 1. Login into [Yandex](https://passport.yandex.ru/auth) account. 2. You can retrieve uid by opening [Yandex Mail](https://mail.yandex.ru) and copy uid from the url in the address bar. -![yandex-uid](images/api-configure/yandex-music-uid.png) + ![yandex-uid](images/api-configure/yandex-music-uid.png) # Spotify (optional) + Spotify Module can work without provided data, but for more stability better provide custom application data. > [!WARNING] @@ -76,3 +81,12 @@ Spotify Module can work without provided data, but for more stability better pro 4. Find the request that has the name session (you can filter by typing session in the filter box) and click on it 5. Go to the Payload tab 6. You should see your client id in the Query String Parameters section, and your oauth token (access_token) in the Request Payload section + +# Genius (optional) + +> [!WARNING] +> Provide to enable /lyrics command and lyrics button in audioplayer + +1. Go to [Genius](https://genius.com/login) and login. +2. Go to [Genius Developer Dashboard](https://genius.com/api-clients/new) and create a new app +3. Generate and copy client access token diff --git a/wiki/Setup.md b/wiki/Setup.md index 351960c..bba576e 100644 --- a/wiki/Setup.md +++ b/wiki/Setup.md @@ -5,29 +5,31 @@ But in both cases, you need to configure .env file. Also you need retrieve token, client id and enable intents on Discord Developer Portal. -- Create file .env.production -- Fill all fields in .env.production. If the field is marked as (Optional), you can skip it. +- Create file .env.production (if you developer create .env.development) +- Fill all fields in .env.* If the field is marked as (Optional), you can skip it. - (Required) To get Discord Token and enable intents, follow the [Discord Developer Portal](https://github.com/AlexInCube/AlCoTest/wiki/API-Configure#discord-developer-portal-required) section. - (Optional) To get Spotify Secret and ID, follow the [Spotify](https://github.com/AlexInCube/AlCoTest/wiki/API-Configure#spotify-optional) section. - (Optional) To get Yandex Music token, follow the [Yandex Music](https://github.com/AlexInCube/AlCoTest/wiki/API-Configure#yandex-music-optional) section. - (Optional) To get SoundCloud token, follow the [Soundcloud](https://github.com/AlexInCube/AlCoTest/wiki/API-Configure#soundcloud-optional) section. - -| Name | Example | Description | Required? | -|------------------------------|-----------------------|---------------------------------------------------------------------------|-----------| -| `BOT_VERBOSE_LOGGING` | false | The bot will give more information to the console, useful for debugging | ❌ | -| `BOT_COMMAND_PREFIX` | // | Used only for text commands | ✔️ | -| `BOT_LANGUAGE` | en | Supported values: en ru | ❌ | -| `MONGO_URI` | mongodb://mongo:27017 | The public key for sending notifications | ✔️ | -| `MONGO_DATABASE_NAME` | aicbot | Database name in MongoDB | ✔️ | -| `BOT_DISCORD_TOKEN` | ODEzNzUwMTY1N... | Token from Discord Developer Portal | ✔️ | -| `BOT_DISCORD_CLIENT_ID` | 813750165783... | Application ID from Discord Developer Portal | ✔️ | -| `BOT_DISCORD_OVERPOWERED_ID` | 29016845994426.... | Discord bot owner user ID, required for having more bot control for owner | ✔️ | -| `BOT_SPOTIFY_CLIENT_SECRET` | | Used when the Spotify module cannot get the credentials automatically | ❌ | -| `BOT_SPOTIFY_CLIENT_ID` | | Used when the Spotify module get the credentials automatically | ❌ | -| `BOT_YANDEXMUSIC_TOKEN` | | Provide to enable Yandex Music module | ❌ | -| `BOT_YANDEXMUSIC_UID` | | Provide to enable Yandex Music module | ❌ | -| `BOT_SOUNDCLOUD_CLIENT_ID` | | Provide to fetch more data with SoundCloud Go+ account | ❌ | -| `BOT_SOUNDCLOUD_TOKEN` | | Provide to fetch more data with SoundCloud Go+ account | ❌ | +- (Optional) To get Genius token, follow the [Genius](https://github.com/AlexInCube/AlCoTest/wiki/API-Configure#genius-optional) section. + +| Name | Example | Description | Required | +|------------------------------|-----------------------|---------------------------------------------------------------------------|----------| +| `BOT_VERBOSE_LOGGING` | false | The bot will give more information to the console, useful for debugging | ❌ | +| `BOT_COMMAND_PREFIX` | // | Used only for text commands | ✔️ | +| `BOT_LANGUAGE` | en | Supported values: en ru | ❌ | +| `MONGO_URI` | mongodb://mongo:27017 | The public key for sending notifications | ✔️ | +| `MONGO_DATABASE_NAME` | aicbot | Database name in MongoDB | ✔️ | +| `BOT_DISCORD_TOKEN` | ODEzNzUwMTY1N... | Token from Discord Developer Portal | ✔️ | +| `BOT_DISCORD_CLIENT_ID` | 813750165783... | Application ID from Discord Developer Portal | ✔️ | +| `BOT_DISCORD_OVERPOWERED_ID` | 29016845994426.... | Discord bot owner user ID, required for having more bot control for owner | ✔️ | +| `BOT_SPOTIFY_CLIENT_SECRET` | | Used when the Spotify module cannot get the credentials automatically | ❌ | +| `BOT_SPOTIFY_CLIENT_ID` | | Used when the Spotify module get the credentials automatically | ❌ | +| `BOT_YANDEXMUSIC_TOKEN` | | Provide to enable Yandex Music module | ❌ | +| `BOT_YANDEXMUSIC_UID` | | Provide to enable Yandex Music module | ❌ | +| `BOT_SOUNDCLOUD_CLIENT_ID` | | Provide to fetch more data with SoundCloud Go+ account | ❌ | +| `BOT_SOUNDCLOUD_TOKEN` | | Provide to fetch more data with SoundCloud Go+ account | ❌ | +| `BOT_GENIUS_TOKEN` | | Provide to fetch songs lyrics from Genius | | # 🐋 Run in Docker (recommended) @@ -56,7 +58,7 @@ AICoTest/ - Install C++ compiler. Follow this [guide](https://github.com/nodejs/node-gyp#on-windows) - Install FFMpeg. Follow this [guide](https://www.wikihow.com/Install-FFmpeg-on-Windows) - Clone repository to your computer -- Follow the [Configure .env](#-configure-env) section and copy .env.production in folder with repository. +- Follow the [Configure .env](#-configure-env) section and copy .env.development in folder with repository. - (Optional) Follow the [YouTube Cookie](https://github.com/AlexInCube/AlCoTest/wiki/API-Configure#-youtube-cookie-optional) and copy yt-cookies.json in the folder with repository. - Install Node.js packages in the folder with repository @@ -75,3 +77,7 @@ npm run build ``` npm run production ``` +or if you are a developer +``` +npm run development +```