diff --git a/pom.xml b/pom.xml index b8b350654..af250b4f5 100644 --- a/pom.xml +++ b/pom.xml @@ -45,22 +45,21 @@ 4.4.1_353 - com.jagrosh - jda-utilities - 3.0.5 - pom + com.github.JDA-Applications + JDA-Utilities + c16a4b264b dev.arbjerg lavaplayer - 2.2.1 + 2.2.2 dev.lavalink.youtube common - 1.5.2 + 1.8.3 com.github.jagrosh diff --git a/src/main/java/com/jagrosh/jmusicbot/Bot.java b/src/main/java/com/jagrosh/jmusicbot/Bot.java index de6d6f756..6041e2669 100644 --- a/src/main/java/com/jagrosh/jmusicbot/Bot.java +++ b/src/main/java/com/jagrosh/jmusicbot/Bot.java @@ -26,6 +26,7 @@ import com.jagrosh.jmusicbot.playlist.PlaylistLoader; import com.jagrosh.jmusicbot.settings.SettingsManager; import java.util.Objects; +import com.jagrosh.jmusicbot.utils.YoutubeOauth2TokenHandler; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Guild; @@ -44,24 +45,28 @@ public class Bot private final PlaylistLoader playlists; private final NowplayingHandler nowplaying; private final AloneInVoiceHandler aloneInVoiceHandler; + private final YoutubeOauth2TokenHandler youTubeOauth2TokenHandler; + private final GUI gui; private boolean shuttingDown = false; private JDA jda; - private GUI gui; - public Bot(EventWaiter waiter, BotConfig config, SettingsManager settings) + public Bot(EventWaiter waiter, BotConfig config, SettingsManager settings, GUI gui) { this.waiter = waiter; this.config = config; this.settings = settings; this.playlists = new PlaylistLoader(config); this.threadpool = Executors.newSingleThreadScheduledExecutor(); + this.youTubeOauth2TokenHandler = new YoutubeOauth2TokenHandler(); + this.youTubeOauth2TokenHandler.init(); this.players = new PlayerManager(this); this.players.init(); this.nowplaying = new NowplayingHandler(this); this.nowplaying.init(); this.aloneInVoiceHandler = new AloneInVoiceHandler(this); this.aloneInVoiceHandler.init(); + this.gui = gui; } public BotConfig getConfig() @@ -103,6 +108,11 @@ public AloneInVoiceHandler getAloneInVoiceHandler() { return aloneInVoiceHandler; } + + public YoutubeOauth2TokenHandler getYouTubeOauth2Handler() + { + return youTubeOauth2TokenHandler; + } public JDA getJDA() { @@ -152,9 +162,4 @@ public void setJDA(JDA jda) { this.jda = jda; } - - public void setGUI(GUI gui) - { - this.gui = gui; - } } diff --git a/src/main/java/com/jagrosh/jmusicbot/BotConfig.java b/src/main/java/com/jagrosh/jmusicbot/BotConfig.java index f6a8757ac..06c64cf5e 100644 --- a/src/main/java/com/jagrosh/jmusicbot/BotConfig.java +++ b/src/main/java/com/jagrosh/jmusicbot/BotConfig.java @@ -42,7 +42,7 @@ public class BotConfig private String token, prefix, altprefix, helpWord, playlistsFolder, logLevel, successEmoji, warningEmoji, errorEmoji, loadingEmoji, searchingEmoji, evalEngine; - private boolean stayInChannel, songInGame, npImages, updatealerts, useEval, dbots; + private boolean youtubeOauth2, stayInChannel, songInGame, npImages, updatealerts, useEval, dbots; private long owner, maxSeconds, aloneTimeUntilStop; private int maxYTPlaylistPages; private double skipratio; @@ -84,6 +84,7 @@ public void load() searchingEmoji = config.getString("searching"); game = OtherUtil.parseGame(config.getString("game")); status = OtherUtil.parseStatus(config.getString("status")); + youtubeOauth2 = config.getBoolean("youtubeoauth2"); stayInChannel = config.getBoolean("stayinchannel"); songInGame = config.getBoolean("songinstatus"); npImages = config.getBoolean("npimages"); @@ -293,6 +294,11 @@ public String getHelp() return helpWord; } + public boolean useYoutubeOauth2() + { + return youtubeOauth2; + } + public boolean getStay() { return stayInChannel; diff --git a/src/main/java/com/jagrosh/jmusicbot/JMusicBot.java b/src/main/java/com/jagrosh/jmusicbot/JMusicBot.java index 30409a6bf..2e2ad7ddb 100644 --- a/src/main/java/com/jagrosh/jmusicbot/JMusicBot.java +++ b/src/main/java/com/jagrosh/jmusicbot/JMusicBot.java @@ -82,28 +82,15 @@ private static void startBot() config.load(); if(!config.isValid()) return; - LOG.info("Loaded config from " + config.getConfigLocation()); - // set log level from config - ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel( - Level.toLevel(config.getLogLevel(), Level.INFO)); - - // set up the listener - EventWaiter waiter = new EventWaiter(); - SettingsManager settings = new SettingsManager(); - Bot bot = new Bot(waiter, config, settings); - CommandClient client = createCommandClient(config, settings, bot); - + GUI gui = null; if(!prompt.isNoGUI()) { - try + try { - GUI gui = new GUI(bot); - bot.setGUI(gui); + gui = new GUI(); gui.init(); - - LOG.info("Loaded config from " + config.getConfigLocation()); } catch(Exception e) { @@ -113,6 +100,21 @@ private static void startBot() } } + LOG.info("Loaded config from " + config.getConfigLocation()); + + // set log level from config + ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel( + Level.toLevel(config.getLogLevel(), Level.INFO)); + + // set up the listener + EventWaiter waiter = new EventWaiter(); + SettingsManager settings = new SettingsManager(); + Bot bot = new Bot(waiter, config, settings, gui); + if (gui != null) + gui.setBot(bot); + CommandClient client = createCommandClient(config, settings, bot); + + // attempt to log in and start try { diff --git a/src/main/java/com/jagrosh/jmusicbot/Listener.java b/src/main/java/com/jagrosh/jmusicbot/Listener.java index 2e50b793a..3f2ee1531 100644 --- a/src/main/java/com/jagrosh/jmusicbot/Listener.java +++ b/src/main/java/com/jagrosh/jmusicbot/Listener.java @@ -17,8 +17,10 @@ import com.jagrosh.jmusicbot.utils.OtherUtil; import java.util.concurrent.TimeUnit; +import com.jagrosh.jmusicbot.utils.YoutubeOauth2TokenHandler; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.PrivateChannel; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.VoiceChannel; import net.dv8tion.jda.api.events.ReadyEvent; @@ -85,6 +87,26 @@ public void onReady(ReadyEvent event) catch(Exception ignored) {} // ignored }, 0, 24, TimeUnit.HOURS); } + if (bot.getConfig().useYoutubeOauth2()) + { + YoutubeOauth2TokenHandler.Data data = bot.getYouTubeOauth2Handler().getData(); + if (data != null) + { + try + { + PrivateChannel channel = bot.getJDA().openPrivateChannelById(bot.getConfig().getOwnerId()).complete(); + channel + .sendMessage( + "# DO NOT AUTHORISE THIS WITH YOUR MAIN GOOGLE ACCOUNT!!!\n" + + "## Create or use an alternative/burner Google account!\n" + + "To give JMusicBot access to your Google account, go to " + + data.getAuthorisationUrl() + + " and enter the code **" + data.getCode() + "**") + .queue(); + } + catch (Exception ignored) {} + } + } } @Override diff --git a/src/main/java/com/jagrosh/jmusicbot/audio/AudioHandler.java b/src/main/java/com/jagrosh/jmusicbot/audio/AudioHandler.java index e5b93fc50..b26c95471 100644 --- a/src/main/java/com/jagrosh/jmusicbot/audio/AudioHandler.java +++ b/src/main/java/com/jagrosh/jmusicbot/audio/AudioHandler.java @@ -41,6 +41,7 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.User; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** @@ -53,6 +54,7 @@ public class AudioHandler extends AudioEventAdapter implements AudioSendHandler public final static String PAUSE_EMOJI = "\u23F8"; // ⏸ public final static String STOP_EMOJI = "\u23F9"; // ⏹ + private final static Logger LOGGER = LoggerFactory.getLogger(AudioHandler.class); private final List defaultQueue = new LinkedList<>(); private final Set votes = new HashSet<>(); @@ -202,8 +204,22 @@ public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason } @Override - public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) { - LoggerFactory.getLogger("AudioHandler").error("Track " + track.getIdentifier() + " has failed to play", exception); + public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception) + { + if ( + exception.getMessage().equals("Sign in to confirm you're not a bot") + || exception.getMessage().equals("Please sign in") + || exception.getMessage().equals("This video requires login.") + ) + LOGGER.error( + "Track {} has failed to play: {}. " + + "You will need to sign in to Google to play YouTube tracks. " + + "More info: https://jmusicbot.com/youtube-oauth2", + track.getIdentifier(), + exception.getMessage() + ); + else + LOGGER.error("Track {} has failed to play", track.getIdentifier(), exception); } @Override diff --git a/src/main/java/com/jagrosh/jmusicbot/audio/PlayerManager.java b/src/main/java/com/jagrosh/jmusicbot/audio/PlayerManager.java index 56999a92b..ecaee80a7 100644 --- a/src/main/java/com/jagrosh/jmusicbot/audio/PlayerManager.java +++ b/src/main/java/com/jagrosh/jmusicbot/audio/PlayerManager.java @@ -17,6 +17,7 @@ import com.dunctebot.sourcemanagers.DuncteBotSources; import com.jagrosh.jmusicbot.Bot; +import com.jagrosh.jmusicbot.utils.OtherUtil; import com.sedmelluq.discord.lavaplayer.container.MediaContainerRegistry; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; @@ -31,6 +32,11 @@ import com.sedmelluq.discord.lavaplayer.source.vimeo.VimeoAudioSourceManager; import dev.lavalink.youtube.YoutubeAudioSourceManager; import net.dv8tion.jda.api.entities.Guild; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; /** * @@ -38,6 +44,7 @@ */ public class PlayerManager extends DefaultAudioPlayerManager { + private final static Logger LOGGER = LoggerFactory.getLogger(PlayerManager.class); private final Bot bot; public PlayerManager(Bot bot) @@ -49,8 +56,7 @@ public void init() { TransformativeAudioSourceManager.createTransforms(bot.getConfig().getTransforms()).forEach(t -> registerSourceManager(t)); - YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true); - yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages()); + YoutubeAudioSourceManager yt = setupYoutubeAudioSourceManager(); registerSourceManager(yt); registerSourceManager(SoundCloudAudioSourceManager.createDefault()); @@ -66,7 +72,42 @@ public void init() DuncteBotSources.registerAll(this, "en-US"); } - + + private YoutubeAudioSourceManager setupYoutubeAudioSourceManager() + { + YoutubeAudioSourceManager yt = new YoutubeAudioSourceManager(true); + yt.setPlaylistPageCount(bot.getConfig().getMaxYTPlaylistPages()); + + // OAuth2 setup + if (bot.getConfig().useYoutubeOauth2()) + { + String token = null; + try + { + token = Files.readString(OtherUtil.getPath("youtubetoken.txt")); + } + catch (NoSuchFileException e) + { + /* ignored */ + } + catch (IOException e) + { + LOGGER.warn("Failed to read YouTube OAuth2 token file: {}", e.getMessage()); + return yt; + } + LOGGER.debug("Read YouTube OAuth2 refresh token from youtubetoken.txt"); + try + { + yt.useOauth2(token, false); + } + catch (Exception e) + { + LOGGER.warn("Failed to authorise with YouTube. If this issue persists, delete the youtubetoken.txt file to reauthorise.", e); + } + } + return yt; + } + public Bot getBot() { return bot; diff --git a/src/main/java/com/jagrosh/jmusicbot/gui/GUI.java b/src/main/java/com/jagrosh/jmusicbot/gui/GUI.java index e3f47ae7a..6e8ecab2e 100644 --- a/src/main/java/com/jagrosh/jmusicbot/gui/GUI.java +++ b/src/main/java/com/jagrosh/jmusicbot/gui/GUI.java @@ -30,15 +30,18 @@ public class GUI extends JFrame { private final ConsolePanel console; - private final Bot bot; + private Bot bot; - public GUI(Bot bot) + public GUI() { super(); - this.bot = bot; console = new ConsolePanel(); } + public void setBot(Bot bot) { + this.bot = bot; + } + public void init() { setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); @@ -56,7 +59,8 @@ public void init() { try { - bot.shutdown(); + if (bot != null) + bot.shutdown(); } catch(Exception ex) { diff --git a/src/main/java/com/jagrosh/jmusicbot/utils/YoutubeOauth2TokenHandler.java b/src/main/java/com/jagrosh/jmusicbot/utils/YoutubeOauth2TokenHandler.java new file mode 100644 index 000000000..3e1071702 --- /dev/null +++ b/src/main/java/com/jagrosh/jmusicbot/utils/YoutubeOauth2TokenHandler.java @@ -0,0 +1,91 @@ +package com.jagrosh.jmusicbot.utils; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.turbo.TurboFilter; +import ch.qos.logback.core.spi.FilterReply; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; + +import java.nio.file.Files; + +/** + * A logback turbo filter, used retrieve the YouTube OAuth2 refresh token that gets logged once authorized with YouTube. + * + * @author Michaili K. + */ +public class YoutubeOauth2TokenHandler extends TurboFilter { + public final static org.slf4j.Logger LOGGER = LoggerFactory.getLogger(YoutubeOauth2TokenHandler.class); + private Data data; + + + public void init() + { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + context.addTurboFilter(this); + } + + public Data getData() + { + return data; + } + + @Override + public FilterReply decide(Marker marker, Logger logger, Level level, String format, Object[] params, Throwable t) + { + if (!logger.getName().equals("dev.lavalink.youtube.http.YoutubeOauth2Handler")) + return FilterReply.NEUTRAL; + + if (format.equals("OAUTH INTEGRATION: To give youtube-source access to your account, go to {} and enter code {}")) + { + this.data = new Data((String) params[0], (String) params[1]); + return FilterReply.NEUTRAL; + } + if (format.equals("OAUTH INTEGRATION: Token retrieved successfully. Store your refresh token as this can be reused. ({})")) + { + LOGGER.info( + "Authorization successful & retrieved token! Storing the token in {}", + OtherUtil.getPath("youtubetoken.txt").toAbsolutePath() + ); + + try + { + Files.write(OtherUtil.getPath("youtubetoken.txt"), params[0].toString().getBytes()); + } + catch (Exception e) + { + LOGGER.error( + "Failed to write the YouTube OAuth2 refresh token to storage! You will need to authorize again on the next reboot", + e + ); + } + return FilterReply.DENY; + } + + return FilterReply.NEUTRAL; + } + + public static class Data + { + private final String authorisationUrl; + private final String code; + + private Data(String authorisationUrl, String code) + { + this.authorisationUrl = authorisationUrl; + this.code = code; + } + + public String getCode() + { + return code; + } + + public String getAuthorisationUrl() + { + return authorisationUrl; + } + } + +} diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index a4fda1e02..b5564f54e 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -48,6 +48,14 @@ game = "DEFAULT" status = ONLINE +// If you set this to true, the bot will use a Google account to play YouTube tracks. +// This is only needed if you are experiencing issues with YouTube playback. +// Once enabled, instructions for login will be written to the console and DMs. +// DO NOT use your main Google account! Use an alternative account. + +youtubeoauth2=false + + // If you set this to true, the bot will list the title of the song it is currently playing in its // "Playing" status. Note that this will ONLY work if the bot is playing music on ONE guild; // if the bot is playing on multiple guilds, this will not work.