diff --git a/src/me/despawningbone/discordbot/command/music/Music.java b/src/me/despawningbone/discordbot/command/music/Music.java
index e8d9db1..f8d63e4 100644
--- a/src/me/despawningbone/discordbot/command/music/Music.java
+++ b/src/me/despawningbone/discordbot/command/music/Music.java
@@ -1,678 +1,680 @@
 package me.despawningbone.discordbot.command.music;
 
 import java.awt.Color;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.net.URLDecoder;
 import java.net.URLEncoder;
 import java.text.DecimalFormat;
 import java.text.NumberFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.NoSuchElementException;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
 import org.apache.commons.lang3.StringEscapeUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.json.JSONTokener;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.jsoup.nodes.Element;
 import org.jsoup.safety.Whitelist;
 import org.jsoup.select.Elements;
 
 import com.google.common.base.Splitter;
 import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
 
 import me.despawningbone.discordbot.DiscordBot;
 import me.despawningbone.discordbot.command.Command;
 import me.despawningbone.discordbot.command.CommandResult;
 import me.despawningbone.discordbot.command.CommandResult.CommandResultType;
 import me.despawningbone.discordbot.command.music.AudioTrackHandler.TrackData;
 import net.dv8tion.jda.api.EmbedBuilder;
 import net.dv8tion.jda.api.Permission;
 import net.dv8tion.jda.api.entities.Member;
 import net.dv8tion.jda.api.entities.Message;
 import net.dv8tion.jda.api.entities.MessageEmbed;
 import net.dv8tion.jda.api.entities.TextChannel;
 import net.dv8tion.jda.api.entities.User;
 import net.dv8tion.jda.api.entities.VoiceChannel;
 import net.dv8tion.jda.api.managers.AudioManager;
 
 @SuppressWarnings("deprecation")
 public class Music extends Command {
 	
 	private AudioTrackHandler handler;
 	
 	public Music() {
 		this.alias = Arrays.asList("m");
 		this.desc = "The music sub-bot!";
 		this.usage = "<subcommands>";
 		
 		this.handler = new AudioTrackHandler();
 		
 		//DONE merge p, pl and ps?
 		registerSubCommand("play", Arrays.asList("p"), (c, u, m, a) -> {
 			List<String> params = new ArrayList<>(Arrays.asList(a));
 			String type = params.removeAll(Collections.singleton("-s")) ? "soundcloud" : "youtube " + (params.removeAll(Collections.singleton("-l")) ? "playlist" : "video");
 			if(a.length < 1) return new CommandResult(CommandResultType.INVALIDARGS, "Please enter something to search or an URL to play."); 
 			try {
 				return handler.searchAndPlay(String.join(" ", params), type, c.getGuild().getMember(u), c).get();
 			} catch (NoSuchElementException e) {
 				return new CommandResult(CommandResultType.NORESULT);
 			} catch (InterruptedException | ExecutionException e) {
 				return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
 			}
 		}, "[-s/-l] <URL/search words>", Arrays.asList("despacito", "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "-l tpz sega", "-s asymmetry reol"), "Queue some music to play!", Arrays.asList(
 				"  * this also supports all opus streams and common audio format as long as you provide the url.",
 				"  * you can specify `-s` when searching to search soundcloud instead of youtube,",
 				"  * or `-l` to search for youtube playlists instead."));
 		
 		//TODO merge? i dont think it can be though
 		//TODO guild config for non restricted loop/autoplay
 		registerSubCommand("autoplay", Arrays.asList("ap"), (c, u, m, a) -> {
 			VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
 			if (vc.getMembers().size() <= 2
 					|| DiscordBot.ModID.contains(u.getId())) {	
 					c.sendMessage("Autoplay mode toggled "
 							+ (handler.toggleLoopQueue(c.getGuild(), "autoplay") ? "on.\nIt will end once someone joins the music channel." : "off."))
 							.queue();
 					return new CommandResult(CommandResultType.SUCCESS);
 			} else {
 				return new CommandResult(CommandResultType.INVALIDARGS, "You are currently not alone in the music channel, therefore this feature is disabled.");
 			}
-		}, "", null, "Auto-queues related track of the last track when you are alone!",
-				Arrays.asList("  Note: it will autoplay indefinitely until you toggle it again or someone joins."));
+		}, "", null, "Auto-queues related tracks when you are alone!",
+				Arrays.asList("  * Currently only works if the last track in the queue is a youtube video.", "  Note: it will autoplay indefinitely until you toggle it again or someone joins."));
 		
 		registerSubCommand("loop", Arrays.asList("l", "loopqueue"), (c, u, m, a) -> {
 			VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
 			if (vc.getMembers().size() <= 2
 					|| DiscordBot.ModID.contains(u.getId())) {	
 					c.sendMessage("Looping mode toggled "
 							+ (handler.toggleLoopQueue(c.getGuild(), "loop") ? "on.\nIt will end once someone joins the music channel." : "off."))
 							.queue();
 					return new CommandResult(CommandResultType.SUCCESS);
 			} else {
 				return new CommandResult(CommandResultType.INVALIDARGS, "You are currently not alone in the music channel, therefore this feature is disabled.");
 			}
 		}, "", null, "Loop the queue indefinitely if you are alone!", 
 				Arrays.asList("  Note: skipping tracks will remove it from the loop too."));
 		
 		
 		registerSubCommand("approve", Arrays.asList("a"), (c, u, m, a) -> { 
 			GuildMusicManager mm = handler.getGuildMusicManager(c.getGuild());
 			if (mm.pending == null) {
 				return new CommandResult(CommandResultType.FAILURE, "There is currently no pending playlists/livestreams.");
 			}
 			Member requester = c.getGuild().getMemberByTag(mm.pending.get(0).getUserData(TrackData.class).getUserWithDiscriminator());
 			VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
 			if(vc == null) vc = requester.getVoiceState().getChannel();  //fallback to requester, usually due to bot not joined yet (first track needs approval)
 			if(vc == null) vc = c.getGuild().getMember(u).getVoiceState().getChannel();  //fallback to approver in case requester left voice channel
 			try {
 				int req = (int) Math.ceil(vc.getMembers().stream().filter(mem -> !mem.getUser().isBot()).count() / 2.0);
 				List<AudioTrack> tracks = mm.pending;  //have to place before vote or else it gets wiped 
 				int votes = mm.vote(u, "tracks", req);
 				if (votes == -1) {  //automatically cleaned up in guildmusicmanager already
 					c.sendMessage("Pending playlist/livestream approved. Queuing...").queue();
 					handler.queueTracks(tracks, requester);
 				} else {
 					c.sendMessage("You voted for the pending playlist! (" + votes + "/"+ req + ")").queue();
 				}	
 				return new CommandResult(CommandResultType.SUCCESS);
 			} catch(UnsupportedOperationException e) {
 				return new CommandResult(CommandResultType.FAILURE, e.getMessage());
 			}
 		}, "", null, "Approve the pending playlist or livestream.", null);
 		
 		
 		registerSubCommand("skip", Arrays.asList("s"), (c, u, m, a) -> {
 			TrackData track = handler.getGuildMusicManager(c.getGuild()).player.getPlayingTrack().getUserData(TrackData.class);
 			if(!u.getAsTag().equals(track.getUserWithDiscriminator())) {
 				VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
 				int req = (int) Math.ceil(vc.getMembers().stream().filter(mem -> !mem.getUser().isBot()).count() / 2.0);
 				try {
 					int votes = track.voteSkip(u, req);
 					if (votes != -1) {
 						c.sendMessage("You voted to skip the current track! (" + votes + "/" + req + ")").queue();
 						return new CommandResult(CommandResultType.SUCCESS);
 					}	
 				} catch (UnsupportedOperationException e) {
 					return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage()); 
 				}
 			}
 			//else skips track if no need votes or vote passed
 			String next = handler.skipTrack(c.getGuild());
 			if(next != null) {
 				c.sendMessage("Skipped to the next track: `" + next + "`.").queue();
 			} else {
 				c.sendMessage("No tracks found after this one. Stopping the player...").queue();
 			}
 			return new CommandResult(CommandResultType.SUCCESS);
 		}, "", null, "Vote to skip the currently playing track.", 
 				Arrays.asList("  * this is equivalent to `forceskip` if you are the one who requested the track."));
 		//TODO merge? i dont think i can either though
 		registerSubCommand("forceskip", Arrays.asList("fs"), (c, u, m, a) -> {
 			String next = handler.skipTrack(c.getGuild());
 			if(next != null) {
 				c.sendMessage("Skipped to the next track: `" + next + "`.").queue();
 			} else {
 				c.sendMessage("No tracks found after this one. Stopping the player...").queue();
 			}
 			return new CommandResult(CommandResultType.SUCCESS);
-		}, "", null, "Skip the currently playing track without voting", null,
+		}, "", null, "Skip the currently playing track without voting.", null,
 				EnumSet.of(Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
 		
 		
 		registerSubCommand("info", Arrays.asList("i", "trackinfo", "track", "nowplaying", "np", "current", "c"), (c, u, m, a) -> {
 			try {
 				c.sendMessage(handler.getTrackInfo(c.getGuild(), a.length > 0 ? Integer.parseInt(a[0]) : 1).build()).queue();
 				return new CommandResult(CommandResultType.SUCCESS);
 			} catch(NumberFormatException | NullPointerException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, "Invalid track index specified.");
 			}
 		}, "[index]", Arrays.asList("", "2"), "See the info about a track in the queue.",
 				Arrays.asList("  * If you did not specify an index, it will return info about the current track playing."));
 		
 		
 		registerSubCommand("queue", Arrays.asList("q", "page", "list"), (c, u, m, a) -> {
 			try {
 				c.sendMessage(handler.queueCheck(c.getGuild(), a.length > 0 ? Integer.parseInt(a[0]) : 1).build()).queue();
 				return new CommandResult(CommandResultType.SUCCESS);
 			} catch(NumberFormatException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, "Invalid page index specified.");
 			} catch(IllegalArgumentException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
 			}
 		}, "[page]", Arrays.asList("", "2"), "See the queue of tracks to play.", 
 				Arrays.asList("  * If you did not specify an page number, it will return the first page."));
 		
 		//TODO allow pausing when alone? unpause when people join; or vote system?
 		//TODO yet another merge lmao but this time idk coz rythm has them as seperate commands
 		registerSubCommand("pause", Arrays.asList("pa"), (c, u, m, a) -> {
 			try {
 				handler.togglePause(c.getGuild(), true);
 				c.sendMessage("Successfully paused the player.").queue();
 				return new CommandResult(CommandResultType.SUCCESS);
 			} catch(IllegalStateException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
 			}
 		}, "", null, "Pauses the music player.", null, EnumSet.of(Permission.VOICE_MOVE_OTHERS), BotUserLevel.DEFAULT.ordinal());
 		
 		registerSubCommand("resume", Arrays.asList("re", "unpause"), (c, u, m, a) -> {
 			try {
 				handler.togglePause(c.getGuild(), false);
 				c.sendMessage("Successfully resumed the player.").queue();
 				return new CommandResult(CommandResultType.SUCCESS);
 			} catch(IllegalStateException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
 			}
 		}, "", null, "Resumes the music player.", null, EnumSet.of(Permission.VOICE_MOVE_OTHERS), BotUserLevel.DEFAULT.ordinal());
 		
 	
 		registerSubCommand("setposition", Arrays.asList("set", "setpos", "seek"), (c, u, m, a) -> {
 			try {
 				String[] s = a[0].split("(?<=[+-])");  //get type of relative pos; only first +/- is used and consecutive +/-s get ignored
 				String[] t = s[s.length - 1].split(":");
 				int index = t.length > 2 ? 1 : 0;
 				String setT = handler.setTrackPosition(c.getGuild(), index == 1 ? Long.parseLong(t[0]) : 0 , Long.parseLong(t[index]), Long.parseLong(t[index + 1]), s.length == 1 ? "" : s[0]);
 				c.sendMessage("Successfully set the timestamp to `" + setT + "`.").queue();
 				return new CommandResult(CommandResultType.SUCCESS);
 			} catch (IndexOutOfBoundsException | NumberFormatException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a valid timestamp with colons.");
 			} catch (IllegalArgumentException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, e.getMessage());
 			}
 		}, "[+/-][hr:]<min:sec>", Arrays.asList("4:52:21", "2:00", "+30:00", "-1:23:45"), "Set the playing position in the current track!",
 				Arrays.asList("timestamps without signs are absolute, whereas `+`/`-` means go forward or backward from the current position for the amount of time specified respectively.", "  * the person who requested the current track can always set the position, regardless of perms."),
 				EnumSet.of(Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
 		
 		
 		//TODO merge with skip?
 		registerSubCommand("removetrack", Arrays.asList("rt", "r", "skiptrack", "st"), (c, u, m, a) -> {
 			if(a.length > 0) {
 				try {
 					int num = Integer.parseInt(a[0]);
 					if(num == 1) return new CommandResult(CommandResultType.INVALIDARGS, "You cannot remove the current track from the queue with this command. Use !desp music skip instead.");
 					if(num < 1) return new CommandResult(CommandResultType.INVALIDARGS, "The index you entered is invalid.");
 					AudioTrack removed = handler.getGuildMusicManager(c.getGuild()).scheduler.removeTrack(num - 1);
 					if(removed != null) {
 						c.sendMessage("Removed track `" + removed.getInfo().title + "` requested by `" + removed.getUserData(TrackData.class).getUserWithDiscriminator() + "`.").queue();
 						return new CommandResult(CommandResultType.SUCCESS);
 					} else {
 						return new CommandResult(CommandResultType.INVALIDARGS, "The track does not exist.");
 					}
 				} catch(NumberFormatException e) {
 					return new CommandResult(CommandResultType.INVALIDARGS, "Please enter a valid index number.");
 				}
 			} else {
 				return new CommandResult(CommandResultType.INVALIDARGS, "Please enter an index.");
 			}
 			
 		}, "<index>", null, "Remove a track at the specified index of the queue.",
 				Arrays.asList("  * the person who requested the track which is to be removed can always execute the command, regardless of perms."),
 				EnumSet.of(Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
 
 		
 		registerSubCommand("move", Arrays.asList("m", "movetrack"), (c, u, m, a) -> {
 			try {
 				if(a[0].equals("1") || a[1].equals("1")) return new CommandResult(CommandResultType.INVALIDARGS, "You cannot move the currently playing track.");
 				AudioTrack track = handler.getGuildMusicManager(c.getGuild()).scheduler.moveTrack(Integer.parseInt(a[0]) - 2, Integer.parseInt(a[1]) - 2);
 				c.sendMessage("Successfully moved `" + track.getInfo().title + "` to position `" + a[1] + "`.").queue();
 				return new CommandResult(CommandResultType.SUCCESS);
 			} catch(IndexOutOfBoundsException | NumberFormatException | NullPointerException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, "Please valid indices.");
 			}
 		}, "<fromindex> <toindex>", Arrays.asList("2 3"), "Move a track to the specified index.", Arrays.asList("  * you can also run this command regardless of perms if you are alone with the bot.")
 				, EnumSet.of(Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
 		
 		
 		//TODO make vote system for this
 		registerSubCommand("shuffle", null, (c, u, m, a) -> {
 			VoiceChannel vc = c.getGuild().getAudioManager().getConnectedChannel();
 			if (vc.getMembers().size() <= 2
 					|| DiscordBot.ModID.contains(u.getId())) {	
 					handler.getGuildMusicManager(c.getGuild()).scheduler.shuffleQueue();
 					c.sendMessage("Successfully shuffled the queue.").queue();
 					return new CommandResult(CommandResultType.SUCCESS);
 			} else {
 				return new CommandResult(CommandResultType.INVALIDARGS, "You are currently not alone in the music channel, therefore this feature is disabled.");
 			}
 		}, "", null, "Shuffle the tracks in the queue when you are alone!", null);
 		
 		
 		//TODO make vote system for this
 		registerSubCommand("clear", Arrays.asList("disconnect", "dc", "stop", "clearqueue"), (c, u, m, a) -> {
 			handler.stopAndClearQueue(c.getGuild());
 			c.sendMessage("The queue has been cleared.").queue();
 			return new CommandResult(CommandResultType.SUCCESS);
 		}, "", null, "Clear the queue and stop the player.", Arrays.asList("  * you can also run this command regardless of perms if you are alone with the bot.")
 				, EnumSet.of(Permission.VOICE_MUTE_OTHERS, Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
 
 		registerSubCommand("lyrics", Arrays.asList("ly"), (c, u, m, a) -> {
 			c.sendTyping().queue();
 			try {
 				String search = a.length > 0 ? String.join(" ", a) : handler.getGuildMusicManager(c.getGuild()).player.getPlayingTrack().getInfo().title
 						.split("ft.")[0].replaceAll("\\(.*?\\)", "")
 						.replaceAll("\\[.*?\\]", "")
 						.replaceAll("\\【.*?\\】", "")
 						.replaceAll("-", "").trim();
 				getLyrics(search).forEach(em -> c.sendMessage(em).queue());
 				return new CommandResult(CommandResultType.SUCCESS);
 			} catch(IllegalArgumentException | UnsupportedOperationException e) {
 				return new CommandResult(CommandResultType.FAILURE, e.getMessage());
 			} catch(NullPointerException e) {
 				return new CommandResult(CommandResultType.INVALIDARGS, "There is nothing playing currently! Please specify a song title to search the lyrics up.");
 			} catch(IOException e) {
 				return new CommandResult(CommandResultType.ERROR, ExceptionUtils.getStackTrace(e));
 			}
 		}, "[search words]", Arrays.asList(""), "Search some lyrics up!", Arrays.asList("  * if you did not specify any search words, the bot will try to fetch the lyrics of the current track playing, if any."));
 		
 		
 		//TODO equalizer persistence?
 		//make this premium?
 		registerSubCommand("equalizer", Arrays.asList("eq"), (c, u, m, a) -> {
 			GuildMusicManager mm = handler.getGuildMusicManager(c.getGuild());
 			Float[] vals;
 			if(a.length == 0) {
 				vals = mm.getCurrentGain();
 			} else {
 				try {
 					try {
 						if(a.length != 15) throw new NumberFormatException();
 						vals = new Float[15];
 						for(int i = 0; i < a.length; i++) {
 							vals[i] = Float.parseFloat(a[i]);
 						}
 						mm.setGain(0, vals);
 					} catch(NumberFormatException e) {
 							vals = mm.setPresetGain(a[0]);
 					}
 				} catch(IllegalArgumentException e2) {
 					return new CommandResult(CommandResultType.INVALIDARGS, e2.getMessage());
 				} 
 			}
 			//formatting
 			DecimalFormat df = new DecimalFormat("0.00");
 			df.setPositivePrefix("+");
 			EmbedBuilder eb = new EmbedBuilder();
 			eb.setTitle("Current equalizer graph");
 			eb.appendDescription("```\n");
 			for(double line = 0.25; line >= -0.25; line -= 0.05) {
 				eb.appendDescription(df.format(line) + "  ");
 				for(int band = 0; band < 15; band++) {
 					if(Math.abs(0.05 * Math.round(vals[band] / 0.05) - line) < 1E-7) eb.appendDescription("🔘");
 					else eb.appendDescription(" ❘ ");
 				}
 				eb.appendDescription("\n");
 			}
 			eb.appendDescription("```");
 			eb.addField("Actual values", Arrays.asList(vals).stream().map(f -> df.format(f)).collect(Collectors.joining(" ")), false);
 			c.sendMessage(eb.build()).queue();
 			return new CommandResult(CommandResultType.SUCCESS);
 		}, "[bandvalues/presetname]", Arrays.asList("", "0.09 0.07 0.07 0.01 0 0 -0.02 -0.02 0.03 0.03 0.05 0.07 0.09 0.1 0.1", "bassboost"), "Sets the equalizer for the music player in this guild!", 
 				Arrays.asList("Accepts 15 bands with values ranging from -0.25 to 0.25, where -0.25 is muted and 0.25 is double volume.", "* presets include: `bassboost`, `default`, `rock`.", "Input nothing to return the current settings.", "",
 						"Note: you might experience some audio cracking for band values >0.1, since amplifying volumes remotely does not work well.", "It is recommended to use values from -0.25 to 0.1, and turning discord volume up instead.")
 				, EnumSet.of(Permission.VOICE_MUTE_OTHERS, Permission.VOICE_MOVE_OTHERS), -BotUserLevel.BOT_MOD.ordinal());
 		
 		
 		registerSubCommand("shutdown", null, (c, u, m, a) -> {
 			handler.shutdown();
 			handler = null;
 			c.sendMessage("Successfully destroyed the music player instance.").queue();
 			return new CommandResult(CommandResultType.SUCCESS);
 		}, "", null, "Shuts down the music player sub-bot.", null, EnumSet.noneOf(Permission.class), BotUserLevel.BOT_OWNER.ordinal());
 		
 		registerSubCommand("startup", Arrays.asList("start"), (c, u, m, a) -> {
 			if(handler != null) return new CommandResult(CommandResultType.FAILURE, "An instance is already running. Please shut it down first.");
 			else handler = new AudioTrackHandler();
 			c.sendMessage("Successfully created the music player instance.").queue();
 			return new CommandResult(CommandResultType.SUCCESS);
 		}, "", null, "Boots up the music player sub-bot.", null, EnumSet.noneOf(Permission.class), BotUserLevel.BOT_OWNER.ordinal());
 		
 		registerSubCommand("restart", Arrays.asList("reboot"), (c, u, m, a) -> {
 			handler.shutdown();
 			handler = new AudioTrackHandler();
 			c.sendMessage("Successfully rebooted the music player instance.").queue();
 			return new CommandResult(CommandResultType.SUCCESS);
 		}, "", null, "Reboots the music player sub-bot.", null, EnumSet.noneOf(Permission.class), BotUserLevel.BOT_OWNER.ordinal());
 	}
 
 	public AudioTrackHandler getAudioTrackHandler() {
 		return handler;
 	}
 	
 	//TODO make a skipuntil command that requires voting? //prob not
 	//TODO make clear accessible on vote?
 	
 	@Override
 	public CommandResult execute(TextChannel channel, User author, Message msg, String[] args) {
 		
 		DiscordBot.lastMusicCmd.put(channel.getGuild().getId(), channel);
 		Command sub = args.length > 0 ? getSubCommand(args[0].toLowerCase()) : null;
 		
 		if(sub != null) {  //only run if not null, else hand to normal sub command handler
 			//if handler is null it means the music player is shut down
 			if(!sub.getName().equals("startup")) {
 				if(handler == null) return new CommandResult(CommandResultType.FAILURE, "The music bot is in maintenance right now!");
 				
 				//pre check to block out all music commands if the player is not running
 				List<String> exception = Arrays.asList("play", "approve", "shutdown", "lyrics", "equalizer");
 				GuildMusicManager mm = handler.getGuildMusicManager(channel.getGuild());
 				if(mm.player.getPlayingTrack() == null && 
 						!exception.contains(sub.getName()))
 					return new CommandResult(CommandResultType.INVALIDARGS, "There are no tracks playing currently.");
 				else {
 					//else block out all users that are not in the same voice channel 
 					exception = Arrays.asList("lyrics", "queue", "info");
 					AudioManager man = channel.getGuild().getAudioManager();
 					if(man.isConnected() && !man.getConnectedChannel().getMembers().contains(channel.getGuild().getMember(author))
 							&& !(exception.contains(sub.getName()) || DiscordBot.ModID.contains(author.getId()))) {
 						return new CommandResult(CommandResultType.INVALIDARGS, "You are not in the same channel as the bot."); 
 					}
 				}
 				
 				//perms overriding for queuer
 				if(Arrays.asList("forceskip", "skip", "setposition").contains(sub.getName())) {
 					if(author.getAsTag().equals(mm.player.getPlayingTrack().getUserData(TrackData.class).getUserWithDiscriminator()))
 						return sub.execute(channel, author, msg, Arrays.copyOfRange(args, 1, args.length));
 				} else if(sub.getName().equals("removetrack")) {
 					try {
 						if(author.getAsTag().equals(mm.scheduler.findTracks(Integer.parseInt(args[1]) - 1, 1).get(0).getUserData(TrackData.class).getUserWithDiscriminator()))
 							return sub.execute(channel, author, msg, Arrays.copyOfRange(args, 1, args.length)); 
 					} catch(Exception ignored) {
 						ignored.printStackTrace();
 					}
 				}
 				
 				//perms overriding for when alone
 				if(Arrays.asList("move", "clear").contains(sub.getName())) {  //should always be connected to vc
 					if(channel.getGuild().getAudioManager().getConnectedChannel().getMembers().size() == 2) {  //alone; no need to check if the other member is requester, since its checked before
 						return sub.execute(channel, author, msg, Arrays.copyOfRange(args, 1, args.length)); 
 					}
 				}
 			}
 		}
 		
 		CommandResult res = super.execute(channel, author, msg, args);  //pass to subcommand handler for perms checking
 		if(res.getResultType() == CommandResultType.NOPERMS && Arrays.asList("forceskip", "skip", "setposition", "removetrack").contains(sub.getName())) {  //add back info to command result
 			res = new CommandResult(CommandResultType.INVALIDARGS, res.getMessage().getContentRaw().split(" to execute")[0] + ", or have requested the track to execute this command."); 
 		}
 		return res;
 		
 	}
 	
 	
 	//maybe move the lyrics stuff elsewhere?
 	
 	
 	private final String geniusAuth = DiscordBot.tokens.getProperty("genius");
 	
 	//annotated usually isnt actual lyrics, but the following are known to be some
 	private List<String> whitelist = Arrays.asList("https://genius.com/Noma-jpn-brain-power-annotated");
 	
 	//private ExecutorService executor = Executors.newCachedThreadPool();   //unfortunately i need to finish the api search to get the url to start the html scrape, which means i cannot use future for multithread here
 	
 	public List<MessageEmbed> getLyrics(String search) throws IOException {  //DONE dont splice words between embeds; make whole sentence to be spliced instead
 		//System.out.println(search);
 		JSONObject main = null;
 		try {
 			URLConnection searchCon = new URL("https://api.genius.com/search?access_token=" + geniusAuth + "&q=" + URLEncoder.encode(search, "UTF-8").replaceAll("\\+", "%20")).openConnection();
 			searchCon.addRequestProperty("User-Agent", "Mozilla/4.0");
 			InputStream searchStream = searchCon.getInputStream();
 			JSONTokener searchResult = new JSONTokener(searchStream);
 			JSONArray list = new JSONObject(searchResult).getJSONObject("response").getJSONArray("hits");
 			
 			//get first valid result
 			for(int i = 0; i < list.length(); i++) {
 				JSONObject check = list.getJSONObject(i).getJSONObject("result");
 				if(!check.getString("url").endsWith("lyrics") && !whitelist.contains(check.getString("url"))) {
 					System.out.println("    " + check.getString("url"));
 					continue;
 				}
 				main = check;
 				break;
 			}
 			
 			searchStream.close();
 		} catch (JSONException | ArrayIndexOutOfBoundsException e) {
 			e.printStackTrace();
 			throw new IllegalArgumentException("There were no results unfortunately :cry:");
 		}
 			
 		if(main == null) {
 			throw new IllegalArgumentException("There were no results unfortunately :cry:");
 		}
 		
 		//get actual lyrics
 		String url = main.getString("url");
 		Document document = Jsoup.connect(url).userAgent("Mozilla/4.0").get();
 		Element html = document.body();
 		
 		//parse and lint
 		Element el = html.selectFirst("div[class=\"lyrics\"]");
 		String s;
 		if(el == null) {
 			el = html.selectFirst("div[class*=\"Lyrics__Root\"]");  //second version of the page
 			el.select("div[class*=\"Ad__Container\"]").remove();
 			el.select("a, span, i, div[class*=\"Lyrics__Container\"]").unwrap();
-			s = el.html().replaceAll("\\\\n", "\n");
+			el.select("div[class*=\"Lyrics__Footer\"]").remove();
 		} else {
 			el.select("br").append("\\n");
-		    el.select("p").prepend("\\n\\n");
-		    s = el.html().replaceAll("\\\\n", "\n");
+			el.select("p").prepend("\\n\\n");
 		}
+		s = el.html().replaceAll("\\\\n", "\n");
 	    String lyrics = Jsoup.clean(s, "", Whitelist.none(), new Document.OutputSettings().prettyPrint(false));
 	    
 	    //split into lyrics segments
 	    List<String> lyList = new ArrayList<String>(Splitter.fixedLength(2000).splitToList(
 	    		lyrics.trim().replaceAll("&amp;", "&").replaceAll("&nbsp;", " ")
 	    		.replaceAll("\n ", "\n")));  //retain this formatting? it looks more clean yet more jumbled at the same time
 	    
 	    if(lyList.size() > 5) {  //failover catch for whats most likely not lyrics
 	    	throw new UnsupportedOperationException("The lyrics is too long for a normal song :poop:");
 	    }
 	    
 	    //build first embed
 	    EmbedBuilder eb = new EmbedBuilder();
 	    String auImg = main.getJSONObject("primary_artist").getString("header_image_url");
     	eb.setAuthor(main.getString("full_title"), url, auImg.contains("https://assets.genius.com/images/default_avatar_300.png") ? null : auImg);
     	String alImg = main.getString("song_art_image_thumbnail_url");
     	eb.setThumbnail(alImg.contains("https://assets.genius.com/images/default_cover_image.png") ? null : alImg);
     	formatLyrics(eb, "**Lyrics**\n\n " + (lyList.size() > 0 ? lyList.get(0).trim() : "N/A"), lyList, 0);
     	eb.setColor(new Color(255, 255, 100));
     	
     	//build rest of the embeds
 		List<MessageEmbed> em = new ArrayList<MessageEmbed>();
 	    if(lyList.size() > 1) {
 	    	em.add(eb.build());
 	    	
 	    	for(int i = 1; i < lyList.size(); i++) {
 	    		EmbedBuilder loopEm = new EmbedBuilder();
 	    		loopEm.setColor(new Color(255, 255, 100));
 	    		formatLyrics(loopEm, lyList.get(i), lyList, i);
 	    		if(i == lyList.size() - 1) {
 	    			setFinalLyricsEmbed(loopEm, main, html);
 	    		}
 	    		em.add(loopEm.build());
 	    	}
 	    } else {  //first and last same embed, thus set final
 	    	setFinalLyricsEmbed(eb, main, html);
 			em = Arrays.asList(eb.build());
 	    }
 	    
 		//System.out.println(lyrics);
 		return em;
 	}
 	
 	//set given embed's description, shifting lyrics to the next embed "page" if overflow
 	private void formatLyrics(EmbedBuilder eb, String segment, List<String> lyList, int i) {
 		if(segment.length() < 2000) {
 			eb.setDescription(segment.trim());
 			return;
 		}
 		String includeSeg = segment.length() > 2000 ? segment.substring(0, 2000) : segment;
 		int index = includeSeg.lastIndexOf("\n");
 		eb.setDescription(segment.substring(0, index));
 		String move = segment.substring(includeSeg.lastIndexOf("\n"));
 		try {
 			if(!move.isEmpty()) lyList.set(i + 1, move + lyList.get(i + 1));
 		} catch (IndexOutOfBoundsException e) {
 			lyList.add(move);
 		}
 	}
 	
 	//appends metadata portion to the given embed (should be the last one)
 	private void setFinalLyricsEmbed(EmbedBuilder eb, JSONObject main, Element html) {   //change the format for the supplementary info in artists and albums from italic to sth else?
 		try {
 			String name = main.getJSONObject("primary_artist").getString("name");
 
 			try {
 				String others = html.select("script:containsData(_sf_async_config)").html().split("_sf_async_config.authors = '")[1].split("';")[0].replace(",", ", ");
 				eb.addField("Artists", new StringBuffer(others).insert(others.indexOf(name) + name.length() + 2, "\n*").toString() + "*", true);
 			} catch (IndexOutOfBoundsException e) {
 				eb.addField("Artist", name, true);
 			}
 		} catch (JSONException e) {
 			e.printStackTrace();
 			eb.addField("Artist", "Unknown", true);
 		}
 		
 		if(!html.select("div[class=\"lyrics\"]").isEmpty()) {
 			Elements buffer = html.select("span:contains(Album) ~ span[class=\"metadata_unit-info\"] a");
 
 			//parse albums
 	    	if(buffer.size() > 0) {
 	    		String album = buffer.get(0).ownText();
 	    		StringBuilder sb = new StringBuilder();
 	        	JSONObject data = new JSONObject(html.select("script[type=\"application/ld+json\"]").get(0).html());
 	    		data.getJSONArray("inAlbum").forEach(obj -> {
 					String n = ((JSONObject) obj).getString("name");
 					if(!n.equals(album)) {
 						sb.append("*" + n.trim() + "*\n");
 					}
 				});
 	    		String others = sb.toString();
 	    		eb.addField("Album" + (others.isEmpty() ? "" : "s"), album + (others.isEmpty() ? "" : "\n" + others), true);  //add link?
 	    	}
 	    	
 			//parse release date
 	    	buffer = html.select("span:contains(Release Date) ~ span[class*=\"metadata_unit-info\"]");
 	    	if(buffer.size() > 0) eb.addField("Release Date", buffer.get(0).ownText(), true);
 	    	
 	    	//parse tags
 			try {
 				String sbuff = URLDecoder.decode(html.select("img[src*=\"page-genres=\"]").first().absUrl("src").split("page-genres=")[1].split("&page-type")[0].replaceAll("\\+", " ").split("&")[0], "UTF-8");
 				if(!sbuff.isEmpty()) {
 		    		eb.addField("Tags", sbuff.trim().replaceAll(",", ", "), false);
 		    	}
 			} catch (UnsupportedEncodingException | NullPointerException e) {
 				e.printStackTrace();
 			}
 			
 			//parse background info
 	    	buffer = html.select("div[class=\"annotation_label\"] ~ div[class=\"rich_text_formatting\"]");
 	    	if(buffer.size() > 0) {
-	    		String bgInfo = buffer.get(0).text();
+	    		buffer.get(0).select("blockquote p").prepend("> ");
+	    		buffer.get(0).select("p").prepend("\\n\\n");
+	    		String bgInfo = buffer.get(0).text().replaceAll("\\\\n", "\n");
 	    		if(!bgInfo.trim().isEmpty()) {
 	    			if(bgInfo.length() > 1024) {
 	    				bgInfo = bgInfo.substring(0, 1021) + "...";
 	    			}
 	    			eb.addField("Background info", bgInfo, false);	
 	    		}
 	    	}
 		} else {  //second version -- actually has a lot more info, use?
 			System.out.println("Second ver");
 			
 			String temp = html.select("script:containsData(__PRELOADED_STATE__)").html().split("JSON.parse\\(\'", 2)[1].split("\'\\);\n", 2)[0];
 			JSONObject preload = new JSONObject(StringEscapeUtils.unescapeJson(temp));
 			JSONObject song = preload.getJSONObject("entities").getJSONObject("songs").getJSONObject(Integer.toString(preload.getJSONObject("songPage").getInt("song")));
 			
 			//parse albums
 			try {
 				StringBuilder sb = new StringBuilder();
 				String album = IntStream.range(0, song.getJSONArray("trackingData").length()).mapToObj(i -> song.getJSONArray("trackingData").getJSONObject(i)).filter(obj -> obj.getString("key").equals("Primary Album")).findFirst().get().getString("value").trim();
 				song.getJSONArray("albums").forEach(obj -> {
 					String n = ((JSONObject) obj).getString("name");
 					if(!n.trim().equals(album)) {
 						sb.append("*" + n.trim() + "*\n");
 					}
 				});
 				String others = sb.toString();
 	    		eb.addField("Album" + (others.isEmpty() ? "" : "s"), album + (others.isEmpty() ? "" : "\n" + others), true);  //add link?	
 			} catch (JSONException ignored) {
 				//no albums; value is null
 			}
 			
 			//release date
 			eb.addField("Release Date", song.getString("releaseDateForDisplay"), true);
 			
 			//parse tags
 			ArrayList<String> tags = new ArrayList<>();
 			for(int i = 0; i < song.getJSONArray("tags").length(); i++) {
-				song.getJSONArray("tags").getJSONObject(i).getString("name");
+				tags.add(song.getJSONArray("tags").getJSONObject(i).getString("name"));
 			}
 			if(!tags.isEmpty()) eb.addField("Tags", String.join(", ", tags), false);
 			
 			//parse background info
 			String bgInfo = song.getJSONObject("description").getString("markdown");
 			if(bgInfo.length() > 1024) {
 				bgInfo = bgInfo.substring(0, 1021) + "...";
 			}
 			if(!bgInfo.equals("?")) eb.addField("Background info", bgInfo, false);  //apparently its ? for empty descs lol
 		}
    
     	eb.setFooter((main.getJSONObject("stats").has("pageviews") ? NumberFormat.getIntegerInstance().format(main.getJSONObject("stats").getInt("pageviews")) + " views | " : "") + "Powered by Genius",
     			"https://yt3.ggpht.com/a/AATXAJzPOKLs0x9W_yNpTUPvwg-zeSnJaxqzf2CU0g=s900-c-k-c0xffffffff-no-rj-mo");
 	}
 }