mirror of
https://github.com/kirameki-cafe/Yumi.git
synced 2026-09-13 10:49:20 +00:00
🐛 fix: Temp patch broken music player
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
/build
|
/build
|
||||||
/configs
|
/configs
|
||||||
|
/temp
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist
|
/dist
|
||||||
|
|||||||
+186
-185
@@ -1,60 +1,60 @@
|
|||||||
{
|
{
|
||||||
"common": {
|
"common": {
|
||||||
"available_args": "Available arguments",
|
"available_args": "Available arguments",
|
||||||
"member_no_permissions": "Access denied (O_O;)",
|
"member_no_permissions": "Access denied (O_O;)",
|
||||||
"member_no_permissions_description": "You need ``{{PERMISSIONS}}`` permissions on this guild",
|
"member_no_permissions_description": "You need ``{{PERMISSIONS}}`` permissions on this guild",
|
||||||
"bot_no_permissions": "I don't have permission (O_O;)",
|
"bot_no_permissions": "I don't have permission (O_O;)",
|
||||||
"bot_no_permissions_description": "I need ``{{PERMISSIONS}}`` permissions",
|
"bot_no_permissions_description": "I need ``{{PERMISSIONS}}`` permissions",
|
||||||
"processing": "Please wait a moment~",
|
"processing": "Please wait a moment~",
|
||||||
"processing_description": "Getting it done right now! (งᓀ‸ᓂ)ง",
|
"processing_description": "Getting it done right now! (งᓀ‸ᓂ)ง",
|
||||||
"early_access_features": "Early access features",
|
"early_access_features": "Early access features",
|
||||||
"early_access_features_description": "Hey there! So, this new feature we've got is still in its early access phase. We're working hard to test and make it even better. But, just a heads up, early access means there might be some surprises like unexpected stuff, security things, or features that are still a work in progress.\n\nIf you're up for it, guild administrators can jump in and try it out by opting in through the settings"
|
"early_access_features_description": "Hey there! So, this new feature we've got is still in its early access phase. We're working hard to test and make it even better. But, just a heads up, early access means there might be some surprises like unexpected stuff, security things, or features that are still a work in progress.\n\nIf you're up for it, guild administrators can jump in and try it out by opting in through the settings"
|
||||||
},
|
},
|
||||||
"early_access_warning": {
|
"early_access_warning": {
|
||||||
"title": "Early access warning",
|
"title": "Early access warning",
|
||||||
"description": "Experience the newest features first with early access! Things might not be perfect yet, but your feedback helps us make it amazing. Contact us with the support command if you find anything\n\n You can disable early access features anytime in the settings"
|
"description": "Experience the newest features first with early access! Things might not be perfect yet, but your feedback helps us make it amazing. Contact us with the support command if you find anything\n\n You can disable early access features anytime in the settings"
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"title": "Stats",
|
"title": "Stats",
|
||||||
"info": "Statistical data of me",
|
"info": "Statistical data of me",
|
||||||
"users": "Users",
|
"users": "Users",
|
||||||
"x_servers": "{{COUNT}} servers",
|
"x_servers": "{{COUNT}} servers",
|
||||||
"x_users": "{{COUNT}} users",
|
"x_users": "{{COUNT}} users",
|
||||||
"uptime_since": "Uptime since",
|
"uptime_since": "Uptime since",
|
||||||
"server_uptime_x": "Server | {{{TIME}}}",
|
"server_uptime_x": "Server | {{{TIME}}}",
|
||||||
"process_uptime_x": "Process | {{{TIME}}}"
|
"process_uptime_x": "Process | {{{TIME}}}"
|
||||||
},
|
},
|
||||||
"userinfo": {
|
"userinfo": {
|
||||||
"title": "User Information",
|
"title": "User Information",
|
||||||
"info": "Show information of a specific user, such as status, activities, and more",
|
"info": "Show information of a specific user, such as status, activities, and more",
|
||||||
"valid_args": "(``@Mention or User ID``)",
|
"valid_args": "(``@Mention or User ID``)",
|
||||||
"user_not_found": "I cannot find that user, is your information correct?",
|
"user_not_found": "I cannot find that user, is your information correct?",
|
||||||
"online": "Online",
|
"online": "Online",
|
||||||
"idle": "Idle",
|
"idle": "Idle",
|
||||||
"dnd": "Do not disturb",
|
"dnd": "Do not disturb",
|
||||||
"offline": "Recently Offline",
|
"offline": "Recently Offline",
|
||||||
"unknown": "Offline / Unknown",
|
"unknown": "Offline / Unknown",
|
||||||
"user_guild_info": "Information on this guild",
|
"user_guild_info": "Information on this guild",
|
||||||
"join_date_unknown": "Unknown join date",
|
"join_date_unknown": "Unknown join date",
|
||||||
"join_date": "Joined {{{TIME}}}",
|
"join_date": "Joined {{{TIME}}}",
|
||||||
"guild_owner": "Owner of this guild",
|
"guild_owner": "Owner of this guild",
|
||||||
"custom_status": "Custom Status",
|
"custom_status": "Custom Status",
|
||||||
"playing_x": "Playing {{NAME}}",
|
"playing_x": "Playing {{NAME}}",
|
||||||
"streaming_x": "Streaming {{NAME}}",
|
"streaming_x": "Streaming {{NAME}}",
|
||||||
"listening_x": "Listening to {{NAME}}",
|
"listening_x": "Listening to {{NAME}}",
|
||||||
"watching_x": "Watching {{NAME}}",
|
"watching_x": "Watching {{NAME}}",
|
||||||
"competing_x": "Competing {{NAME}}",
|
"competing_x": "Competing {{NAME}}",
|
||||||
"since": "Since {{{TIME}}}",
|
"since": "Since {{{TIME}}}",
|
||||||
"end": "Ending {{{TIME}}}"
|
"end": "Ending {{{TIME}}}"
|
||||||
},
|
},
|
||||||
"say": {
|
"say": {
|
||||||
"title": "Say",
|
"title": "Say",
|
||||||
"info": "Make me say something that you want~",
|
"info": "Make me say something that you want~",
|
||||||
"valid_args": "``(Text message)``",
|
"valid_args": "``(Text message)``",
|
||||||
"success": "Done~, I posted a message with your content!",
|
"success": "Done~, I posted a message with your content!",
|
||||||
"error": "An error occurred while trying to post the message with your content!"
|
"error": "An error occurred while trying to post the message with your content!"
|
||||||
},
|
},
|
||||||
"purge": {
|
"purge": {
|
||||||
"title": "Purge",
|
"title": "Purge",
|
||||||
"info": "Delete a certain amount of messages",
|
"info": "Delete a certain amount of messages",
|
||||||
"valid_args": "``(Number of messages)``",
|
"valid_args": "``(Number of messages)``",
|
||||||
@@ -70,133 +70,134 @@
|
|||||||
"purging_step_normal_delete_done": "Deleted {{COUNT}} messages",
|
"purging_step_normal_delete_done": "Deleted {{COUNT}} messages",
|
||||||
"error": "An error occurred while purging messages"
|
"error": "An error occurred while purging messages"
|
||||||
},
|
},
|
||||||
"invite": {
|
"invite": {
|
||||||
"title": "Invite",
|
"title": "Invite",
|
||||||
"info": "[Invite {{BOT_NAME}} to your server!]({{{LINK}}})",
|
"info": "[Invite {{BOT_NAME}} to your server!]({{{LINK}}})",
|
||||||
"private": "{{BOT_NAME}}'s invite is currently private. Only the developers can add me to another server"
|
"private": "{{BOT_NAME}}'s invite is currently private. Only the developers can add me to another server"
|
||||||
},
|
},
|
||||||
"help": {
|
"help": {
|
||||||
"title": "Help - {{BOT_NAME}}",
|
"title": "Help - {{BOT_NAME}}",
|
||||||
"prefix_title": "Prefix",
|
"prefix_title": "Prefix",
|
||||||
"prefix_description": "You can call me using ``{{{PREFIX}}}``, {{{PREFIX_MENTION}}} or ``/slash command``",
|
"prefix_description": "You can call me using ``{{{PREFIX}}}``, {{{PREFIX_MENTION}}} or ``/slash command``",
|
||||||
"available_commands": "Available commands",
|
"available_commands": "Available commands",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"music": "Music",
|
"music": "Music",
|
||||||
"games": "Games",
|
"games": "Games",
|
||||||
"admin": "Admin",
|
"admin": "Admin",
|
||||||
"developer": "Developer",
|
"developer": "Developer",
|
||||||
"footer": "Made with 💖 and [open source]"
|
"footer": "Made with 💖 and [open source]"
|
||||||
},
|
},
|
||||||
"musicplayer": {
|
"musicplayer": {
|
||||||
"now_playing": "Now playing",
|
"now_playing": "Now playing",
|
||||||
"now_playing_repeating": "Now playing (Repeating)",
|
"now_playing_repeating": "Now playing (Repeating)",
|
||||||
"no_music_playing": "There are no music playing (ᴗ˳ᴗ);",
|
"no_music_playing": "There are no music playing (ᴗ˳ᴗ);",
|
||||||
"not_in_voice": "You need to be in the voice channel first~",
|
"not_in_voice": "You need to be in the voice channel first~",
|
||||||
"different_voice_channel": "You are not in the same voice channel as me!",
|
"different_voice_channel": "You are not in the same voice channel as me!",
|
||||||
"track_error": "Error while playing music, skipping this track"
|
"track_error": "Error while playing music, skipping this track"
|
||||||
},
|
},
|
||||||
"musicplayer_join": {
|
"musicplayer_join": {
|
||||||
"success": "Successfully joined the voice channel, I'm here now~",
|
"success": "Successfully joined the voice channel, I'm here now~",
|
||||||
"error": "Unable to join the voice channel",
|
"error": "Unable to join the voice channel",
|
||||||
"disconnected": "Disconnected",
|
"disconnected": "Disconnected",
|
||||||
"disconnected_reason_disconnected": "I got disconnected from the voice channel",
|
"disconnected_reason_disconnected": "I got disconnected from the voice channel",
|
||||||
"in_use": "I can't join mutiple voice channel on the same guild.\n There are also somebody listening to the music in the voice channel I'm currently in.",
|
"in_use": "I can't join mutiple voice channel on the same guild.\n There are also somebody listening to the music in the voice channel I'm currently in.",
|
||||||
"in_use_wait": "Wait until I finish playing there or I'm alone there. Better yet, join them!",
|
"in_use_wait": "Wait until I finish playing there or I'm alone there. Better yet, join them!",
|
||||||
"can_force_move": "\n\n**Or... just (ab)use your admin permissions and move me to where you want!**",
|
"can_force_move": "\n\n**Or... just (ab)use your admin permissions and move me to where you want!**",
|
||||||
"already_joined_title": "I'm already here~ (* ^ ω ^)",
|
"already_joined_title": "I'm already here~ (* ^ ω ^)",
|
||||||
"already_joined_description": "Already in the voice channel you are currently connected to"
|
"already_joined_description": "Already in the voice channel you are currently connected to"
|
||||||
},
|
},
|
||||||
"musicplayer_leave": {
|
"musicplayer_leave": {
|
||||||
"success": "Successfully left the voice channel, see ya!"
|
"success": "Successfully left the voice channel, see ya!"
|
||||||
},
|
},
|
||||||
"musicplayer_loop": {
|
"musicplayer_loop": {
|
||||||
"info": "Current loop mode is {{MODE}}",
|
"info": "Current loop mode is {{MODE}}",
|
||||||
"valid_args": "Available loop mode: ``None`` ``Current``",
|
"valid_args": "Available loop mode: ``None`` ``Current``",
|
||||||
"invalid": "Invalid loop mode",
|
"invalid": "Invalid loop mode",
|
||||||
"loop_set": "Loop mode is now set to {{MODE}}"
|
"loop_set": "Loop mode is now set to {{MODE}}"
|
||||||
},
|
},
|
||||||
"musicplayer_pause": {
|
"musicplayer_pause": {
|
||||||
"paused": "Paused",
|
"paused": "Paused",
|
||||||
"already_paused": "Already paused (´。• ω •。`)"
|
"already_paused": "Already paused (´。• ω •。`)"
|
||||||
},
|
},
|
||||||
"musicplayer_resume": {
|
"musicplayer_resume": {
|
||||||
"resumed": "Resumed",
|
"resumed": "Resumed",
|
||||||
"not_paused": "The music is already playing (´。• ᵕ •。`)"
|
"not_paused": "The music is already playing (´。• ᵕ •。`)"
|
||||||
},
|
},
|
||||||
"musicplayer_play": {
|
"musicplayer_play": {
|
||||||
"title": "Play",
|
"title": "Play",
|
||||||
"info": "Play a song",
|
"info": "Play a song",
|
||||||
"valid_args": "``(Search query or Link)``",
|
"valid_args": "``(Search query or Link)``",
|
||||||
"queue_added_song": "Song added to queue! ヽ(>∀<☆)ノ",
|
"queue_added_song": "Song added to queue! ヽ(>∀<☆)ノ",
|
||||||
"queue_added_songs": "Songs added to queue! ヽ(>∀<☆)ノ",
|
"queue_added_songs": "Songs added to queue! ヽ(>∀<☆)ノ",
|
||||||
"queue_added_songs_description": "Added {{COUNT}} songs to the queue",
|
"queue_added_songs_description": "Added {{COUNT}} songs to the queue",
|
||||||
"error": "Error while looking up the song"
|
"error": "Error while looking up the song"
|
||||||
},
|
},
|
||||||
"musicplayer_playmy": {
|
"musicplayer_playmy": {
|
||||||
"not_detected": "You don't seem to be listening to any music currently according to your activity"
|
"not_detected": "You don't seem to be listening to any music currently according to your activity",
|
||||||
},
|
"too_long": "musicplayer_playmy.too_long"
|
||||||
"musicplayer_queue": {
|
},
|
||||||
"title": "Queue",
|
"musicplayer_queue": {
|
||||||
"valid_args": "``Clear`` ``Shuffle``",
|
"title": "Queue",
|
||||||
"empty": "The queue is empty! (っ˘ω˘ς )",
|
"valid_args": "``Clear`` ``Shuffle``",
|
||||||
"now_playing": "Now playing",
|
"empty": "The queue is empty! (っ˘ω˘ς )",
|
||||||
"upcoming_song": "Upcoming song",
|
"now_playing": "Now playing",
|
||||||
"looping_current": "(Looping current)",
|
"upcoming_song": "Upcoming song",
|
||||||
"song_x_in_queue": "There are {{COUNT}} song in the queue!",
|
"looping_current": "(Looping current)",
|
||||||
"cleared": "Queue cleared~ (。•ˇ‸ˇ•。)",
|
"song_x_in_queue": "There are {{COUNT}} song in the queue!",
|
||||||
"shuffled": "Queue shuffled"
|
"cleared": "Queue cleared~ (。•ˇ‸ˇ•。)",
|
||||||
},
|
"shuffled": "Queue shuffled"
|
||||||
"musicplayer_search": {
|
},
|
||||||
"title": "Search",
|
"musicplayer_search": {
|
||||||
"info": "Search for a song",
|
"title": "Search",
|
||||||
"valid_args": "``(Search query)``",
|
"info": "Search for a song",
|
||||||
"title_result": "Search result",
|
"valid_args": "``(Search query)``",
|
||||||
"x_results_found": "{{COUNT}} results found"
|
"title_result": "Search result",
|
||||||
},
|
"x_results_found": "{{COUNT}} results found"
|
||||||
"musicplayer_skip": {
|
},
|
||||||
"skipped": "Skipped",
|
"musicplayer_skip": {
|
||||||
"skipped_last_song": "Skipped, that was the last song"
|
"skipped": "Skipped",
|
||||||
},
|
"skipped_last_song": "Skipped, that was the last song"
|
||||||
"settings": {
|
},
|
||||||
"title": "Settings",
|
"settings": {
|
||||||
"description": "Change how {{BOT_NAME}} behave on **{{{GUILD_NAME}}}**",
|
"title": "Settings",
|
||||||
"valid_args": "``Prefix`` ``Language`` ``EnableServiceAnnouncement`` ``ServiceAnnouncementChannel`` ``EarlyAccess`` ``PurgeLimit``"
|
"description": "Change how {{BOT_NAME}} behave on **{{{GUILD_NAME}}}**",
|
||||||
},
|
"valid_args": "``Prefix`` ``Language`` ``EnableServiceAnnouncement`` ``ServiceAnnouncementChannel`` ``EarlyAccess`` ``PurgeLimit``"
|
||||||
"settings_prefix": {
|
},
|
||||||
"info": "Current prefix is {{{PREFIX}}}",
|
"settings_prefix": {
|
||||||
"info_description": "Provide a new prefix to change",
|
"info": "Current prefix is {{{PREFIX}}}",
|
||||||
"prefix_too_long": "Prefix too long",
|
"info_description": "Provide a new prefix to change",
|
||||||
"prefix_too_long_description": "Can you even remember that? 😆",
|
"prefix_too_long": "Prefix too long",
|
||||||
"prefix_is_mention": "Prefix cannot be @Mention of me",
|
"prefix_too_long_description": "Can you even remember that? 😆",
|
||||||
"prefix_is_mention_description": "You can already call me by mentioning me. I got that covered, don't worry",
|
"prefix_is_mention": "Prefix cannot be @Mention of me",
|
||||||
"prefix_updated": "Prefix updated",
|
"prefix_is_mention_description": "You can already call me by mentioning me. I got that covered, don't worry",
|
||||||
"prefix_updated_description": "From now onwards, I shall be called by using ``{{{PREFIX}}}``"
|
"prefix_updated": "Prefix updated",
|
||||||
},
|
"prefix_updated_description": "From now onwards, I shall be called by using ``{{{PREFIX}}}``"
|
||||||
"settings_purge_limit": {
|
},
|
||||||
"info": "Current purge limit is {{LIMIT}}",
|
"settings_purge_limit": {
|
||||||
"info_description": "Provide a new purge limit to change",
|
"info": "Current purge limit is {{LIMIT}}",
|
||||||
"purge_limit_too_big": "Purge limit too big, maximum is {{LIMIT}}",
|
"info_description": "Provide a new purge limit to change",
|
||||||
"purge_limit_zero": "Purge limit can't be zero",
|
"purge_limit_too_big": "Purge limit too big, maximum is {{LIMIT}}",
|
||||||
"purge_limit_nan": "Purge limit is not a number",
|
"purge_limit_zero": "Purge limit can't be zero",
|
||||||
"purge_limit_updated": "Purge limit updated to {{LIMIT}}"
|
"purge_limit_nan": "Purge limit is not a number",
|
||||||
},
|
"purge_limit_updated": "Purge limit updated to {{LIMIT}}"
|
||||||
"settings_language": {
|
},
|
||||||
"info": "Current language is {{LANGUAGE}}",
|
"settings_language": {
|
||||||
"info_description": "Provide a new language to change\n**Available languages**: ``{{LANGUAGES}}``",
|
"info": "Current language is {{LANGUAGE}}",
|
||||||
"language_invalid": "Invalid language",
|
"info_description": "Provide a new language to change\n**Available languages**: ``{{LANGUAGES}}``",
|
||||||
"language_invalid_description": "The language code is invalid or not supported yet",
|
"language_invalid": "Invalid language",
|
||||||
"language_updated": "Language updated",
|
"language_invalid_description": "The language code is invalid or not supported yet",
|
||||||
"language_updated_description": "I will be using {{LANGUAGE}} from now on"
|
"language_updated": "Language updated",
|
||||||
},
|
"language_updated_description": "I will be using {{LANGUAGE}} from now on"
|
||||||
"ping": {
|
},
|
||||||
"title": "Network performance",
|
"ping": {
|
||||||
"title_processing": "Measuring network performance ( ◕▿◕ )",
|
"title": "Network performance",
|
||||||
"running_on": "Running on {{HOST}}",
|
"title_processing": "Measuring network performance ( ◕▿◕ )",
|
||||||
"development_environment": "Development Environment"
|
"running_on": "Running on {{HOST}}",
|
||||||
},
|
"development_environment": "Development Environment"
|
||||||
"support": {
|
},
|
||||||
"title": "Support",
|
"support": {
|
||||||
"info": "[Join {{BOT_NAME}}'s support server]({{{LINK}}})",
|
"title": "Support",
|
||||||
"not_available": "{{BOT_NAME}}'s support server is currently unavailable"
|
"info": "[Join {{BOT_NAME}}'s support server]({{{LINK}}})",
|
||||||
}
|
"not_available": "{{BOT_NAME}}'s support server is currently unavailable"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
"validator": "^13.12.0",
|
"validator": "^13.12.0",
|
||||||
"vrchat": "^1.17.6",
|
"vrchat": "^1.17.6",
|
||||||
"winston": "^3.13.0",
|
"winston": "^3.13.0",
|
||||||
|
"yt-dlp-wrap": "^2.3.12",
|
||||||
"zlib-sync": "^0.1.9"
|
"zlib-sync": "^0.1.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
||||||
import { makeInfoEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
import { makeInfoEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
||||||
|
|
||||||
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Users from '../../services/Users';
|
import Users from '../../services/Users';
|
||||||
import Cache from '../../providers/Cache';
|
import Cache from '../../providers/Cache';
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
makeSuccessEmbed
|
makeSuccessEmbed
|
||||||
} from '../../utils/DiscordMessage';
|
} from '../../utils/DiscordMessage';
|
||||||
|
|
||||||
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Users from '../../services/Users';
|
import Users from '../../services/Users';
|
||||||
import Cache from '../../providers/Cache';
|
import Cache from '../../providers/Cache';
|
||||||
import VRChatAPI from '../../providers/VRChatAPI';
|
import VRChatAPI from '../../providers/VRChatAPI';
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import DiscordMusicPlayer, {
|
|||||||
DiscordMusicPlayerInstance,
|
DiscordMusicPlayerInstance,
|
||||||
DiscordMusicPlayerLoopMode,
|
DiscordMusicPlayerLoopMode,
|
||||||
TrackUtils
|
TrackUtils
|
||||||
} from '../../providers/DiscordMusicPlayer';
|
} from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
|
|
||||||
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { I18n } from 'i18n';
|
import { I18n } from 'i18n';
|
||||||
import { Message, CommandInteraction } from 'discord.js';
|
import { Message, CommandInteraction } from 'discord.js';
|
||||||
|
|
||||||
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
import { makeSuccessEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
import { makeSuccessEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { I18n } from 'i18n';
|
import { I18n } from 'i18n';
|
||||||
import { Message, CommandInteraction } from 'discord.js';
|
import { Message, CommandInteraction } from 'discord.js';
|
||||||
|
|
||||||
import DiscordMusicPlayer, { DiscordMusicPlayerLoopMode } from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer, { DiscordMusicPlayerLoopMode } from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { I18n } from 'i18n';
|
|||||||
import { SpotifyTrack, YouTubeVideo } from 'play-dl';
|
import { SpotifyTrack, YouTubeVideo } from 'play-dl';
|
||||||
|
|
||||||
import DiscordProvider from '../../providers/Discord';
|
import DiscordProvider from '../../providers/Discord';
|
||||||
import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
import { makeErrorEmbed, makeInfoEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
import { makeErrorEmbed, makeInfoEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Message, CommandInteraction, Interaction } from 'discord.js';
|
import { Message, CommandInteraction, Interaction } from 'discord.js';
|
||||||
import { I18n } from 'i18n';
|
import { I18n } from 'i18n';
|
||||||
|
|
||||||
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { I18n } from 'i18n';
|
|||||||
import { joinVoiceChannelProcedure } from './Join';
|
import { joinVoiceChannelProcedure } from './Join';
|
||||||
|
|
||||||
import DiscordProvider from '../../providers/Discord';
|
import DiscordProvider from '../../providers/Discord';
|
||||||
import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
||||||
@@ -92,6 +92,12 @@ const EMBEDS = {
|
|||||||
description: errorMessage,
|
description: errorMessage,
|
||||||
user: data.getUser()
|
user: data.getUser()
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
TOO_LONG: (data: HybridInteractionMessage, locale: I18n) => {
|
||||||
|
return makeErrorEmbed({
|
||||||
|
title: locale.__('musicplayer_play.too_long'),
|
||||||
|
user: data.getUser()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export default class Play extends DiscordModule {
|
export default class Play extends DiscordModule {
|
||||||
@@ -455,6 +461,12 @@ export default class Play extends DiscordModule {
|
|||||||
}
|
}
|
||||||
instance.addTrackToQueue(result[0]);
|
instance.addTrackToQueue(result[0]);
|
||||||
|
|
||||||
|
// Max 1 hour
|
||||||
|
if (result[0].durationInSec > 3600)
|
||||||
|
return await sendHybridInteractionMessageResponse(data, {
|
||||||
|
embeds: [EMBEDS.TOO_LONG(data, locale)]
|
||||||
|
});
|
||||||
|
|
||||||
if (result.length > 1) {
|
if (result.length > 1) {
|
||||||
// TODO: Find a better logic than this
|
// TODO: Find a better logic than this
|
||||||
// If the first result title is exact match with the query or first title contains half the space of the query, it's probably a sentence
|
// If the first result title is exact match with the query or first title contains half the space of the query, it's probably a sentence
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { I18n } from 'i18n';
|
import { I18n } from 'i18n';
|
||||||
import { Message, CommandInteraction, ActivityType, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
import { Message, CommandInteraction, ActivityType, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js';
|
||||||
|
|
||||||
import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
import { makeSuccessEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
import { makeSuccessEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
||||||
@@ -41,6 +41,12 @@ const EMBEDS = {
|
|||||||
embed.setImage(TrackUtils.getHighestResolutionThumbnail(thumbnails).url);
|
embed.setImage(TrackUtils.getHighestResolutionThumbnail(thumbnails).url);
|
||||||
|
|
||||||
return embed;
|
return embed;
|
||||||
|
},
|
||||||
|
TOO_LONG: (data: HybridInteractionMessage, locale: I18n) => {
|
||||||
|
return makeErrorEmbed({
|
||||||
|
title: locale.__('musicplayer_playmy.too_long'),
|
||||||
|
user: data.getUser()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,6 +115,12 @@ export default class PlayMy extends DiscordModule {
|
|||||||
if (!result) continue;
|
if (!result) continue;
|
||||||
instance.addTrackToQueue(result[0]);
|
instance.addTrackToQueue(result[0]);
|
||||||
|
|
||||||
|
// Max 1 hour
|
||||||
|
if (result[0].durationInSec > 3600)
|
||||||
|
return await sendHybridInteractionMessageResponse(data, {
|
||||||
|
embeds: [EMBEDS.TOO_LONG(data, locale)]
|
||||||
|
});
|
||||||
|
|
||||||
let row;
|
let row;
|
||||||
if (result.length > 1) {
|
if (result.length > 1) {
|
||||||
// TODO: Find a better logic than this
|
// TODO: Find a better logic than this
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import DiscordMusicPlayer, {
|
|||||||
DiscordMusicPlayerInstance,
|
DiscordMusicPlayerInstance,
|
||||||
DiscordMusicPlayerLoopMode,
|
DiscordMusicPlayerLoopMode,
|
||||||
TrackUtils
|
TrackUtils
|
||||||
} from '../../providers/DiscordMusicPlayer';
|
} from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Message, CommandInteraction, Interaction } from 'discord.js';
|
import { Message, CommandInteraction, Interaction } from 'discord.js';
|
||||||
import { I18n } from 'i18n';
|
import { I18n } from 'i18n';
|
||||||
|
|
||||||
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
|
|
||||||
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { I18n } from 'i18n';
|
|||||||
|
|
||||||
import { joinVoiceChannelProcedure } from './Join';
|
import { joinVoiceChannelProcedure } from './Join';
|
||||||
|
|
||||||
import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer, { TrackUtils, ValidTracks } from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModu
|
|||||||
import { Message, CommandInteraction } from 'discord.js';
|
import { Message, CommandInteraction } from 'discord.js';
|
||||||
import { makeSuccessEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
import { makeSuccessEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage';
|
||||||
|
|
||||||
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayer';
|
import DiscordMusicPlayer from '../../providers/DiscordMusicPlayerTempFix';
|
||||||
import { I18n } from 'i18n';
|
import { I18n } from 'i18n';
|
||||||
import Locale from '../../services/Locale';
|
import Locale from '../../services/Locale';
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ class Configuration {
|
|||||||
if (!fs.existsSync(dir)) {
|
if (!fs.existsSync(dir)) {
|
||||||
fs.mkdirSync(dir);
|
fs.mkdirSync(dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Temp folder
|
||||||
|
const tempdir = path.join(process.cwd(), 'temp/');
|
||||||
|
if (!fs.existsSync(tempdir)) {
|
||||||
|
fs.mkdirSync(tempdir);
|
||||||
|
}
|
||||||
|
|
||||||
this.copyExampleIfNotExists('Ping.json');
|
this.copyExampleIfNotExists('Ping.json');
|
||||||
this.copyExampleIfNotExists('ServiceAnnouncement.json');
|
this.copyExampleIfNotExists('ServiceAnnouncement.json');
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,783 @@
|
|||||||
|
import {
|
||||||
|
VoiceChannel,
|
||||||
|
Snowflake,
|
||||||
|
StageChannel,
|
||||||
|
Guild,
|
||||||
|
PermissionsBitField,
|
||||||
|
BaseGuildVoiceChannel,
|
||||||
|
BaseGuildTextChannel
|
||||||
|
} from 'discord.js';
|
||||||
|
import {
|
||||||
|
AudioPlayer,
|
||||||
|
VoiceConnection,
|
||||||
|
createAudioPlayer,
|
||||||
|
joinVoiceChannel,
|
||||||
|
createAudioResource,
|
||||||
|
VoiceConnectionStatus,
|
||||||
|
AudioPlayerStatus,
|
||||||
|
NoSubscriberBehavior,
|
||||||
|
VoiceConnectionState,
|
||||||
|
AudioPlayerError,
|
||||||
|
DiscordGatewayAdapterCreator
|
||||||
|
} from '@discordjs/voice';
|
||||||
|
import playdl, { Spotify, SpotifyPlaylist, SpotifyTrack, YouTubeVideo } from 'play-dl';
|
||||||
|
import { EventEmitter } from 'stream';
|
||||||
|
import YTDlpWrap from 'yt-dlp-wrap';
|
||||||
|
const ytDlpWrap = new YTDlpWrap();
|
||||||
|
import fs from 'fs';
|
||||||
|
import DiscordProvider from './Discord';
|
||||||
|
import Environment from './Environment';
|
||||||
|
import Logger from '../libs/Logger';
|
||||||
|
|
||||||
|
const LOGGING_TAG = '[DiscordMusicPlayer]';
|
||||||
|
|
||||||
|
export type ValidTracks = YouTubeVideo | SpotifyTrack;
|
||||||
|
declare class YouTubeThumbnail {
|
||||||
|
url: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
constructor(data: any);
|
||||||
|
toJSON(): {
|
||||||
|
url: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpotifyThumbnail {
|
||||||
|
height: number;
|
||||||
|
width: number;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenOptions {
|
||||||
|
spotify?: {
|
||||||
|
client_id: string;
|
||||||
|
client_secret: string;
|
||||||
|
refresh_token: string;
|
||||||
|
market: string;
|
||||||
|
};
|
||||||
|
soundcloud?: {
|
||||||
|
client_id: string;
|
||||||
|
};
|
||||||
|
youtube?: {
|
||||||
|
cookie: string;
|
||||||
|
};
|
||||||
|
useragent?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokenObject: TokenOptions = {};
|
||||||
|
|
||||||
|
if (Environment.get().YOUTUBE_COOKIE_BASE64) {
|
||||||
|
Logger.debug(LOGGING_TAG, 'Setting YouTube cookie');
|
||||||
|
tokenObject.youtube = {
|
||||||
|
cookie: Buffer.from(Environment.get().YOUTUBE_COOKIE_BASE64, 'base64').toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Environment.get().SPOTIFY_CLIENT_ID &&
|
||||||
|
Environment.get().SPOTIFY_CLIENT_SECRET &&
|
||||||
|
Environment.get().SPOTIFY_REFRESH_TOKEN &&
|
||||||
|
Environment.get().SPOTIFY_CLIENT_MARKET
|
||||||
|
) {
|
||||||
|
Logger.debug(LOGGING_TAG, 'Setting Spotify token');
|
||||||
|
tokenObject.spotify = {
|
||||||
|
client_id: Environment.get().SPOTIFY_CLIENT_ID,
|
||||||
|
client_secret: Environment.get().SPOTIFY_CLIENT_SECRET,
|
||||||
|
refresh_token: Environment.get().SPOTIFY_REFRESH_TOKEN,
|
||||||
|
market: Environment.get().SPOTIFY_CLIENT_MARKET
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
playdl.setToken(tokenObject);
|
||||||
|
|
||||||
|
export class TrackUtils {
|
||||||
|
public static getTitle(track: ValidTracks) {
|
||||||
|
if (track instanceof YouTubeVideo) {
|
||||||
|
return track.title;
|
||||||
|
} else if (track instanceof SpotifyTrack) {
|
||||||
|
let artistNames = '';
|
||||||
|
for (let artist of track.artists) artistNames += artist.name + ' ';
|
||||||
|
|
||||||
|
return `${artistNames} - ${track.name}`;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid track type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async getThumbnails(track: ValidTracks) {
|
||||||
|
if (track instanceof YouTubeVideo) {
|
||||||
|
return track.thumbnails;
|
||||||
|
} else if (track instanceof SpotifyTrack) {
|
||||||
|
if (track.thumbnail) return [track.thumbnail];
|
||||||
|
else {
|
||||||
|
// Try to find the thumbnail again
|
||||||
|
const result = await DiscordMusicPlayer_Instance.searchSpotifyBySpotifyLink(track);
|
||||||
|
if (!result) return null;
|
||||||
|
if (result.thumbnail) return [result.thumbnail];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid track type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static getHighestResolutionThumbnail(thumbnails: YouTubeThumbnail[] | SpotifyThumbnail[] | null) {
|
||||||
|
if (!thumbnails) return null;
|
||||||
|
|
||||||
|
return (thumbnails as any[]).reduce((prev: any, current: any) =>
|
||||||
|
prev.height * prev.width > current.height * current.width ? prev : current
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Queue {
|
||||||
|
public track: ValidTracks[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class YouTubeLink {
|
||||||
|
public videoId: string;
|
||||||
|
public list?: string;
|
||||||
|
|
||||||
|
constructor(videoId: string, list: string) {
|
||||||
|
this.videoId = videoId;
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SpotifyLink {
|
||||||
|
public id: string;
|
||||||
|
public type: 'track' | 'playlist' | 'album';
|
||||||
|
public url: string;
|
||||||
|
|
||||||
|
constructor(id: string, type: 'track' | 'playlist' | 'album', url: string) {
|
||||||
|
this.id = id;
|
||||||
|
this.type = type;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PlayerPlayingEvent {
|
||||||
|
public instance: DiscordMusicPlayerInstance;
|
||||||
|
|
||||||
|
constructor(instance: DiscordMusicPlayerInstance) {
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PlayerErrorEvent {
|
||||||
|
public instance: DiscordMusicPlayerInstance;
|
||||||
|
public error: Error;
|
||||||
|
|
||||||
|
constructor(instance: DiscordMusicPlayerInstance, error: Error) {
|
||||||
|
this.instance = instance;
|
||||||
|
this.error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VoiceDisconnectedEvent {
|
||||||
|
public instance: DiscordMusicPlayerInstance;
|
||||||
|
|
||||||
|
constructor(instance: DiscordMusicPlayerInstance) {
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DiscordMusicPlayerLoopMode {
|
||||||
|
None = 'none',
|
||||||
|
Current = 'current'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiscordMusicPlayerInstance {
|
||||||
|
public queue: Queue;
|
||||||
|
public player: AudioPlayer;
|
||||||
|
public textChannel?: BaseGuildTextChannel | BaseGuildVoiceChannel;
|
||||||
|
public voiceChannel: VoiceChannel | StageChannel;
|
||||||
|
public voiceConnection?: VoiceConnection;
|
||||||
|
public previousTrack?: ValidTracks;
|
||||||
|
|
||||||
|
public paused: boolean = false;
|
||||||
|
public loopMode: DiscordMusicPlayerLoopMode = DiscordMusicPlayerLoopMode.None;
|
||||||
|
|
||||||
|
public actualPlaybackURL?: string;
|
||||||
|
|
||||||
|
public readonly events: EventEmitter;
|
||||||
|
|
||||||
|
constructor({ voiceChannel }: { voiceChannel: VoiceChannel | StageChannel }) {
|
||||||
|
this.queue = new Queue();
|
||||||
|
this.player = createAudioPlayer({
|
||||||
|
behaviors: {
|
||||||
|
noSubscriber: NoSubscriberBehavior.Pause,
|
||||||
|
maxMissedFrames: 1000
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.voiceChannel = voiceChannel;
|
||||||
|
this.events = new EventEmitter();
|
||||||
|
|
||||||
|
this.player.on(AudioPlayerStatus.Idle, async (oldStage, newStage) => {
|
||||||
|
//The player stopped
|
||||||
|
if (newStage.status === AudioPlayerStatus.Idle && oldStage.status !== AudioPlayerStatus.Idle) {
|
||||||
|
// Loop mode is set to current song
|
||||||
|
if (this.loopMode === DiscordMusicPlayerLoopMode.Current) {
|
||||||
|
if (this.queue.track.length !== 0) {
|
||||||
|
this.previousTrack = this.queue.track[0];
|
||||||
|
this.playTrack(this.queue.track[0]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are more songs in the queue, remove finished song and play the next one
|
||||||
|
if (this.queue.track.length !== 0) {
|
||||||
|
let previousTrack = this.queue.track.shift();
|
||||||
|
if (previousTrack) this.previousTrack = previousTrack;
|
||||||
|
|
||||||
|
if (this.queue.track.length > 0) {
|
||||||
|
this.playTrack(this.queue.track[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.player.on(AudioPlayerStatus.Playing, (oldState: any, newState: any) => {
|
||||||
|
this.events.emit('playing', new PlayerPlayingEvent(this));
|
||||||
|
});
|
||||||
|
|
||||||
|
this.player.on('error', (error: Error) => {
|
||||||
|
this.events.emit('error', new PlayerErrorEvent(this, error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public joinVoiceChannel(
|
||||||
|
voiceChannel: VoiceChannel | StageChannel,
|
||||||
|
textChannel?: BaseGuildTextChannel | BaseGuildVoiceChannel
|
||||||
|
) {
|
||||||
|
const permissions = voiceChannel.permissionsFor(DiscordProvider.client.user!);
|
||||||
|
|
||||||
|
if (!permissions || !voiceChannel.joinable || !permissions.has(PermissionsBitField.Flags.Connect))
|
||||||
|
throw new Error('No permissions');
|
||||||
|
|
||||||
|
if (textChannel) this.textChannel = textChannel;
|
||||||
|
|
||||||
|
this.voiceConnection = joinVoiceChannel({
|
||||||
|
channelId: this.voiceChannel.id,
|
||||||
|
guildId: this.voiceChannel.guild.id,
|
||||||
|
adapterCreator: this.voiceChannel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator
|
||||||
|
});
|
||||||
|
|
||||||
|
this.voiceConnection.on(
|
||||||
|
VoiceConnectionStatus.Ready,
|
||||||
|
async (oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
|
||||||
|
let guild = DiscordProvider.client.guilds.cache.get(voiceChannel.guild.id);
|
||||||
|
if (guild) {
|
||||||
|
let currentVC = guild?.members.me?.voice.channel;
|
||||||
|
if (currentVC && currentVC.id !== this.voiceChannel.id) {
|
||||||
|
this.voiceChannel = currentVC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.voiceConnection.on(
|
||||||
|
VoiceConnectionStatus.Disconnected,
|
||||||
|
(oldState: VoiceConnectionState, newState: VoiceConnectionState) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
let guild = DiscordProvider.client.guilds.cache.get(voiceChannel.guildId);
|
||||||
|
if (guild) {
|
||||||
|
if (!guild?.members.me?.voice.channelId) {
|
||||||
|
this.events.emit('disconnect', new VoiceDisconnectedEvent(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async leaveVoiceChannel() {
|
||||||
|
if (this.player) this.player.pause();
|
||||||
|
|
||||||
|
const guild = DiscordProvider.client.guilds.cache.get(this.voiceChannel.guild.id);
|
||||||
|
if (!guild) return;
|
||||||
|
|
||||||
|
if (guild.members.me?.voice) this.voiceConnection?.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async pausePlayer() {
|
||||||
|
if (this.paused || !this.player) return;
|
||||||
|
if (!this.player.pause(true)) throw new Error('Unable to pause player');
|
||||||
|
this.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async resumePlayer() {
|
||||||
|
if (!this.paused || !this.player) return;
|
||||||
|
if (!this.player.unpause()) throw new Error('Unable to resume player');
|
||||||
|
this.paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addTrackToQueue(track: ValidTracks) {
|
||||||
|
if (this.queue.track.length === 0) {
|
||||||
|
this.queue.track.push(track);
|
||||||
|
this.playTrack(this.queue.track[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue.track.push(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async playTrack(track: ValidTracks) {
|
||||||
|
if (!this.voiceConnection) throw new Error('No voice connection');
|
||||||
|
|
||||||
|
try {
|
||||||
|
let resource;
|
||||||
|
if (track instanceof YouTubeVideo) {
|
||||||
|
await ytDlpWrap.execPromise([
|
||||||
|
track.url,
|
||||||
|
'--extractor-args',
|
||||||
|
'youtube:player_client=ios',
|
||||||
|
'-f',
|
||||||
|
'140',
|
||||||
|
'-o',
|
||||||
|
`./temp/${track.id}.mp3`
|
||||||
|
]);
|
||||||
|
const streamFile = fs.createReadStream(`./temp/${track.id}.mp3`);
|
||||||
|
|
||||||
|
const stream = await playdl.stream(track.url);
|
||||||
|
stream.stream = streamFile;
|
||||||
|
|
||||||
|
Logger.verbose(
|
||||||
|
LOGGING_TAG,
|
||||||
|
`New stream created, type: ${stream.type}, url: ${track.url}, Guild: ${this.voiceChannel.guild.id}, VoiceChannel: ${this.voiceChannel.id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
resource = createAudioResource(stream.stream, {
|
||||||
|
//inputType: stream.type
|
||||||
|
});
|
||||||
|
this.actualPlaybackURL = track.url;
|
||||||
|
} else {
|
||||||
|
const search = await DiscordMusicPlayer_Instance.searchYouTubeBySpotifyLink(track);
|
||||||
|
if (!search) throw new Error('Unable to find Spotify track on YouTube');
|
||||||
|
|
||||||
|
await ytDlpWrap.execPromise([
|
||||||
|
search.url,
|
||||||
|
'--extractor-args',
|
||||||
|
'youtube:player_client=ios',
|
||||||
|
'-f',
|
||||||
|
'140',
|
||||||
|
'-o',
|
||||||
|
`./temp/${search.id}.mp3`
|
||||||
|
]);
|
||||||
|
const streamFile = fs.createReadStream(`./temp/${search.id}.mp3`);
|
||||||
|
|
||||||
|
const stream = await playdl.stream(search.url);
|
||||||
|
stream.stream = streamFile;
|
||||||
|
|
||||||
|
Logger.verbose(
|
||||||
|
LOGGING_TAG,
|
||||||
|
`New stream created, type: ${stream.type}, url: ${track.url}, Guild: ${this.voiceChannel.guild.id}, VoiceChannel: ${this.voiceChannel.id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
resource = createAudioResource(stream.stream, {
|
||||||
|
inputType: stream.type
|
||||||
|
});
|
||||||
|
this.actualPlaybackURL = search.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.player.play(resource);
|
||||||
|
this.voiceConnection.subscribe(this.player);
|
||||||
|
} catch (error: any) {
|
||||||
|
this.events.emit('error', new PlayerErrorEvent(this, error));
|
||||||
|
this.skipTrack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async skipTrack() {
|
||||||
|
if (!this.voiceConnection) throw new Error('No voice connection');
|
||||||
|
|
||||||
|
if (this.queue.track.length > 1) {
|
||||||
|
this.previousTrack = this.queue.track[0];
|
||||||
|
this.queue.track.shift();
|
||||||
|
this.playTrack(this.queue.track[0]);
|
||||||
|
} else {
|
||||||
|
this.previousTrack = this.queue.track[0];
|
||||||
|
this.queue.track.shift();
|
||||||
|
this.player.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearQueue() {
|
||||||
|
if (!this.voiceConnection) throw new Error('No voice connection');
|
||||||
|
|
||||||
|
if (!this.queue.track || this.queue.track.length === 0) return;
|
||||||
|
this.queue.track = [this.queue.track[0]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public shuffleQueue() {
|
||||||
|
if (!this.voiceConnection) throw new Error('No voice connection');
|
||||||
|
|
||||||
|
const shuffleFixedFirst = (queue: ValidTracks[]) => {
|
||||||
|
if (queue.length <= 2) return queue;
|
||||||
|
|
||||||
|
const fixedFirst = queue.shift();
|
||||||
|
if (!fixedFirst) return queue;
|
||||||
|
|
||||||
|
queue.sort(() => Math.random() - 0.5);
|
||||||
|
queue.unshift(fixedFirst);
|
||||||
|
return queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!this.queue.track || this.queue.track.length === 0) return;
|
||||||
|
this.queue.track = shuffleFixedFirst(this.queue.track);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLoopMode(mode: DiscordMusicPlayerLoopMode) {
|
||||||
|
this.loopMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLoopMode() {
|
||||||
|
return this.loopMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPreviousTrack(): ValidTracks | undefined {
|
||||||
|
return this.previousTrack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getActualPlaybackURL() {
|
||||||
|
return this.actualPlaybackURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isReady() {
|
||||||
|
if (!this.voiceConnection) return false;
|
||||||
|
return this.voiceConnection.state.status === VoiceConnectionStatus.Ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isConnected() {
|
||||||
|
if (!this.voiceConnection) return false;
|
||||||
|
|
||||||
|
if (this.voiceConnection.state.status === VoiceConnectionStatus.Destroyed) return false;
|
||||||
|
if (this.voiceConnection.state.status === VoiceConnectionStatus.Disconnected) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isPaused() {
|
||||||
|
return this.paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async destroy() {
|
||||||
|
await this.leaveVoiceChannel();
|
||||||
|
|
||||||
|
if (this.voiceConnection) {
|
||||||
|
this.voiceConnection.removeAllListeners();
|
||||||
|
if (this.voiceConnection.state.status !== 'destroyed') this.voiceConnection.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.player) {
|
||||||
|
this.player.removeAllListeners();
|
||||||
|
this.player.stop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.queue.track = [];
|
||||||
|
this.textChannel = undefined;
|
||||||
|
this.voiceConnection = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async _fake_error_on_player() {
|
||||||
|
const stream = 'https://fakestream:42069/fake/stream/fake/audio/fake.mp3';
|
||||||
|
const resource = createAudioResource(stream);
|
||||||
|
this.player.play(resource);
|
||||||
|
this.player.emit('error', new AudioPlayerError(new Error('Music player was manually crashed'), null!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiscordMusicPlayer {
|
||||||
|
public GuildQueue = new Map();
|
||||||
|
|
||||||
|
public getGuildInstance(guildId: Snowflake): DiscordMusicPlayerInstance | null {
|
||||||
|
if (!this.isGuildInstanceExists(guildId)) return null;
|
||||||
|
return this.GuildQueue.get(guildId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isGuildInstanceExists(guildId: Snowflake) {
|
||||||
|
return this.GuildQueue.has(guildId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createGuildInstance(guildId: Snowflake, voiceChannel: VoiceChannel | StageChannel) {
|
||||||
|
this.GuildQueue.set(guildId, new DiscordMusicPlayerInstance({ voiceChannel }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async destoryGuildInstance(guild: Guild | Snowflake) {
|
||||||
|
let guildId: Snowflake = guild instanceof Guild ? guild.id : guild;
|
||||||
|
|
||||||
|
if (this.isGuildInstanceExists(guildId)) {
|
||||||
|
await this.GuildQueue.get(guildId).destroy();
|
||||||
|
this.GuildQueue.delete(guildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async searchYouTubeByQuery(query: string) {
|
||||||
|
const searched: YouTubeVideo[] = await playdl.search(query, {
|
||||||
|
source: { youtube: 'video' }
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.verbose(
|
||||||
|
LOGGING_TAG,
|
||||||
|
`Search YouTube by query: ${query}, Total result: ${searched.length}, ${JSON.stringify(searched)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (searched.length == 0) return null;
|
||||||
|
return searched;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async searchYouTubeBySpotifyLink(spotifyLink: SpotifyLink) {
|
||||||
|
const track = await this.searchSpotifyBySpotifyLink(spotifyLink);
|
||||||
|
if (!track) return;
|
||||||
|
|
||||||
|
let artistNames = '';
|
||||||
|
for (let artist of track.artists) artistNames += artist.name + ' ';
|
||||||
|
|
||||||
|
const ytSearchResult = await this.searchYouTubeByQuery(`${artistNames} ${track.name}`);
|
||||||
|
if (!ytSearchResult) return null;
|
||||||
|
|
||||||
|
return ytSearchResult[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async searchSpotifyBySpotifyLink(spotifyLink: SpotifyLink) {
|
||||||
|
if (playdl.is_expired()) {
|
||||||
|
Logger.debug(LOGGING_TAG, 'Spotify token expired, refreshing...');
|
||||||
|
await playdl.refreshToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spotifyLink.type != 'track') return;
|
||||||
|
|
||||||
|
// Fetch data from spotify
|
||||||
|
const searched: Spotify = await playdl
|
||||||
|
.spotify('https://open.spotify.com/track/' + spotifyLink.id)
|
||||||
|
.catch((err) => {
|
||||||
|
Logger.error(err.message);
|
||||||
|
throw new Error('Error while searching on Spotify');
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.verbose(LOGGING_TAG, `Search Spotify by link: ${spotifyLink.id}, ${JSON.stringify(searched)}`);
|
||||||
|
|
||||||
|
if (!(searched instanceof SpotifyTrack)) return;
|
||||||
|
|
||||||
|
if (!searched) return null;
|
||||||
|
const track = searched as unknown as SpotifyTrack;
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async searchYouTubeByYouTubeLink(youtubeLink: YouTubeLink) {
|
||||||
|
// Search the url
|
||||||
|
const searched: YouTubeVideo[] = await playdl.search('https://www.youtube.com/watch?v=' + youtubeLink.videoId, {
|
||||||
|
source: { youtube: 'video' }
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.verbose(
|
||||||
|
LOGGING_TAG,
|
||||||
|
`Search YouTube by link (Pass 1): ${youtubeLink.videoId}, Total result: ${
|
||||||
|
searched.length
|
||||||
|
}, ${JSON.stringify(searched)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let video of searched) {
|
||||||
|
if (video.id === youtubeLink.videoId) return video;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serch the video Id
|
||||||
|
const searched2: YouTubeVideo[] = await playdl.search(youtubeLink.videoId, {
|
||||||
|
source: { youtube: 'video' }
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.verbose(
|
||||||
|
LOGGING_TAG,
|
||||||
|
`Seach YouTube by video ID (Pass 2): ${youtubeLink.videoId}, Total result: ${
|
||||||
|
searched2.length
|
||||||
|
}, ${JSON.stringify(searched2)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let video of searched2) {
|
||||||
|
if (video.id === youtubeLink.videoId) return video;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort, search the title
|
||||||
|
const videoInfo = await playdl.video_basic_info('https://www.youtube.com/watch?v=' + youtubeLink.videoId);
|
||||||
|
if (videoInfo?.video_details?.title) {
|
||||||
|
const searched: YouTubeVideo[] = await playdl.search(videoInfo.video_details.title, {
|
||||||
|
source: { youtube: 'video' }
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.verbose(
|
||||||
|
LOGGING_TAG,
|
||||||
|
`Search YouTube by title (Pass 3): ${videoInfo.video_details.title}, Total result: ${
|
||||||
|
searched.length
|
||||||
|
}, ${JSON.stringify(searched)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let video of searched) {
|
||||||
|
if (video.id === youtubeLink.videoId) return video;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let yt_info = await playdl.video_info('https://www.youtube.com/watch?v=' + youtubeLink.videoId);
|
||||||
|
|
||||||
|
if (yt_info) {
|
||||||
|
return new YouTubeVideo({
|
||||||
|
id: yt_info.video_details.id,
|
||||||
|
url: yt_info.video_details.url,
|
||||||
|
type: yt_info.video_details.type,
|
||||||
|
title: yt_info.video_details.title,
|
||||||
|
description: yt_info.video_details.description,
|
||||||
|
durationRaw: yt_info.video_details.durationRaw,
|
||||||
|
durationInSec: yt_info.video_details.durationInSec,
|
||||||
|
uploadedAt: yt_info.video_details.uploadedAt,
|
||||||
|
upcoming: yt_info.video_details.upcoming,
|
||||||
|
views: yt_info.video_details.views,
|
||||||
|
thumbnails: yt_info.video_details.thumbnails,
|
||||||
|
channel: yt_info.video_details.channel,
|
||||||
|
likes: yt_info.video_details.likes,
|
||||||
|
live: yt_info.video_details.live,
|
||||||
|
liveAt: yt_info.video_details.liveAt,
|
||||||
|
private: yt_info.video_details.private,
|
||||||
|
tags: yt_info.video_details.tags,
|
||||||
|
discretionAdvised: yt_info.video_details.discretionAdvised,
|
||||||
|
music: yt_info.video_details.music
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getYouTubeSongsInPlayList(youtubeLink: string) {
|
||||||
|
const result = await playdl.playlist_info(youtubeLink, {
|
||||||
|
incomplete: true
|
||||||
|
});
|
||||||
|
Logger.verbose(LOGGING_TAG, `Get YouTube songs in playlist: ${youtubeLink}, ${JSON.stringify(result)}`);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getSpotifySongsInPlayList(spotifyLink: string) {
|
||||||
|
if (playdl.is_expired()) {
|
||||||
|
Logger.debug(LOGGING_TAG, 'Spotify token expired, refreshing...');
|
||||||
|
await playdl.refreshToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await playdl.spotify(spotifyLink).catch((err) => {
|
||||||
|
Logger.error(err.message);
|
||||||
|
throw new Error('Error while searching on Spotify');
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.verbose(LOGGING_TAG, `Get Spotify songs in playlist: ${spotifyLink}, ${JSON.stringify(result)}`);
|
||||||
|
|
||||||
|
if (!(result.type == 'playlist' || result.type == 'album')) throw new Error('Not a spotify playlist');
|
||||||
|
|
||||||
|
return result as unknown as SpotifyPlaylist;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isYouTubeLink(link: string): boolean {
|
||||||
|
try {
|
||||||
|
this.parseYouTubeLink(link);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isSpotifyLink(link: string): boolean {
|
||||||
|
try {
|
||||||
|
this.parseSpotifyLink(link);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseYouTubeLink(query: string): YouTubeLink {
|
||||||
|
if (
|
||||||
|
query.startsWith('https://www.youtube.com/watch?v=') ||
|
||||||
|
query.startsWith('http://www.youtube.com/watch?v=') ||
|
||||||
|
query.startsWith('https://music.youtube.com/watch?v=') ||
|
||||||
|
query.startsWith('https://music.youtube.com/watch?v=')
|
||||||
|
) {
|
||||||
|
let data = this.parseURLQuery(query);
|
||||||
|
if (!data.v) throw new Error('YouTube link is invalid');
|
||||||
|
return {
|
||||||
|
videoId: data.v,
|
||||||
|
list: data.list ? (data.list !== 'RDMM' ? data.list : undefined) : undefined
|
||||||
|
};
|
||||||
|
} else if (query.startsWith('https://youtu.be/') || query.startsWith('http://youtu.be/')) {
|
||||||
|
//Get youtube video id after the url
|
||||||
|
let videoId = query.split('/')[3];
|
||||||
|
|
||||||
|
if (!videoId) throw new Error('YouTube link is invalid');
|
||||||
|
if (query.split('/')[4]) throw new Error('YouTube link is invalid');
|
||||||
|
|
||||||
|
return {
|
||||||
|
videoId: videoId
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
query.startsWith('https://www.youtube.com/shorts/') ||
|
||||||
|
query.startsWith('http://www.youtube.com/shorts/')
|
||||||
|
) {
|
||||||
|
let videoId = query.split('/')[4];
|
||||||
|
|
||||||
|
if (!videoId) throw new Error('YouTube link is invalid');
|
||||||
|
if (query.split('/')[5]) throw new Error('YouTube link is invalid');
|
||||||
|
|
||||||
|
return {
|
||||||
|
videoId: videoId
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
query.startsWith('https://www.youtube.com/playlist?list=') ||
|
||||||
|
query.startsWith('http://www.youtube.com/playlist?list=')
|
||||||
|
) {
|
||||||
|
let listId = query.split('?list=')[1];
|
||||||
|
return {
|
||||||
|
videoId: '',
|
||||||
|
list: listId
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error('YouTube link is invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public parseSpotifyLink(query: string): SpotifyLink {
|
||||||
|
if (query.startsWith('https://open.spotify.com/track/')) {
|
||||||
|
let id = query.split('/')[4].split(/[?#]/)[0];
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
type: 'track',
|
||||||
|
url: query.split(/[?#]/)[0]
|
||||||
|
};
|
||||||
|
} else if (query.startsWith('https://open.spotify.com/album/')) {
|
||||||
|
let id = query.split('/')[4].split(/[?#]/)[0];
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
type: 'album',
|
||||||
|
url: query.split(/[?#]/)[0]
|
||||||
|
};
|
||||||
|
} else if (query.startsWith('https://open.spotify.com/playlist/')) {
|
||||||
|
let id = query.split('/')[4].split(/[?#]/)[0];
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
type: 'playlist',
|
||||||
|
url: query.split(/[?#]/)[0]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error('Spotify link is invalid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseURLQuery(query: string) {
|
||||||
|
let queryObject: any = {};
|
||||||
|
if (query.indexOf('?') >= 0) {
|
||||||
|
let queryString = query.split('?')[1];
|
||||||
|
let queryArray = queryString.split('&');
|
||||||
|
for (let i = 0; i < queryArray.length; i++) {
|
||||||
|
let queryPair = queryArray[i].split('=');
|
||||||
|
queryObject[queryPair[0]] = queryPair[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return queryObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DiscordMusicPlayer_Instance = new DiscordMusicPlayer();
|
||||||
|
export default DiscordMusicPlayer_Instance;
|
||||||
@@ -2256,6 +2256,11 @@ yn@3.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
|
||||||
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
|
||||||
|
|
||||||
|
yt-dlp-wrap@^2.3.12:
|
||||||
|
version "2.3.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/yt-dlp-wrap/-/yt-dlp-wrap-2.3.12.tgz#35601d4e4fad12c35a6c25041a6d6404cf5066d5"
|
||||||
|
integrity sha512-P8fJ+6M1YjukyJENCTviNLiZ8mokxprR54ho3DsSKPWDcac489OjRiStGEARJr6un6ETS6goTn4CWl/b/rM3aA==
|
||||||
|
|
||||||
zlib-sync@^0.1.9:
|
zlib-sync@^0.1.9:
|
||||||
version "0.1.9"
|
version "0.1.9"
|
||||||
resolved "https://registry.yarnpkg.com/zlib-sync/-/zlib-sync-0.1.9.tgz#7075cc257e4551f5d9fc74e24cf2c11e39c7a0d1"
|
resolved "https://registry.yarnpkg.com/zlib-sync/-/zlib-sync-0.1.9.tgz#7075cc257e4551f5d9fc74e24cf2c11e39c7a0d1"
|
||||||
|
|||||||
Reference in New Issue
Block a user