diff --git a/build.gradle b/build.gradle index 7af60d6..1a86264 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,10 @@ dependencies { compile 'mysql:mysql-connector-java' compile 'org.reflections:reflections:0.9.11' compile 'org.apache.commons:commons-lang3:3.7' + compile 'com.google.api-client:google-api-client:1.23.0' + compile 'com.google.apis:google-api-services-youtube:v3-rev192-1.23.0' + compile 'com.google.oauth-client:google-oauth-client-java6:1.23.0' + compile 'com.google.oauth-client:google-oauth-client-jetty:1.23.0' testCompile('org.springframework.boot:spring-boot-starter-test') testCompile('com.jayway.jsonpath:json-path') diff --git a/src/main/java/net/Broken/Commands/Music.java b/src/main/java/net/Broken/Commands/Music.java index 4d5a09b..5df2cba 100644 --- a/src/main/java/net/Broken/Commands/Music.java +++ b/src/main/java/net/Broken/Commands/Music.java @@ -23,7 +23,7 @@ public class Music implements Commande { public AudioM audio; Logger logger = LogManager.getLogger(); public Music() { - audio = new AudioM(MainBot.jda.getGuilds().get(0)); + audio = AudioM.getInstance(MainBot.jda.getGuilds().get(0)); } @Override diff --git a/src/main/java/net/Broken/Commands/Over18/Madame.java b/src/main/java/net/Broken/Commands/Over18/Madame.java index 16f4123..7694492 100644 --- a/src/main/java/net/Broken/Commands/Over18/Madame.java +++ b/src/main/java/net/Broken/Commands/Over18/Madame.java @@ -32,7 +32,7 @@ public class Madame implements Commande{ String url = redirect.get("http://dites.bonjourmadame.fr/random"); logger.debug("URL: "+url); - if(scanPageForTipeee(url)){ + if(scanPageForTipeee(url, logger)){ logger.debug("Advertisement detected! Retry! ("+url+")"); } else{ @@ -82,7 +82,7 @@ public class Madame implements Commande{ * @throws StringIndexOutOfBoundsException * @throws IOException */ - private boolean scanPageForTipeee(String url) throws StringIndexOutOfBoundsException, IOException{ + public static boolean scanPageForTipeee(String url, Logger logger) throws StringIndexOutOfBoundsException, IOException{ String content = FindContentOnWebPage.getSourceUrl(url); String imgClickLink = content.substring(content.indexOf("photo post")); imgClickLink = imgClickLink.substring(imgClickLink.indexOf(" textChannel.sendTyping().queue()); - CommandLoader.load(); //On recupere le l'id serveur @@ -94,9 +94,9 @@ public class Init { dayListener.addListener(new DailyMadame()); dayListener.start(); + logger.debug("-----------------FIN INITIALISATION-----------------"); - jda.getPresence().setGame(Game.of("Statut: Ok!")); } catch (LoginException | InterruptedException | RateLimitedException e) @@ -107,4 +107,12 @@ public class Init { return jda; } + + + static void polish(JDA jda){ + CommandLoader.load(); + ApiCommandLoader.load(); + jda.getPresence().setGame(Game.of("Statut: Ok!")); + + } } diff --git a/src/main/java/net/Broken/MainBot.java b/src/main/java/net/Broken/MainBot.java index c70c56e..10d1ff3 100644 --- a/src/main/java/net/Broken/MainBot.java +++ b/src/main/java/net/Broken/MainBot.java @@ -6,6 +6,7 @@ import net.Broken.Tools.EmbedMessageUtils; import net.Broken.Tools.MessageTimeOut; import net.Broken.Tools.PrivateMessage; import net.Broken.Tools.UserSpamUtils; +import net.Broken.audio.Youtube.YoutubeTools; import net.dv8tion.jda.core.JDA; import net.dv8tion.jda.core.Permission; import net.dv8tion.jda.core.entities.ChannelType; @@ -20,6 +21,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.stereotype.Controller; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -36,6 +38,7 @@ public class MainBot { public static boolean roleFlag = false; public static HashMap spamUtils = new HashMap<>(); public static JDA jda; + public static boolean ready = false; @@ -69,7 +72,7 @@ public class MainBot { i++; } - jda = Init.initBot(token, dev); + jda = Init.initJda(token, dev); ConfigurableApplicationContext ctx = SpringApplication.run(MainBot.class, args); if(jda == null) { System.exit(SpringApplication.exit(ctx, (ExitCodeGenerator) () -> { @@ -78,8 +81,14 @@ public class MainBot { })); } + try { + YoutubeTools.getInstance(jda.getGuilds().get(0)).getYouTubeService(); + } catch (IOException e) { + e.printStackTrace(); + } - ApiCommandLoader.load(); + Init.polish(jda); + ready = true; @@ -95,8 +104,14 @@ public class MainBot { */ public static void handleCommand(CommandParser.CommandContainer cmd) { - //On verifie que la commande existe + if(!ready) + { + + return; + } + + //On verifie que la commande existe if (commandes.containsKey(cmd.commande)) { Commande cmdObj = commandes.get(cmd.commande); @@ -145,13 +160,25 @@ public class MainBot { else { MessageReceivedEvent event = cmd.event; - if(event.isFromType(ChannelType.PRIVATE)) - event.getPrivateChannel().sendMessage(EmbedMessageUtils.getUnknowCommand()).queue(); - else { - Message message = event.getTextChannel().sendMessage(EmbedMessageUtils.getUnknowCommand()).complete(); - new MessageTimeOut(messageTimeOut, message, event.getMessage()); + if(commandes.size() == 0){ + if(event.isFromType(ChannelType.PRIVATE)) + event.getPrivateChannel().sendMessage("Loading please wait...").queue(); + else { + Message message = event.getTextChannel().sendMessage("Loading please wait...").complete(); + new MessageTimeOut(messageTimeOut, message, event.getMessage()); + } } - logger.warn("Commande inconnue"); + else{ + + if(event.isFromType(ChannelType.PRIVATE)) + event.getPrivateChannel().sendMessage(EmbedMessageUtils.getUnknowCommand()).queue(); + else { + Message message = event.getTextChannel().sendMessage(EmbedMessageUtils.getUnknowCommand()).complete(); + new MessageTimeOut(messageTimeOut, message, event.getMessage()); + } + logger.warn("Commande inconnue"); + } + } diff --git a/src/main/java/net/Broken/RestApi/Commands/AutoFlowOff.java b/src/main/java/net/Broken/RestApi/Commands/AutoFlowOff.java new file mode 100644 index 0000000..df55aa3 --- /dev/null +++ b/src/main/java/net/Broken/RestApi/Commands/AutoFlowOff.java @@ -0,0 +1,30 @@ +package net.Broken.RestApi.Commands; + +import net.Broken.Commands.Music; +import net.Broken.RestApi.CommandInterface; +import net.Broken.RestApi.Data.CommandPostData; +import net.Broken.RestApi.Data.CommandResponseData; +import net.Broken.audio.AudioM; +import net.Broken.audio.NotConnectedException; +import net.Broken.audio.NullMusicManager; +import net.Broken.audio.TrackScheduler; +import net.dv8tion.jda.core.entities.User; +import org.apache.logging.log4j.LogManager; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class AutoFlowOff implements CommandInterface{ + + @Override + public ResponseEntity action(Music musicCommande, CommandPostData data, User user) { + AudioM audioM = AudioM.getInstance(null); + try { + TrackScheduler scheduler = audioM.getGuildMusicManager().scheduler; + scheduler.setAutoFlow(false); + return new ResponseEntity<>(new CommandResponseData(data.command,"ok"), HttpStatus.OK); + } catch (NullMusicManager | NotConnectedException nullMusicManager) { + LogManager.getLogger().catching(nullMusicManager); + return new ResponseEntity<>(new CommandResponseData(data.command,"Not connected", "connect"), HttpStatus.INTERNAL_SERVER_ERROR); + } + } +} diff --git a/src/main/java/net/Broken/RestApi/Commands/AutoFlowOn.java b/src/main/java/net/Broken/RestApi/Commands/AutoFlowOn.java new file mode 100644 index 0000000..001e259 --- /dev/null +++ b/src/main/java/net/Broken/RestApi/Commands/AutoFlowOn.java @@ -0,0 +1,31 @@ +package net.Broken.RestApi.Commands; + +import net.Broken.Commands.Music; +import net.Broken.RestApi.CommandInterface; +import net.Broken.RestApi.Data.CommandPostData; +import net.Broken.RestApi.Data.CommandResponseData; +import net.Broken.audio.AudioM; +import net.Broken.audio.NotConnectedException; +import net.Broken.audio.NullMusicManager; +import net.Broken.audio.TrackScheduler; +import net.dv8tion.jda.core.entities.User; +import org.apache.logging.log4j.LogManager; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class AutoFlowOn implements CommandInterface{ + + @Override + public ResponseEntity action(Music musicCommande, CommandPostData data, User user) { + AudioM audioM = AudioM.getInstance(null); + try { + TrackScheduler scheduler = audioM.getGuildMusicManager().scheduler; + scheduler.setAutoFlow(true); + return new ResponseEntity<>(new CommandResponseData(data.command,"ok"), HttpStatus.OK); + } catch (NullMusicManager | NotConnectedException nullMusicManager) { + LogManager.getLogger().catching(nullMusicManager); + return new ResponseEntity<>(new CommandResponseData(data.command,"Not connected", "connect"), HttpStatus.INTERNAL_SERVER_ERROR); + } + + } +} diff --git a/src/main/java/net/Broken/RestApi/Commands/Connect.java b/src/main/java/net/Broken/RestApi/Commands/Connect.java index 42f6975..d581d78 100644 --- a/src/main/java/net/Broken/RestApi/Commands/Connect.java +++ b/src/main/java/net/Broken/RestApi/Commands/Connect.java @@ -16,7 +16,7 @@ import org.springframework.http.ResponseEntity; public class Connect implements CommandInterface{ @Override public ResponseEntity action(Music musicCommande, CommandPostData data, User user) { - AudioM audioM = musicCommande.getAudioManager(); + AudioM audioM = AudioM.getInstance(null); if(data.chanelId == null) return new ResponseEntity<>(new CommandResponseData(data.command,"Missing chanelId"),HttpStatus.BAD_REQUEST); VoiceChannel voiceChannel = null; diff --git a/src/main/java/net/Broken/RestApi/Data/CurrentMusicData.java b/src/main/java/net/Broken/RestApi/Data/CurrentMusicData.java index 9085374..1c77961 100644 --- a/src/main/java/net/Broken/RestApi/Data/CurrentMusicData.java +++ b/src/main/java/net/Broken/RestApi/Data/CurrentMusicData.java @@ -14,13 +14,15 @@ public class CurrentMusicData { private final long currentPos; private final String state; private final boolean pause; + private final boolean autoflow; - public CurrentMusicData(UserAudioTrackData info, long currentPos, String state, boolean pause) { + public CurrentMusicData(UserAudioTrackData info, long currentPos, String state, boolean pause, boolean autoflow) { this.info = info; this.currentPos = currentPos; this.state = state; this.pause = pause; + this.autoflow = autoflow; } public UserAudioTrackData getInfo() { @@ -37,4 +39,8 @@ public class CurrentMusicData { else return state; } + + public boolean isAutoflow() { + return autoflow; + } } diff --git a/src/main/java/net/Broken/RestApi/GeneralApiController.java b/src/main/java/net/Broken/RestApi/GeneralApiController.java new file mode 100644 index 0000000..ae994bc --- /dev/null +++ b/src/main/java/net/Broken/RestApi/GeneralApiController.java @@ -0,0 +1,23 @@ +package net.Broken.RestApi; + +import net.Broken.MainBot; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/") +public class GeneralApiController { + @RequestMapping(value = "/isReady", method = RequestMethod.GET) + public ResponseEntity isReady(){ + if(MainBot.ready){ + return new ResponseEntity<>(HttpStatus.OK); + } + else + { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + } +} diff --git a/src/main/java/net/Broken/RestApi/MusicWebAPIController.java b/src/main/java/net/Broken/RestApi/MusicWebAPIController.java index 4b35b96..056457d 100644 --- a/src/main/java/net/Broken/RestApi/MusicWebAPIController.java +++ b/src/main/java/net/Broken/RestApi/MusicWebAPIController.java @@ -51,16 +51,16 @@ public class MusicWebAPIController { AudioTrack currentTrack = player.getPlayingTrack(); if(currentTrack == null) { - return new CurrentMusicData(null,0, "STOP",false); + return new CurrentMusicData(null,0, "STOP",false, musicCommande.audio.getGuildMusicManager().scheduler.isAutoFlow()); } UserAudioTrackData uat = new UserAudioTrackData(musicCommande.audio.getGuildMusicManager().scheduler.getCurrentPlayingTrack()); - return new CurrentMusicData(uat, currentTrack.getPosition(), currentTrack.getState().toString(), player.isPaused()); + return new CurrentMusicData(uat, currentTrack.getPosition(), currentTrack.getState().toString(), player.isPaused(), musicCommande.audio.getGuildMusicManager().scheduler.isAutoFlow()); } catch (NullMusicManager | NotConnectedException nullMusicManager) { - return new CurrentMusicData(null,0, "STOP",false); + return new CurrentMusicData(null,0, "STOP",false, false); } }else { - return new CurrentMusicData(null,0, "DISCONNECTED",false); + return new CurrentMusicData(null,0, "DISCONNECTED",false, false); } } diff --git a/src/main/java/net/Broken/Tools/DayListener/Listeners/DailyMadame.java b/src/main/java/net/Broken/Tools/DayListener/Listeners/DailyMadame.java index 212c99b..27238e3 100644 --- a/src/main/java/net/Broken/Tools/DayListener/Listeners/DailyMadame.java +++ b/src/main/java/net/Broken/Tools/DayListener/Listeners/DailyMadame.java @@ -1,5 +1,6 @@ package net.Broken.Tools.DayListener.Listeners; +import net.Broken.Commands.Over18.Madame; import net.Broken.MainBot; import net.Broken.Tools.DayListener.NewDayListener; import net.Broken.Tools.Redirection; @@ -24,8 +25,16 @@ public class DailyMadame implements NewDayListener{ while(!success && !error) { try { - chanel.sendMessage("Le Daily Madame mes petits cochons :kissing_heart:\n" + redirect.get("http://dites.bonjourmadame.fr/random")).queue(); - success=true; + + String url = redirect.get("http://dites.bonjourmadame.fr/random"); + logger.debug("URL: "+url); + if(Madame.scanPageForTipeee(url, logger)){ + logger.debug("Advertisement detected! Retry! ("+url+")"); + } + else{ + chanel.sendMessage("Le Daily Madame mes petits cochons :kissing_heart:\n" + url).queue(); + success=true; + } } catch (IOException e) { errorCp++; logger.warn("Erreur de redirection. (Essais n°"+errorCp+")"); diff --git a/src/main/java/net/Broken/audio/AudioM.java b/src/main/java/net/Broken/audio/AudioM.java index 14c4b9f..1b85475 100644 --- a/src/main/java/net/Broken/audio/AudioM.java +++ b/src/main/java/net/Broken/audio/AudioM.java @@ -52,10 +52,19 @@ public class AudioM { private Guild guild; private Logger logger = LogManager.getLogger(); + private static AudioM INSTANCE; + + public static AudioM getInstance(Guild guild){ + if(INSTANCE == null){ + INSTANCE = new AudioM(guild); + } + + return INSTANCE; + } - public AudioM(Guild guild) { + private AudioM(Guild guild) { this.playerManager = new DefaultAudioPlayerManager(); AudioSourceManagers.registerRemoteSources(playerManager); AudioSourceManagers.registerLocalSource(playerManager); @@ -80,11 +89,7 @@ public class AudioM { logger.info("Single Track detected!"); UserAudioTrack uat = new UserAudioTrack(event.getAuthor(), track); Message message = event.getTextChannel().sendMessage(EmbedMessageUtils.getMusicOk("Ajout de "+track.getInfo().title+" à la file d'attente!")).complete(); - List messages = new ArrayList(){{ - add(message); - add(event.getMessage()); - }}; - new MessageTimeOut(messages, MainBot.messageTimeOut).start(); + new MessageTimeOut(MainBot.messageTimeOut, message, event.getMessage()).start(); play(guild, voiceChannel, musicManager, uat, onHead); } @@ -95,11 +100,7 @@ public class AudioM { AudioTrack firstTrack = playlist.getSelectedTrack(); Message message = event.getTextChannel().sendMessage(EmbedMessageUtils.getMusicOk("Ajout de "+firstTrack.getInfo().title+" et les 30 premiers titres à la file d'attente!")).complete(); - List messages = new ArrayList(){{ - add(message); - add(event.getMessage()); - }}; - new MessageTimeOut(messages, MainBot.messageTimeOut).start(); + new MessageTimeOut(MainBot.messageTimeOut, message, event.getMessage()).start(); playListLoader(playlist, playlistLimit ,event.getAuthor() , onHead); @@ -132,6 +133,37 @@ public class AudioM { }); } + public void loadAndPlayAuto(String trackUrl){ + playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler(){ + @Override + public void trackLoaded(AudioTrack track) { + logger.info("Auto add " + track.getInfo().title +" to playlist."); + UserAudioTrack userAudioTrack = new UserAudioTrack(MainBot.jda.getSelfUser(),track); + play(guild, playedChanel, musicManager, userAudioTrack, true); + } + + @Override + public void playlistLoaded(AudioPlaylist playlist) { + AudioTrack track = playlist.getTracks().get(0); + logger.info("Auto add " + track.getInfo().title +" to playlist."); + UserAudioTrack userAudioTrack = new UserAudioTrack(MainBot.jda.getSelfUser(),track); + play(guild, playedChanel, musicManager, userAudioTrack, true); + } + + @Override + public void noMatches() { + logger.warn("Track not found: "+trackUrl); + } + + @Override + public void loadFailed(FriendlyException exception) { + logger.error("Cant load media!"); + logger.error(exception.getMessage()); + } + }); + } + + /** * Load playlist to playlist * @param playlist Loaded playlist @@ -270,7 +302,7 @@ public class AudioM { */ public void add(MessageReceivedEvent event, String url, int playListLimit, boolean onHead) { if(playedChanel != null){ - loadAndPlay(event,playedChanel, url, playListLimit,onHead); + loadAndPlay(event ,playedChanel, url, playListLimit,onHead); } else { @@ -320,6 +352,7 @@ public class AudioM { public AudioPlayerManager getPlayerManager() { return playerManager; } + public VoiceChannel getPlayedChanel() { return playedChanel; } diff --git a/src/main/java/net/Broken/audio/TrackScheduler.java b/src/main/java/net/Broken/audio/TrackScheduler.java index 08a96eb..ebbf48c 100644 --- a/src/main/java/net/Broken/audio/TrackScheduler.java +++ b/src/main/java/net/Broken/audio/TrackScheduler.java @@ -1,11 +1,14 @@ package net.Broken.audio; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; import com.sedmelluq.discord.lavaplayer.track.AudioTrackInfo; +import net.Broken.MainBot; import net.Broken.RestApi.Data.UserAudioTrackData; +import net.Broken.audio.Youtube.YoutubeTools; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -21,6 +24,8 @@ public class TrackScheduler extends AudioEventAdapter { private final AudioPlayer player; private final BlockingDeque queue; private UserAudioTrack currentPlayingTrack; + private boolean autoFlow = false; + private ArrayList history = new ArrayList<>(); Logger logger = LogManager.getLogger(); /** @@ -42,12 +47,23 @@ public class TrackScheduler extends AudioEventAdapter { // Calling startTrack with the noInterrupt set to true will start the track only if nothing is currently playing. If // something is playing, it returns false and does nothing. In that case the player was already playing so this // track goes to the queue instead. + if(track.getSubmittedUser() != MainBot.jda.getSelfUser()){ + logger.debug("Flush history"); + history = new ArrayList<>(); + } + + history.add(track.getAudioTrack().getIdentifier()); if (!player.startTrack(track.getAudioTrack(), true)) { queue.offer(track); } else{ currentPlayingTrack = track; } + if(track.getSubmittedUser() != MainBot.jda.getSelfUser()) { + needAutoPlay(); + } + + } /** @@ -58,15 +74,23 @@ public class TrackScheduler extends AudioEventAdapter { // Calling startTrack with the noInterrupt set to true will start the track only if nothing is currently playing. If // something is playing, it returns false and does nothing. In that case the player was already playing so this // track goes to the queue instead. + if(track.getSubmittedUser() != MainBot.jda.getSelfUser()){ + logger.debug("Flush history"); + history = new ArrayList<>(); + } + + history.add(track.getAudioTrack().getIdentifier()); if (!player.startTrack(track.getAudioTrack(), true)) { queue.addFirst(track); } else{ currentPlayingTrack = track; } + if(track.getSubmittedUser() != MainBot.jda.getSelfUser()) { + needAutoPlay(); + } } - public void pause() { player.setPaused(true); } @@ -76,7 +100,6 @@ public class TrackScheduler extends AudioEventAdapter { } - public void stop(){ player.stopTrack(); this.currentPlayingTrack = null; @@ -115,11 +138,13 @@ public class TrackScheduler extends AudioEventAdapter { return false; } else { logger.info("Delete succeful"); + needAutoPlay(); return true; } } } logger.info("Delete failure! Not found."); + return false; } @@ -130,9 +155,11 @@ public class TrackScheduler extends AudioEventAdapter { // Start the next track, regardless of if something is already playing or not. In case queue was empty, we are // giving null to startTrack, which is a valid argument and will simply stop the player. UserAudioTrack track = queue.poll(); - this.currentPlayingTrack = track; + if(track != null) + this.currentPlayingTrack = track; if(track != null) player.startTrack(track.getAudioTrack(), false); + needAutoPlay(); } @Override @@ -140,8 +167,35 @@ public class TrackScheduler extends AudioEventAdapter { // Only start the next track if the end reason is suitable for it (FINISHED or LOAD_FAILED) if (endReason.mayStartNext) { nextTrack(); + needAutoPlay(); } } + private void needAutoPlay(){ + if((queue.size() < 1) && autoFlow && currentPlayingTrack != null){ + logger.info("Auto add needed!"); + AudioM audioM = AudioM.getInstance(null); + YoutubeTools youtubeTools = YoutubeTools.getInstance(null); + try { + String id = youtubeTools.getRelatedVideo(currentPlayingTrack.getAudioTrack().getInfo().identifier, history); + logger.info("Related id: "+id); + audioM.loadAndPlayAuto(id); + } catch (GoogleJsonResponseException e) { + logger.error("There was a service error: " + e.getDetails().getCode() + " : " + e.getDetails().getMessage()); + } catch (Throwable t) { + logger.catching(t); + } + + } + } + + public void setAutoFlow(boolean autoFlow) { + this.autoFlow = autoFlow; + needAutoPlay(); + } + + public boolean isAutoFlow() { + return autoFlow; + } } diff --git a/src/main/java/net/Broken/audio/WebLoadUtils.java b/src/main/java/net/Broken/audio/WebLoadUtils.java index 8608d6a..e1cdb95 100644 --- a/src/main/java/net/Broken/audio/WebLoadUtils.java +++ b/src/main/java/net/Broken/audio/WebLoadUtils.java @@ -31,7 +31,7 @@ public class WebLoadUtils { AudioPlayerManager playerM = musicCommand.getAudioManager().getPlayerManager(); try { - AudioM audioM = musicCommand.getAudioManager(); + AudioM audioM = AudioM.getInstance(null); playerM.loadItemOrdered(musicCommand.getAudioManager().getGuildMusicManager(), data.url, new AudioLoadResultHandler() { @Override public void trackLoaded(AudioTrack track) { diff --git a/src/main/java/net/Broken/audio/Youtube/Authorization.java b/src/main/java/net/Broken/audio/Youtube/Authorization.java new file mode 100644 index 0000000..3d7f6f1 --- /dev/null +++ b/src/main/java/net/Broken/audio/Youtube/Authorization.java @@ -0,0 +1,41 @@ +package net.Broken.audio.Youtube; + +import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; +import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.java6.auth.oauth2.VerificationCodeReceiver; +import com.google.api.client.util.Preconditions; +import net.Broken.Tools.PrivateMessage; +import net.dv8tion.jda.core.entities.Guild; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; + +public class Authorization extends AuthorizationCodeInstalledApp { + + private Guild guild; + private Logger logger = LogManager.getLogger(); + + /** + * @param flow authorization code flow + * @param receiver verification code receiver + */ + public Authorization(AuthorizationCodeFlow flow, VerificationCodeReceiver receiver, Guild guild) { + super(flow, receiver); + this.guild = guild; + } + + @Override + protected void onAuthorization(AuthorizationCodeRequestUrl authorizationUrl) throws IOException { + notify(authorizationUrl.build()); + } + + protected void notify(String url){ + Preconditions.checkNotNull(url); + logger.fatal("Please open this URL: "+url); + PrivateMessage.send(guild.getOwner().getUser(),"Please open this url to confirm google api account acces : " + url,null); + + + } +} diff --git a/src/main/java/net/Broken/audio/Youtube/Receiver.java b/src/main/java/net/Broken/audio/Youtube/Receiver.java new file mode 100644 index 0000000..ba623fd --- /dev/null +++ b/src/main/java/net/Broken/audio/Youtube/Receiver.java @@ -0,0 +1,54 @@ +package net.Broken.audio.Youtube; + +import com.google.api.client.extensions.java6.auth.oauth2.AbstractPromptReceiver; +import net.dv8tion.jda.core.entities.Guild; +import org.apache.logging.log4j.LogManager; + +import java.io.IOException; + +public class Receiver extends AbstractPromptReceiver { + + private static Receiver INSTANCE; + private Guild guild; + private String code; + + private Receiver(Guild guild){ + this.guild = guild; + } + + public static Receiver getInstance(Guild guild){ + if(INSTANCE == null) + INSTANCE = new Receiver(guild); + return INSTANCE; + } + + + @Override + public String getRedirectUri() throws IOException { + return System.getenv("SITE_URL") + "/youtube/callback/"; + } + + @Override + public String waitForCode() { + if(System.getenv("SITE_URL").isEmpty()){ + LogManager.getLogger().fatal("Please set \"SITE_URL\" environment variable and restart the bot!"); + } + while(code == null){ + try { + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return code; + } + + @Override + public void stop() { + + } + + public void setCode(String code) { + this.code = code; + } +} diff --git a/src/main/java/net/Broken/audio/Youtube/YoutubeTools.java b/src/main/java/net/Broken/audio/Youtube/YoutubeTools.java new file mode 100644 index 0000000..600f2e5 --- /dev/null +++ b/src/main/java/net/Broken/audio/Youtube/YoutubeTools.java @@ -0,0 +1,143 @@ +package net.Broken.audio.Youtube; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.googleapis.json.GoogleJsonResponseException; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.store.FileDataStoreFactory; +import com.google.api.services.youtube.YouTube; +import com.google.api.services.youtube.YouTubeScopes; +import com.google.api.services.youtube.model.SearchListResponse; +import com.google.api.services.youtube.model.SearchResult; +import net.dv8tion.jda.core.entities.Guild; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.*; +import java.util.*; + +public class YoutubeTools { + /** Application name. */ + private final String APPLICATION_NAME = "Discord Bot"; + + /** Directory to store user credentials for this application. */ + private final File DATA_STORE_DIR = new File(".credentials/java-youtube-api"); + private final File CLIENT_SECRET_DIR = new File(".credentials/client_secret.json"); + + /** Global instance of the {@link FileDataStoreFactory}. */ + private FileDataStoreFactory DATA_STORE_FACTORY; + + /** Global instance of the JSON factory. */ + private final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance(); + + /** Global instance of the HTTP transport. */ + private HttpTransport HTTP_TRANSPORT; + + private Logger logger = LogManager.getLogger(); + + private Guild guild; + + /** Global instance of the scopes required by this quickstart. + * + * If modifying these scopes, delete your previously saved credentials + * at ~/.credentials/drive-java-quickstart + */ + private final Collection SCOPES = Arrays.asList(YouTubeScopes.YOUTUBEPARTNER, YouTubeScopes.YOUTUBE_FORCE_SSL); + + private static YoutubeTools INSTANCE ; + + private YoutubeTools(Guild guild){ + + try { + HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); + DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR); + } catch (Throwable t) { + logger.catching(t); + } + this.guild = guild; + } + + public static YoutubeTools getInstance(Guild guild){ + if(INSTANCE == null) + INSTANCE = new YoutubeTools(guild); + return INSTANCE; + } + + + + /** + * Creates an authorized Credential object. + * @return an authorized Credential object. + * @throws IOException + */ + private Credential authorize() throws IOException { + // Load client secrets. + InputStream in = new FileInputStream(CLIENT_SECRET_DIR); + GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader( in )); + + + // Build flow and trigger user authorization request. + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( + HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) + .setDataStoreFactory(DATA_STORE_FACTORY) + .setAccessType("online") + .build(); + + + + Credential credential = new Authorization(flow, Receiver.getInstance(null), guild).authorize("user"); + logger.debug("Credentials saved to " + DATA_STORE_DIR.getAbsolutePath()); + return credential; + } + + /** + * Build and return an authorized API client service, such as a YouTube + * Data API client service. + * @return an authorized API client service + * @throws IOException + */ + public YouTube getYouTubeService() throws IOException { + Credential credential = authorize(); + return new YouTube.Builder( + HTTP_TRANSPORT, JSON_FACTORY, credential) + .setApplicationName(APPLICATION_NAME) + .build(); + } + + public String getRelatedVideo(String videoId, ArrayList history) throws IOException, GoogleJsonResponseException, Throwable { + + YouTube youtube = getYouTubeService(); + + HashMap parameters = new HashMap<>(); + parameters.put("part", "snippet"); + parameters.put("relatedToVideoId", videoId); + parameters.put("type", "video"); + + YouTube.Search.List searchListRelatedVideosRequest = youtube.search().list(parameters.get("part")); + if (parameters.containsKey("relatedToVideoId") && parameters.get("relatedToVideoId") != "") { + searchListRelatedVideosRequest.setRelatedToVideoId(parameters.get("relatedToVideoId")); + } + + if (parameters.containsKey("type") && !parameters.get("type").equals("")) { + searchListRelatedVideosRequest.setType(parameters.get("type")); + } + + SearchListResponse response = searchListRelatedVideosRequest.execute(); + + for(SearchResult item : response.getItems()){ + if(!history.contains(item.getId().getVideoId())){ + return item.getId().getVideoId(); + } + else + logger.debug("ID already on history"); + } + + logger.debug("All on history ?"); + return response.getItems().get(0).getId().getVideoId(); + + } +} diff --git a/src/main/java/net/Broken/webView/CheckPage.java b/src/main/java/net/Broken/webView/CheckPage.java new file mode 100644 index 0000000..a54d731 --- /dev/null +++ b/src/main/java/net/Broken/webView/CheckPage.java @@ -0,0 +1,12 @@ +package net.Broken.webView; + +import net.Broken.MainBot; + +public class CheckPage { + public static String getPageIfReady(String page){ + if(MainBot.ready) + return page; + else + return "loading"; + } +} diff --git a/src/main/java/net/Broken/webView/GeneralWebView.java b/src/main/java/net/Broken/webView/GeneralWebView.java index 34730ec..0f7287d 100644 --- a/src/main/java/net/Broken/webView/GeneralWebView.java +++ b/src/main/java/net/Broken/webView/GeneralWebView.java @@ -11,6 +11,11 @@ import org.springframework.web.bind.annotation.RequestMapping; public class GeneralWebView { @RequestMapping("/") public String music(Model model){ - return "index"; + + return CheckPage.getPageIfReady("index"); + } + @RequestMapping("/loading") + public String loading(Model model){ + return "loading"; } } diff --git a/src/main/java/net/Broken/webView/MusicWebView.java b/src/main/java/net/Broken/webView/MusicWebView.java index 73d203b..753aa35 100644 --- a/src/main/java/net/Broken/webView/MusicWebView.java +++ b/src/main/java/net/Broken/webView/MusicWebView.java @@ -11,6 +11,6 @@ import org.springframework.web.bind.annotation.RequestMapping; public class MusicWebView { @RequestMapping("/music") public String music(Model model){ - return "music"; + return CheckPage.getPageIfReady("music"); } } diff --git a/src/main/java/net/Broken/webView/RegisterWebView.java b/src/main/java/net/Broken/webView/RegisterWebView.java index 537775c..49e403b 100644 --- a/src/main/java/net/Broken/webView/RegisterWebView.java +++ b/src/main/java/net/Broken/webView/RegisterWebView.java @@ -14,6 +14,6 @@ public class RegisterWebView { @RequestMapping("/register") public String music(@RequestParam(value="id", required = true, defaultValue = "") String id, Model model){ model.addAttribute("id", id); - return "register"; + return CheckPage.getPageIfReady("register"); } } diff --git a/src/main/java/net/Broken/webView/YoutubeCallBack.java b/src/main/java/net/Broken/webView/YoutubeCallBack.java new file mode 100644 index 0000000..0194b06 --- /dev/null +++ b/src/main/java/net/Broken/webView/YoutubeCallBack.java @@ -0,0 +1,24 @@ +package net.Broken.webView; + + +import net.Broken.audio.Youtube.Receiver; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; + +@Controller +public class YoutubeCallBack { + @RequestMapping("/youtube/callback") + public String callback(@RequestParam(value="error", required = false, defaultValue = "") String error, + @RequestParam(value = "code", required = false, defaultValue = "") String code, + Model model){ + model.addAttribute("error", error); + model.addAttribute("code", code); + if(!code.equals("")){ + Receiver.getInstance(null).setCode(code); + } + + return "youtubeCallBack"; + } +} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 467272d..36cd124 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -25,8 +25,12 @@ + + + + + - diff --git a/src/main/resources/static/error/404.html b/src/main/resources/static/error/404.html new file mode 100644 index 0000000..25decd3 --- /dev/null +++ b/src/main/resources/static/error/404.html @@ -0,0 +1,153 @@ + + + + + + Music Control - Discord Bot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+

Oops!

+

Page not found!

+ + home + +
+
+ +
+
+
+ +
+ +
+
+ + + + + + + + + + diff --git a/src/main/resources/static/img/404.gif b/src/main/resources/static/img/404.gif new file mode 100644 index 0000000..e0a1d6e Binary files /dev/null and b/src/main/resources/static/img/404.gif differ diff --git a/src/main/resources/static/js/loading.js b/src/main/resources/static/js/loading.js new file mode 100644 index 0000000..6ce032e --- /dev/null +++ b/src/main/resources/static/js/loading.js @@ -0,0 +1,18 @@ +$(document).ready(function () { + setInterval("loop()",1000); +}); + +function loop() { + $.ajax({ + type: "GET", + url: "/api/isReady", + success: function (data) { + console.log("Ready"); + debugger; + location.reload(); + } + + }).fail(function (data) { + console.log("Not ready"); + }); +} \ No newline at end of file diff --git a/src/main/resources/static/js/music.js b/src/main/resources/static/js/music.js index 3e60bda..2846513 100644 --- a/src/main/resources/static/js/music.js +++ b/src/main/resources/static/js/music.js @@ -10,7 +10,8 @@ var btn_info; var btn_disconnect; var btn_flush; var btn_add; - +var switchAutoFlow; +var loadingFlag = false; $(document).ready(function() { @@ -21,7 +22,7 @@ $(document).ready(function() { btn_disconnect = $('#btn_disconnect'); btn_flush = $('#flush_btn'); btn_add = $('#add_btn'); - + switchAutoFlow = $("#autoflow"); setInterval("getCurentMusic()",1000); $('#modalAdd').modal(); @@ -160,7 +161,10 @@ function getCurentMusic() { break; } + if(switchAutoFlow.is(':checked') != data.autoflow) + switchAutoFlow.prop('checked', data.autoflow); getPlayList(); + }) .fail(function (data) { if(!error){ @@ -177,7 +181,7 @@ function getPlayList() { data = data.list; if(data != null && data.length != 0){ var noUpdate = comparePlaylist(data, savedPlaylist); - + // console.log("List up to date : "+noUpdate); if(!noUpdate){ savedPlaylist = data; $('#playlist_list').empty(); @@ -208,13 +212,25 @@ function getPlayList() { }); } } - else + else{ $('#playlist_list').empty(); + savedPlaylist = {}; + } + if(loadingFlag){ + modal_loading.modal('close'); + loadingFlag = false; + } + + }).fail(function (data) { if(!error){ alert("Comunication error, please refresh."); error = true; } + if(loadingFlag){ + modal_loading.modal('close'); + loadingFlag = false; + } }); @@ -249,7 +265,7 @@ function getChannels(){ function updateModal(data){ $('#modal_title').text("Title: "+ data.info.audioTrackInfo.title); - $('#modal_author').text("Author: "+ data.info.author); + $('#modal_author').text("Author: "+ data.info.audioTrackInfo.author); $('#modal_lenght').text("Duration: "+ msToTime(data.info.audioTrackInfo.length)); $('#modal_url').text("URL: "+ data.info.audioTrackInfo.uri); $('#modal_submit').text("Submitted by: "+ data.info.user); @@ -307,8 +323,9 @@ function sendCommand(command){ data: JSON.stringify(command), success: function (data) { console.log(data); + loadingFlag = true; getCurentMusic(); - modal_loading.modal('close'); + } }).fail(function (data) { @@ -437,6 +454,15 @@ function listeners() { $('#btn_disconnect').click(function () { sendCommand({command : "DISCONNECT"}) }); + + switchAutoFlow.click(function () { + console.log(switchAutoFlow.is(':checked')) + if(switchAutoFlow.is(':checked')){ + sendCommand({command: 'AUTOFLOWON'}) + } + else + sendCommand({command: 'AUTOFLOWOFF'}) + }); } function disableBtn(btn) { diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 453783a..ae77a64 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -124,8 +124,9 @@ - - + diff --git a/src/main/resources/templates/loading.html b/src/main/resources/templates/loading.html new file mode 100644 index 0000000..3fe4c7e --- /dev/null +++ b/src/main/resources/templates/loading.html @@ -0,0 +1,79 @@ + + + + + + Music Control - Discord Bot + + + + + + + + + + + + + + + + + + + + + + + +
+
+

Bot is starting

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Please wait

+
+ +
+ + + + + + + + + + diff --git a/src/main/resources/templates/music.html b/src/main/resources/templates/music.html index d0c1bdc..65e2198 100644 --- a/src/main/resources/templates/music.html +++ b/src/main/resources/templates/music.html @@ -174,11 +174,11 @@
-
Playlist
-
+
Playlist
+ -
+
add_circle_outline @@ -215,6 +215,17 @@
+
+
+ AutoFlow +
+
+ +
+
@@ -360,7 +371,9 @@

- + + diff --git a/src/main/resources/templates/youtubeCallBack.html b/src/main/resources/templates/youtubeCallBack.html new file mode 100644 index 0000000..448b551 --- /dev/null +++ b/src/main/resources/templates/youtubeCallBack.html @@ -0,0 +1,141 @@ + + + + + + Music Control - Discord Bot + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+

+

+
+
+
+ + + + + + + + + +