From af037f7eb73b99c0986cfb156f74ddf3cdeb0578 Mon Sep 17 00:00:00 2001 From: Isaac Parenteau Date: Tue, 8 Oct 2019 22:25:44 -0500 Subject: [PATCH 1/5] #6 Initial work on implementing playlists --- .../repos/GuildPlaylistRepository.java | 48 +++++++++ .../repos/GuildPlaylistSongRepository.java | 42 ++++++++ .../handlers/GuildMusicHandler.java | 98 +++++++++++++------ .../services/GuildSongRepoService.java | 22 +++++ 4 files changed, 180 insertions(+), 30 deletions(-) create mode 100644 src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistRepository.java create mode 100644 src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistSongRepository.java diff --git a/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistRepository.java b/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistRepository.java new file mode 100644 index 0000000..cd04a09 --- /dev/null +++ b/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistRepository.java @@ -0,0 +1,48 @@ +/** + * + * Project: Eight Track, File: GuildSongRepository.java + * + * Copyright 2019-2019 Locusworks LLC. + * All rights reserved. Federal copyright law prohibits unauthorized reproduction by + * any means and imposes fines up to $25,000 for violation. No part of this material + * may be reproduced, transmitted, transcribed, stored in a retrieval system, copied, + * modified, duplicated, adapted or translated into another program language in any + * form or by any means, electronic, mechanical, photocopying, recording, or + * otherwise, without the prior written permission from Locusworks. Locusworks + * affirms that Eight-Track(R) software and data is subject to United States + * Government Purpose Rights. Contact Locusworks, 1313 Lawnview Drive + * Forney TX 75126, (802) 488-0438, for commercial licensing opportunities. + * + * IN NO EVENT SHALL LOCUSWORKS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, + * INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT + * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF LOCUSWORKS HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NO RESPONSIBILITY IS ASSUMED BY + * LOCUSWORKS FOR ITS USE, OR FOR ANY INFRINGEMENTS OF PATENTS OR OTHER RIGHTS OF + * THIRD PARTIES RESULTING FROM ITS USE. LOCUSWORKS SPECIFICALLY DISCLAIMS ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND + * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS + * IS". LOCUSWORKS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + */ +package net.locusworks.discord.eighttrack.database.repos; + +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; + +import net.locusworks.discord.eighttrack.database.entities.DiscordGuild; +import net.locusworks.discord.eighttrack.database.entities.GuildPlaylist; + +public interface GuildPlaylistRepository extends CrudRepository { + + GuildPlaylist findByGuildAndUserIdAndPlaylist(DiscordGuild guild, Long userId, String playlist); + + @Query("SELECT gpl FROM GuildPlaylist gpl WHERE gpl.guild.guildId = ?1 AND gpl.userId = ?2 AND gpl.playlist = ?3 ") + GuildPlaylist findByGuildAndUserIdAndPlaylist(Long guild, Long userId, String playlist); + + GuildPlaylist findByGuildAndUserId(DiscordGuild guild, Long userId); + + @Query("SELECT gpl FROM GuildPlaylist gpl WHERE gpl.guild.guildId = ?1 AND gpl.userId = ?2 AND gpl.playlist = ?3 ") + GuildPlaylist findByGuildAndUserId(Long guild, Long userId); + +} diff --git a/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistSongRepository.java b/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistSongRepository.java new file mode 100644 index 0000000..a9d5581 --- /dev/null +++ b/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistSongRepository.java @@ -0,0 +1,42 @@ +/** + * + * Project: Eight Track, File: GuildSongRepository.java + * + * Copyright 2019-2019 Locusworks LLC. + * All rights reserved. Federal copyright law prohibits unauthorized reproduction by + * any means and imposes fines up to $25,000 for violation. No part of this material + * may be reproduced, transmitted, transcribed, stored in a retrieval system, copied, + * modified, duplicated, adapted or translated into another program language in any + * form or by any means, electronic, mechanical, photocopying, recording, or + * otherwise, without the prior written permission from Locusworks. Locusworks + * affirms that Eight-Track(R) software and data is subject to United States + * Government Purpose Rights. Contact Locusworks, 1313 Lawnview Drive + * Forney TX 75126, (802) 488-0438, for commercial licensing opportunities. + * + * IN NO EVENT SHALL LOCUSWORKS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, + * INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT + * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF LOCUSWORKS HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NO RESPONSIBILITY IS ASSUMED BY + * LOCUSWORKS FOR ITS USE, OR FOR ANY INFRINGEMENTS OF PATENTS OR OTHER RIGHTS OF + * THIRD PARTIES RESULTING FROM ITS USE. LOCUSWORKS SPECIFICALLY DISCLAIMS ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND + * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS + * IS". LOCUSWORKS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + */ +package net.locusworks.discord.eighttrack.database.repos; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +import net.locusworks.discord.eighttrack.database.entities.GuildPlaylist; +import net.locusworks.discord.eighttrack.database.entities.GuildPlaylistSong; + +public interface GuildPlaylistSongRepository extends CrudRepository { + + List findByGuildPlaylist(GuildPlaylist gpl); + + +} diff --git a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java index 3712755..b9b6745 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java +++ b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java @@ -58,6 +58,7 @@ import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.api.managers.AudioManager; import net.locusworks.crypto.utils.HashUtils; import net.locusworks.discord.eighttrack.database.entities.DiscordGuild; +import net.locusworks.discord.eighttrack.database.entities.GuildPlaylist; import net.locusworks.discord.eighttrack.database.entities.GuildSong; import net.locusworks.discord.eighttrack.database.entities.Song; import net.locusworks.discord.eighttrack.scheduler.TrackScheduler; @@ -98,11 +99,11 @@ public class GuildMusicHandler { this.ts = new TrackScheduler(); player.addListener(ts); } - + public Long getCurrentVoiceChannelId() { return voiceChannelId; } - + public OffsetDateTime getLastPlayed() { return lastPlayed; } @@ -114,7 +115,7 @@ public class GuildMusicHandler { public void isPlaying(boolean playing) { this.playing.set(playing); } - + public void upNext(GuildMessageReceivedEvent event) throws Exception { upNext(event, null); } @@ -152,7 +153,7 @@ public class GuildMusicHandler { } }); } - + public void next(GuildMessageReceivedEvent event) throws Exception { next(event, null); } @@ -181,14 +182,14 @@ public class GuildMusicHandler { player.stopTrack(); voiceChannelId = null; } - + public void play(GuildMessageReceivedEvent event) throws Exception { play(event, null); } public void play(GuildMessageReceivedEvent event, List commands) throws Exception { if (playing.get()) return; - + VoiceChannel vc = event.getMember().getVoiceState().getChannel(); if (vc == null) { event.getChannel().sendMessage(String.format("<@%s> you are not in a voice channel to play music", event.getMember().getId())).queue(); @@ -200,7 +201,7 @@ public class GuildMusicHandler { event.getMember().getId(), vc.getName())).queue(); return; } - + Long tmpId = vc.getIdLong(); if (voiceChannelId != null && voiceChannelId != tmpId) { String channelName = event.getGuild().getVoiceChannelById(voiceChannelId).getName(); @@ -208,14 +209,14 @@ public class GuildMusicHandler { event.getMember().getId(), channelName)).queue(); return; } - + voiceChannelId = tmpId; AudioManager manager = event.getGuild().getAudioManager(); manager.openAudioConnection(vc); manager.setSendingHandler(new EightTrackAudioSendHandler(player)); - + if (commands != null && !commands.isEmpty()) { String uuid = commands.remove(0); boolean foundSong = findSong(uuid); @@ -225,7 +226,7 @@ public class GuildMusicHandler { } stop(event); } - + if (!ts.hasTracks()) { loadRandomSong(); } @@ -285,7 +286,7 @@ public class GuildMusicHandler { }); } } - + private MessageEmbed error(GuildMessageReceivedEvent event, Throwable ex, String fileName) { return new EmbedBuilder() .setTitle("Unable to upload file: " + fileName) @@ -348,9 +349,9 @@ public class GuildMusicHandler { gs.setDateAdded(new Date()); gs.setGuild(guild); gs.setSong(song); - + String[] uuidArray = UUID.randomUUID().toString().split("-"); - + gs.setUuid(uuidArray[uuidArray.length - 1]); guildSongRepoService.getGuildSongRepo().save(gs); @@ -373,71 +374,108 @@ public class GuildMusicHandler { return embed; } - + public void list(GuildMessageReceivedEvent event) { List gsList = guildSongRepoService.getGuildSongRepo().findByGuild(guildId); - + if (gsList == null || gsList.isEmpty()) { event.getChannel().sendMessage("There is no music for this guild.").queue(); return; } - + int longestSong = 0; int longestArtist = 0; for(GuildSong gs : gsList) { if (gs.getSong().getTitle().length() > longestSong) longestSong = gs.getSong().getTitle().length(); if (gs.getSong().getArtist().length() > longestArtist) longestArtist = gs.getSong().getArtist().length(); } - + String fmt = "%6s | %-" + longestSong +"s | %-" + longestArtist +"s | %s%n"; int count = 0; StringBuilder sb = new StringBuilder(); sb.append("**" + "Currently available songs for " + event.getGuild().getName() + "**\n\n```"); sb.append(String.format(fmt, "Track", "Title", "Artist", "id")); sb.append(StringUtils.repeat("-", 27 + longestSong + longestArtist)).append("\n"); - + for(GuildSong gs : gsList) { sb.append(String.format(fmt, ++count, gs.getSong().getTitle(), gs.getSong().getArtist(), gs.getUuid())); } sb.append("```"); event.getChannel().sendMessage(sb.toString()).queue(); } - + private void loadRandomSong() throws Exception { List gsList = guildSongRepoService.getGuildSongRepo().findByGuild(guildId); if (gsList == null || gsList.isEmpty()) return; - + Random random = new Random(System.currentTimeMillis()); int item = random.nextInt(gsList.size()); - + GuildSong song = gsList.get(item); - + apm.loadItem(song.getSong().getFilePath(), ts).get(); } - + private boolean findSong(String uuid) throws Exception { GuildSong gs = guildSongRepoService.getGuildSongRepo().findByUuid(uuid); if (gs == null) return false; - + apm.loadItem(gs.getSong().getFilePath(), ts).get(); - + return true; } public void playlist(GuildMessageReceivedEvent event, List commands) { - + String command = commands.remove(0); - + switch(command) { case "add": addPlayList(event, commands); return; } - + } private void addPlayList(GuildMessageReceivedEvent event, List commands) { - // TODO Auto-generated method stub - + if (commands == null || commands.isEmpty()) { + event.getChannel().sendMessage(event.getAuthor().getAsMention() + " you have to provide the playlist to create a new playlist (no spaces in name) and optionally a list of uuids for songs to add").queue(); + return; + } + + Long guildId = event.getGuild().getIdLong(); + + DiscordGuild guild = guildSongRepoService.getGuildRepo().findByGuildId(guildId); + if (guild == null) { + event.getChannel().sendMessage("Unable to find guild in local database. Please contact administrator").queue(); + return; + } + + String playlist = commands.remove(0); + + long userId = event.getMember().getIdLong(); + + GuildPlaylist gpl = guildSongRepoService.getGuildPlaylistRepo().findByGuildAndUserIdAndPlaylist(guild, userId, playlist); + boolean newList = false; + if (gpl != null && commands.isEmpty()) { + event.getChannel().sendMessage(event.getAuthor().getAsMention() + " a playlist with the name " + playlist + " already exist for you.").queue(); + return; + } else { + newList = true; + gpl = new GuildPlaylist(); + gpl.setDateAdded(new Date()); + gpl.setGuild(guild); + gpl.setPlaylist(playlist); + gpl.setUserId(userId); + guildSongRepoService.getGuildPlaylistRepo().save(gpl); + } + + if (commands.isEmpty() && newList) { + event.getChannel().sendMessage(event.getAuthor().getAsMention() + " playlist " + playlist + " successfully created.").queue(); + return; + } + + + } } diff --git a/src/main/java/net/locusworks/discord/eighttrack/services/GuildSongRepoService.java b/src/main/java/net/locusworks/discord/eighttrack/services/GuildSongRepoService.java index 33ee8cc..e38be4a 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/services/GuildSongRepoService.java +++ b/src/main/java/net/locusworks/discord/eighttrack/services/GuildSongRepoService.java @@ -30,6 +30,8 @@ package net.locusworks.discord.eighttrack.services; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import net.locusworks.discord.eighttrack.database.repos.GuildPlaylistRepository; +import net.locusworks.discord.eighttrack.database.repos.GuildPlaylistSongRepository; import net.locusworks.discord.eighttrack.database.repos.GuildRepository; import net.locusworks.discord.eighttrack.database.repos.GuildSongRepository; import net.locusworks.discord.eighttrack.database.repos.SongRepository; @@ -43,6 +45,12 @@ public class GuildSongRepoService { @Autowired private GuildSongRepository guildSongRepo; + @Autowired + private GuildPlaylistRepository guildPlaylistRepo; + + @Autowired + private GuildPlaylistSongRepository guildPlaylistSongRepo; + @Autowired private SongRepository songRepo; @@ -60,6 +68,20 @@ public class GuildSongRepoService { return guildSongRepo; } + /** + * @return the guildPlaylistRepo + */ + public final GuildPlaylistRepository getGuildPlaylistRepo() { + return guildPlaylistRepo; + } + + /** + * @return the guildPlaylistSongRepo + */ + public final GuildPlaylistSongRepository getGuildPlaylistSongRepo() { + return guildPlaylistSongRepo; + } + /** * @return the songRepo */ From daf447e839391e2ee0e937617b23bd7934b42cfb Mon Sep 17 00:00:00 2001 From: Isaac Parenteau Date: Wed, 9 Oct 2019 22:52:38 -0500 Subject: [PATCH 2/5] Playlist updates #6 Users can now add songs to defined playlist, display their playlists and display songs within their playlist --- .../repos/GuildPlaylistRepository.java | 16 ++- .../repos/GuildPlaylistSongRepository.java | 4 + .../handlers/GuildMusicHandler.java | 123 +++++++++++++++++- 3 files changed, 138 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistRepository.java b/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistRepository.java index cd04a09..bf02e1b 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistRepository.java +++ b/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistRepository.java @@ -27,6 +27,8 @@ */ package net.locusworks.discord.eighttrack.database.repos; +import java.util.List; + import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; @@ -37,12 +39,18 @@ public interface GuildPlaylistRepository extends CrudRepository findByGuildAndUserId(DiscordGuild guild, Long userId); - @Query("SELECT gpl FROM GuildPlaylist gpl WHERE gpl.guild.guildId = ?1 AND gpl.userId = ?2 AND gpl.playlist = ?3 ") - GuildPlaylist findByGuildAndUserId(Long guild, Long userId); + @Query("SELECT gpl FROM GuildPlaylist gpl WHERE gpl.guild.guildId = ?1 AND gpl.userId = ?2") + List findByGuildAndUserId(Long guild, Long userId); + + @Query("SELECT DISTINCT gpl FROM GuildPlaylist gpl LEFT JOIN FETCH gpl.guildPlaylistSongList WHERE gpl.guild.guildId = ?1 AND gpl.userId = ?2") + List findByGuildAndUserIdFetchSongs(Long guild, Long userId); + + @Query("SELECT DISTINCT gpl FROM GuildPlaylist gpl LEFT JOIN FETCH gpl.guildPlaylistSongList WHERE gpl.guild.guildId = ?1 AND gpl.userId = ?2 AND gpl.playlist = ?3") + GuildPlaylist findGuildUserPlaylistFetchSongs(Long guild, Long userId, String playlist); } diff --git a/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistSongRepository.java b/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistSongRepository.java index a9d5581..6138877 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistSongRepository.java +++ b/src/main/java/net/locusworks/discord/eighttrack/database/repos/GuildPlaylistSongRepository.java @@ -29,6 +29,7 @@ package net.locusworks.discord.eighttrack.database.repos; import java.util.List; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import net.locusworks.discord.eighttrack.database.entities.GuildPlaylist; @@ -38,5 +39,8 @@ public interface GuildPlaylistSongRepository extends CrudRepository findByGuildPlaylist(GuildPlaylist gpl); + @Query("SELECT gpls FROM GuildPlaylistSong gpls WHERE gpls.guildPlaylist = ?1 AND gpls.guildSong.uuid IN ?2") + List findByGuildPlaylistAndSongIds(GuildPlaylist gpl, List songIds); + } diff --git a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java index b9b6745..51f09ff 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java +++ b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java @@ -33,12 +33,16 @@ import java.math.BigInteger; import java.nio.file.Files; import java.nio.file.Path; import java.time.OffsetDateTime; +import java.util.ArrayList; import java.util.Date; +import java.util.Iterator; import java.util.List; import java.util.Random; +import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -59,6 +63,7 @@ import net.dv8tion.jda.api.managers.AudioManager; import net.locusworks.crypto.utils.HashUtils; import net.locusworks.discord.eighttrack.database.entities.DiscordGuild; import net.locusworks.discord.eighttrack.database.entities.GuildPlaylist; +import net.locusworks.discord.eighttrack.database.entities.GuildPlaylistSong; import net.locusworks.discord.eighttrack.database.entities.GuildSong; import net.locusworks.discord.eighttrack.database.entities.Song; import net.locusworks.discord.eighttrack.scheduler.TrackScheduler; @@ -433,10 +438,81 @@ public class GuildMusicHandler { case "add": addPlayList(event, commands); return; + case "list": + listPlayList(event, commands); + return; } } + private void listPlayList(GuildMessageReceivedEvent event, List commands) { + if (commands == null || commands.isEmpty()) { + List userPlaylist = guildSongRepoService.getGuildPlaylistRepo().findByGuildAndUserIdFetchSongs(event.getGuild().getIdLong(), event.getMember().getIdLong()); + if (userPlaylist.isEmpty()) { + event.getChannel().sendMessage(event.getMember().getAsMention() + " you have no defined playlists on this server").queue(); + return; + } + + int longestPlaylist = 0; + for(GuildPlaylist gpl : userPlaylist) { + if (gpl.getPlaylist().length() > longestPlaylist) longestPlaylist = gpl.getPlaylist().length(); + } + + String fmt = "%6s | %-" + longestPlaylist +"s | %s%n"; + + StringBuilder sb = new StringBuilder("```"); + sb.append(String.format(fmt, "", "Playlist", "Song Count")); + sb.append(StringUtils.repeat("-", 18 + longestPlaylist)).append("\n"); + + int count = 0; + for(GuildPlaylist gpl : userPlaylist) { + sb.append(String.format(fmt, ++count, gpl.getPlaylist(), gpl.getGuildPlaylistSongList().size())); + } + sb.append("```"); + event.getChannel().sendMessage("**" + "Currently available playlists for " + event.getMember().getAsMention() + "**").queue((succcess)-> + event.getChannel().sendMessage(sb.toString()).queue() + ); + return; + } + + String playlist = commands.remove(0); + + GuildPlaylist gpl = guildSongRepoService.getGuildPlaylistRepo().findGuildUserPlaylistFetchSongs(event.getGuild().getIdLong(), event.getMember().getIdLong(), playlist); + if (gpl == null) { + event.getChannel().sendMessage(event.getMember().getAsMention() + " you have no defined playlists on this server by the name of " + playlist).queue(); + return; + } + + if (gpl.getGuildPlaylistSongList().isEmpty()) { + event.getChannel().sendMessage(event.getMember().getAsMention() + " you have no defined songs for playlist " + playlist).queue(); + return; + } + + int longestSong = 0; + int longestArtist = 0; + for(GuildPlaylistSong gpls : gpl.getGuildPlaylistSongList()) { + GuildSong gs = gpls.getGuildSong(); + if (gs.getSong().getTitle().length() > longestSong) longestSong = gs.getSong().getTitle().length(); + if (gs.getSong().getArtist().length() > longestArtist) longestArtist = gs.getSong().getArtist().length(); + } + + String fmt = "%6s | %-" + longestSong +"s | %-" + longestArtist +"s | %s%n"; + int count = 0; + StringBuilder sb = new StringBuilder("```"); + sb.append(String.format(fmt, "Track", "Title", "Artist", "id")); + sb.append(StringUtils.repeat("-", 27 + longestSong + longestArtist)).append("\n"); + + for(GuildPlaylistSong gpls : gpl.getGuildPlaylistSongList()) { + GuildSong gs = gpls.getGuildSong(); + sb.append(String.format(fmt, ++count, gs.getSong().getTitle(), gs.getSong().getArtist(), gs.getUuid())); + } + sb.append("```"); + event.getChannel().sendMessage("**" + "Current songs in playlists " + playlist + " for " + event.getMember().getAsMention() + "**").queue((succcess)-> + event.getChannel().sendMessage(sb.toString()).queue() + ); + + } + private void addPlayList(GuildMessageReceivedEvent event, List commands) { if (commands == null || commands.isEmpty()) { event.getChannel().sendMessage(event.getAuthor().getAsMention() + " you have to provide the playlist to create a new playlist (no spaces in name) and optionally a list of uuids for songs to add").queue(); @@ -460,7 +536,7 @@ public class GuildMusicHandler { if (gpl != null && commands.isEmpty()) { event.getChannel().sendMessage(event.getAuthor().getAsMention() + " a playlist with the name " + playlist + " already exist for you.").queue(); return; - } else { + } else if (gpl == null) { newList = true; gpl = new GuildPlaylist(); gpl.setDateAdded(new Date()); @@ -475,7 +551,52 @@ public class GuildMusicHandler { return; } + Set songIds = guildSongRepoService.getGuildPlaylistSongRepo() + .findByGuildPlaylistAndSongIds(gpl, commands).stream() + .map(g -> g.getGuildSong().getUuid()) + .collect(Collectors.toSet()); + List gplsList = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + for(Iterator iter = commands.iterator(); iter.hasNext();) { + String id = iter.next(); + GuildSong gs = guildSongRepoService.getGuildSongRepo().findByUuid(id); + if (songIds.contains(id)) { + sb.append(String.format("**%s** by __%s__ already exists in this playlist.%n", gs.getSong().getTitle(), gs.getSong().getArtist())); + iter.remove(); + continue; + } + + if (gs == null) { + sb.append("Song with id `" + id + "` not found. Please check id\n"); + iter.remove(); + continue; + } + + GuildPlaylistSong gpls = new GuildPlaylistSong(); + gpls.setDateAdded(new Date()); + gpls.setGuildPlaylist(gpl); + gpls.setGuildSong(gs); + gplsList.add(gpls); + + sb.append(String.format("**%s** by __%s__ added to playlist.%n", gs.getSong().getTitle(), gs.getSong().getArtist())); + iter.remove(); + } + + if (!gplsList.isEmpty()) { + guildSongRepoService.getGuildPlaylistSongRepo().saveAll(gplsList); + } + + MessageEmbed embed = new EmbedBuilder() + .setTitle("Results for adding playlist " + playlist) + .setAuthor(event.getMember().getEffectiveName(), null, event.getAuthor().getAvatarUrl()) + .setColor(Color.GREEN) + .setDescription(sb.toString()) + .setFooter("", event.getGuild().getSelfMember().getUser().getAvatarUrl()) + .setTimestamp(OffsetDateTime.now()) + .build(); + + event.getChannel().sendMessage(embed).queue(); } } From 1fca679a79e97d2727b5cded903f52e1354def39 Mon Sep 17 00:00:00 2001 From: Isaac Parenteau Date: Fri, 11 Oct 2019 20:55:22 -0500 Subject: [PATCH 3/5] Added ability to delete playlists --- .../handlers/DiscordEventHandler.java | 29 +++- .../handlers/GuildMusicHandler.java | 126 +++++++++++++++- .../eighttrack/handlers/ReactionHandler.java | 133 +++++++++++++++++ .../listeners/ReactionListener.java | 139 ++++++++++++++++++ .../discord/eighttrack/utils/Reactions.java | 37 +++++ 5 files changed, 456 insertions(+), 8 deletions(-) create mode 100644 src/main/java/net/locusworks/discord/eighttrack/handlers/ReactionHandler.java create mode 100644 src/main/java/net/locusworks/discord/eighttrack/listeners/ReactionListener.java create mode 100644 src/main/java/net/locusworks/discord/eighttrack/utils/Reactions.java diff --git a/src/main/java/net/locusworks/discord/eighttrack/handlers/DiscordEventHandler.java b/src/main/java/net/locusworks/discord/eighttrack/handlers/DiscordEventHandler.java index a255546..462b0b4 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/handlers/DiscordEventHandler.java +++ b/src/main/java/net/locusworks/discord/eighttrack/handlers/DiscordEventHandler.java @@ -38,10 +38,14 @@ import java.util.List; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import net.dv8tion.jda.api.entities.ChannelType; +import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.events.guild.GuildJoinEvent; import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; +import net.dv8tion.jda.api.events.message.react.MessageReactionAddEvent; import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.locusworks.discord.eighttrack.database.entities.DiscordGuild; import net.locusworks.discord.eighttrack.services.ConfigurationService; @@ -66,6 +70,9 @@ public class DiscordEventHandler extends ListenerAdapter { @Autowired private GuildSongRepoService guildSongRepoService; + @Autowired + private ReactionHandler reactionHandler; + @PostConstruct private void init() throws IOException { this.musicDir = confService.getMusicDirectory(); @@ -74,6 +81,26 @@ public class DiscordEventHandler extends ListenerAdapter { } } + @Scheduled(fixedRate = 300000L, initialDelay=300000L) + private void clearHandlerCache() { + logger.debug("Clearing reaction handler cache of dead reactions"); + reactionHandler.cleanCache(); + } + + @Override + public void onMessageReactionAdd(MessageReactionAddEvent e) { + if (e.getUser().isBot()) { + return; + } + if (!e.getChannel().getType().equals(ChannelType.TEXT)) { + return; + } + TextChannel channel = (TextChannel) e.getChannel(); + if (reactionHandler.canHandle(channel.getGuild().getIdLong(), e.getMessageIdLong())) { + reactionHandler.handle(channel, e.getMessageIdLong(), e.getUser().getIdLong(), e.getReaction()); + } + } + @Override public void onGuildJoin(GuildJoinEvent event) { try { @@ -108,7 +135,7 @@ public class DiscordEventHandler extends ListenerAdapter { if (!GuildMusicService.getMap().containsKey(event.getGuild().getIdLong())) { GuildMusicService.getMap().put(event.getGuild().getIdLong(), - new GuildMusicHandler(musicDir, event.getGuild().getIdLong(), uploadHandler, guildSongRepoService)); + new GuildMusicHandler(musicDir, event.getGuild().getIdLong(), uploadHandler, reactionHandler, guildSongRepoService)); } GuildMusicHandler gmh = GuildMusicService.getMap().get(event.getGuild().getIdLong()); diff --git a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java index 51f09ff..8e64e3d 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java +++ b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java @@ -40,6 +40,7 @@ import java.util.List; import java.util.Random; import java.util.Set; import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -55,19 +56,24 @@ import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.ChannelType; import net.dv8tion.jda.api.entities.GuildChannel; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Message.Attachment; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.VoiceChannel; import net.dv8tion.jda.api.events.message.guild.GuildMessageReceivedEvent; +import net.dv8tion.jda.api.exceptions.ErrorResponseException; import net.dv8tion.jda.api.managers.AudioManager; +import net.dv8tion.jda.api.requests.ErrorResponse; import net.locusworks.crypto.utils.HashUtils; import net.locusworks.discord.eighttrack.database.entities.DiscordGuild; import net.locusworks.discord.eighttrack.database.entities.GuildPlaylist; import net.locusworks.discord.eighttrack.database.entities.GuildPlaylistSong; import net.locusworks.discord.eighttrack.database.entities.GuildSong; import net.locusworks.discord.eighttrack.database.entities.Song; +import net.locusworks.discord.eighttrack.listeners.ReactionListener; import net.locusworks.discord.eighttrack.scheduler.TrackScheduler; import net.locusworks.discord.eighttrack.services.GuildSongRepoService; +import net.locusworks.discord.eighttrack.utils.Reactions; import net.locusworks.logger.ApplicationLogger; import net.locusworks.logger.ApplicationLoggerFactory; @@ -86,9 +92,10 @@ public class GuildMusicHandler { private Consumer callback; private Mp3UploadHandler uploadHandler; private GuildSongRepoService guildSongRepoService; + private ReactionHandler reactionHandler; private long guildId; - public GuildMusicHandler(Path musicDir, long guildId, Mp3UploadHandler uploadHandler, GuildSongRepoService guildSongRepoService) throws IOException { + public GuildMusicHandler(Path musicDir, long guildId, Mp3UploadHandler uploadHandler, ReactionHandler reactionHandler, GuildSongRepoService guildSongRepoService) throws IOException { this.logger = ApplicationLoggerFactory.getLogger(GuildMusicHandler.class); this.playing = new AtomicBoolean(false); this.musicDir = musicDir; @@ -96,6 +103,7 @@ public class GuildMusicHandler { this.uploadHandler = uploadHandler; this.guildSongRepoService = guildSongRepoService; this.guildId = guildId; + this.reactionHandler = reactionHandler; this.apm = new DefaultAudioPlayerManager(); AudioSourceManagers.registerLocalSource(apm); @@ -438,6 +446,9 @@ public class GuildMusicHandler { case "add": addPlayList(event, commands); return; + case "delete": + deletePlayList(event, commands); + return; case "list": listPlayList(event, commands); return; @@ -512,6 +523,103 @@ public class GuildMusicHandler { ); } + + private void deletePlayList(GuildMessageReceivedEvent event, List commands) { + if (commands == null || commands.isEmpty()) { + event.getChannel().sendMessage(event.getAuthor().getAsMention() + " you have to provide the playlist to create a new playlist (no spaces in name) and optionally a list of uuids for songs to add").queue(); + return; + } + + Long guildId = event.getGuild().getIdLong(); + + DiscordGuild guild = guildSongRepoService.getGuildRepo().findByGuildId(guildId); + if (guild == null) { + event.getChannel().sendMessage("Unable to find guild in local database. Please contact administrator").queue(); + return; + } + + String playlist = commands.remove(0); + + long userId = event.getMember().getIdLong(); + + GuildPlaylist gpl = guildSongRepoService.getGuildPlaylistRepo().findByGuildAndUserIdAndPlaylist(guild, userId, playlist); + + if (commands.isEmpty()) { + + MessageEmbed embed = new EmbedBuilder() + .setColor(Color.RED) + .setTitle("Delete Playlist " + playlist) + .setDescription("Are you sure you want to delete the playlist " + playlist) + .setTimestamp(OffsetDateTime.now()) + .setFooter(event.getGuild().getSelfMember().getEffectiveName(), event.getGuild().getSelfMember().getUser().getAvatarUrl()) + .build(); + + event.getChannel().sendMessage(embed).queue((msg) -> { + + ReactionListener handler = new ReactionListener<>(userId, msg.getId()); + handler.setExpiresIn(TimeUnit.MINUTES, 1); + handler.registerReaction(Reactions.CHECK_MARK_BUTTON, (ret) -> deletePlayListConfirm(gpl, event.getMember().getAsMention(), msg)); + handler.registerReaction(Reactions.CROSS_MARK_BUTTON, (ret) -> deleteMessage(msg)); + + reactionHandler.addReactionListener(guildId, msg, handler); + }); + return; + } + + List songs = guildSongRepoService.getGuildPlaylistSongRepo().findByGuildPlaylistAndSongIds(gpl, commands); + + StringBuilder sb = new StringBuilder("Are you sure you want to delete the following songs from playlist " + playlist + "?\n"); + for (GuildPlaylistSong gpls : songs) { + GuildSong gs = gpls.getGuildSong(); + sb.append(String.format("**%s** by __%s__%n", gs.getSong().getTitle(), gs.getSong().getArtist())); + } + + MessageEmbed embed = new EmbedBuilder() + .setColor(Color.RED) + .setTitle("Delete Playlist Songs") + .setDescription(sb.toString()) + .setTimestamp(OffsetDateTime.now()) + .setFooter(event.getGuild().getSelfMember().getEffectiveName(), event.getGuild().getSelfMember().getUser().getAvatarUrl()) + .build(); + + event.getChannel().sendMessage(embed).queue((msg) -> { + + ReactionListener handler = new ReactionListener<>(userId, msg.getId()); + handler.setExpiresIn(TimeUnit.MINUTES, 1); + handler.registerReaction(Reactions.CHECK_MARK_BUTTON, (ret) -> deletePlayListSongConfirm(songs, event.getMember().getAsMention(), msg)); + handler.registerReaction(Reactions.CROSS_MARK_BUTTON, (ret) -> deleteMessage(msg)); + + reactionHandler.addReactionListener(guildId, msg, handler); + }); + } + + private void deletePlayListSongConfirm(List songs, String user, Message msg) { + try { + guildSongRepoService.getGuildPlaylistSongRepo().deleteAll(songs); + msg.getChannel().sendMessage(user + ", songs removed successfully fom playlist").complete(); + } catch (Exception ex) { + msg.getChannel().sendMessage("Sorry " + user + " I was unable to remove songs from the playlist. Reason: " + ex.getMessage()).complete(); + logger.error("Unable to delete songs from playlist : " + ex.getMessage()); + logger.error(ex); + } finally { + deleteMessage(msg); + } + } + + private void deletePlayListConfirm(GuildPlaylist gpl, String user, Message msg) { + String playlist = gpl.getPlaylist(); + try { + guildSongRepoService.getGuildPlaylistRepo().delete(gpl); + msg.getChannel().sendMessage(user + " " + playlist + " deleted successfully").complete(); + } catch (Exception ex) { + msg.getChannel().sendMessage("Sorry " + user + " I was unable to remove playlist " + playlist + ". Reason: " + ex.getMessage()).complete(); + logger.error("Unable to delete playlist " + playlist + ": " + ex.getMessage()); + logger.error(ex); + } finally { + deleteMessage(msg); + } + + } private void addPlayList(GuildMessageReceivedEvent event, List commands) { if (commands == null || commands.isEmpty()) { @@ -558,18 +666,15 @@ public class GuildMusicHandler { List gplsList = new ArrayList<>(); StringBuilder sb = new StringBuilder(); - for(Iterator iter = commands.iterator(); iter.hasNext();) { - String id = iter.next(); + for(String id : commands.stream().collect(Collectors.toSet())) { GuildSong gs = guildSongRepoService.getGuildSongRepo().findByUuid(id); if (songIds.contains(id)) { sb.append(String.format("**%s** by __%s__ already exists in this playlist.%n", gs.getSong().getTitle(), gs.getSong().getArtist())); - iter.remove(); continue; } if (gs == null) { sb.append("Song with id `" + id + "` not found. Please check id\n"); - iter.remove(); continue; } @@ -580,7 +685,6 @@ public class GuildMusicHandler { gplsList.add(gpls); sb.append(String.format("**%s** by __%s__ added to playlist.%n", gs.getSong().getTitle(), gs.getSong().getArtist())); - iter.remove(); } if (!gplsList.isEmpty()) { @@ -597,6 +701,14 @@ public class GuildMusicHandler { .build(); event.getChannel().sendMessage(embed).queue(); - + } + + private void deleteMessage(Message msg) { + try { + msg.delete().complete(); + reactionHandler.removeReactionListener(guildId, msg.getIdLong()); + } catch (ErrorResponseException ex) { + if (ex.getErrorResponse() != ErrorResponse.UNKNOWN_MESSAGE) throw ex; + } } } diff --git a/src/main/java/net/locusworks/discord/eighttrack/handlers/ReactionHandler.java b/src/main/java/net/locusworks/discord/eighttrack/handlers/ReactionHandler.java new file mode 100644 index 0000000..cca35de --- /dev/null +++ b/src/main/java/net/locusworks/discord/eighttrack/handlers/ReactionHandler.java @@ -0,0 +1,133 @@ +/** + * + * Project: Pseudo-Bot, File: ReactionHandler.java + * + * Copyright 2019 Locusworks LLC. + * All rights reserved. Federal copyright law prohibits unauthorized reproduction by + * any means and imposes fines up to $25,000 for violation. No part of this material + * may be reproduced, transmitted, transcribed, stored in a retrieval system, copied, + * modified, duplicated, adapted or translated into another program language in any + * form or by any means, electronic, mechanical, photocopying, recording, or + * otherwise, without the prior written permission from Locusworks. Locusworks + * affirms that PSEUDO-BOT(R) software and data is subject to United States + * Government Purpose Rights. Contact Locusworks, 1313 Lawnview Drive + * Forney TX 75126, (802) 488-0438, for commercial licensing opportunities. + * + * IN NO EVENT SHALL LOCUSWORKS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, + * INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT + * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF LOCUSWORKS HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NO RESPONSIBILITY IS ASSUMED BY + * LOCUSWORKS FOR ITS USE, OR FOR ANY INFRINGEMENTS OF PATENTS OR OTHER RIGHTS OF + * THIRD PARTIES RESULTING FROM ITS USE. LOCUSWORKS SPECIFICALLY DISCLAIMS ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND + * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS + * IS". LOCUSWORKS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + */ +package net.locusworks.discord.eighttrack.handlers; + +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Component; + +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.ChannelType; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageReaction; +import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.requests.RestAction; +import net.dv8tion.jda.internal.utils.PermissionUtil; +import net.locusworks.discord.eighttrack.listeners.ReactionListener; + +@Component +public class ReactionHandler { + + private final ConcurrentHashMap>> reactions; + + private ReactionHandler() { + reactions = new ConcurrentHashMap<>(); + } + + public synchronized void addReactionListener(long guildId, Message message, ReactionListener handler) { + addReactionListener(guildId, message, handler, true); + } + + public synchronized void addReactionListener(long guildId, Message message, ReactionListener handler, boolean queue) { + if (handler == null) { + return; + } + if (message.getChannelType().equals(ChannelType.TEXT)) { + if (!PermissionUtil.checkPermission(message.getTextChannel(), message.getGuild().getSelfMember(), Permission.MESSAGE_ADD_REACTION)) { + return; + } + } + if (!reactions.containsKey(guildId)) { + reactions.put(guildId, new ConcurrentHashMap<>()); + } + if (!reactions.get(guildId).containsKey(message.getIdLong())) { + for (String emote : handler.getEmotes()) { + RestAction action = message.addReaction(emote); + if (queue) action.queue(); else action.complete(); + } + } + reactions.get(guildId).put(message.getIdLong(), handler); + } + + public synchronized void removeReactionListener(long guildId, long messageId) { + if (!reactions.containsKey(guildId)) return; + reactions.get(guildId).remove(messageId); + } + + /** + * Handles the reaction + * + * @param channel TextChannel of the message + * @param messageId id of the message + * @param userId id of the user reacting + * @param reaction the reaction + */ + public void handle(TextChannel channel, long messageId, long userId, MessageReaction reaction) { + ReactionListener listener = reactions.get(channel.getGuild().getIdLong()).get(messageId); + if (!listener.isActive() || listener.getExpiresInTimestamp() < System.currentTimeMillis()) { + reactions.get(channel.getGuild().getIdLong()).remove(messageId); + } else if ((listener.hasReaction(reaction.getReactionEmote().getName())) && listener.getUserId() == userId) { + reactions.get(channel.getGuild().getIdLong()).get(messageId).updateLastAction(); + Message message = channel.retrieveMessageById(messageId).complete(); + listener.react(reaction.getReactionEmote().getName(), message); + } + } + + /** + * Do we have an event for a message? + * + * @param guildId discord guild-id of the message + * @param messageId id of the message + * @return do we have an handler? + */ + public boolean canHandle(long guildId, long messageId) { + return reactions.containsKey(guildId) && reactions.get(guildId).containsKey(messageId); + } + + public synchronized void removeGuild(long guildId) { + reactions.remove(guildId); + } + + /** + * Delete expired handlers + */ + public synchronized void cleanCache() { + long now = System.currentTimeMillis(); + for (Iterator>>> iterator = reactions.entrySet().iterator(); iterator.hasNext(); ) { + Map.Entry>> mapEntry = iterator.next(); + mapEntry.getValue().values().removeIf(listener -> !listener.isActive() || listener.getExpiresInTimestamp() < now); + if (mapEntry.getValue().values().isEmpty()) { + reactions.remove(mapEntry.getKey()); + } + } + } + + +} diff --git a/src/main/java/net/locusworks/discord/eighttrack/listeners/ReactionListener.java b/src/main/java/net/locusworks/discord/eighttrack/listeners/ReactionListener.java new file mode 100644 index 0000000..7194929 --- /dev/null +++ b/src/main/java/net/locusworks/discord/eighttrack/listeners/ReactionListener.java @@ -0,0 +1,139 @@ +/** + * + * Project: Pseudo-Bot, File: ReactionListener.java + * + * Copyright 2019 Locusworks LLC. + * All rights reserved. Federal copyright law prohibits unauthorized reproduction by + * any means and imposes fines up to $25,000 for violation. No part of this material + * may be reproduced, transmitted, transcribed, stored in a retrieval system, copied, + * modified, duplicated, adapted or translated into another program language in any + * form or by any means, electronic, mechanical, photocopying, recording, or + * otherwise, without the prior written permission from Locusworks. Locusworks + * affirms that PSEUDO-BOT(R) software and data is subject to United States + * Government Purpose Rights. Contact Locusworks, 1313 Lawnview Drive + * Forney TX 75126, (802) 488-0438, for commercial licensing opportunities. + * + * IN NO EVENT SHALL LOCUSWORKS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, + * INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT + * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF LOCUSWORKS HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NO RESPONSIBILITY IS ASSUMED BY + * LOCUSWORKS FOR ITS USE, OR FOR ANY INFRINGEMENTS OF PATENTS OR OTHER RIGHTS OF + * THIRD PARTIES RESULTING FROM ITS USE. LOCUSWORKS SPECIFICALLY DISCLAIMS ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND + * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS + * IS". LOCUSWORKS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + */ +package net.locusworks.discord.eighttrack.listeners; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import net.dv8tion.jda.api.entities.Message; + +public class ReactionListener { + + private final Map> reactions; + private final long userId; + private volatile T data; + private Long expiresIn, lastAction; + private boolean active; + + public ReactionListener(long userId, T data) { + this.data = data; + this.userId = userId; + reactions = new LinkedHashMap<>(); + active = true; + lastAction = System.currentTimeMillis(); + expiresIn = TimeUnit.MINUTES.toMillis(5); + } + + public boolean isActive() { + return active; + } + + public void disable() { + this.active = false; + } + + /** + * The time after which this listener expires which is now + specified time + * Defaults to now+5min + * + * @param timeUnit time units + * @param time amount of time units + */ + public void setExpiresIn(TimeUnit timeUnit, long time) { + expiresIn = timeUnit.toMillis(time); + } + + /** + * Check if this listener has specified emote + * + * @param emote the emote to check for + * @return does this listener do anything with this emote? + */ + public boolean hasReaction(String emote) { + return reactions.containsKey(emote); + } + + /** + * React to the reaction :') + * + * @param emote the emote used + * @param message the message bound to the reaction + */ + public void react(String emote, Message message) { + if (hasReaction(emote)) reactions.get(emote).accept(message); + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + /** + * Register a consumer for a specified emote + * Multiple emote's will result in overriding the old one + * + * @param emote the emote to respond to + * @param consumer the behaviour when emote is used + */ + public void registerReaction(String emote, Consumer consumer) { + reactions.put(emote, consumer); + } + + /** + * @return list of all emotes used in this reaction listener + */ + public Set getEmotes() { + return reactions.keySet(); + } + + /** + * updates the timestamp when the reaction was last accessed + */ + public void updateLastAction() { + lastAction = System.currentTimeMillis(); + } + + /** + * When does this reaction listener expire? + * + * @return timestamp in millis + */ + public Long getExpiresInTimestamp() { + return lastAction + expiresIn; + } + + public long getUserId() { + return userId; + } +} diff --git a/src/main/java/net/locusworks/discord/eighttrack/utils/Reactions.java b/src/main/java/net/locusworks/discord/eighttrack/utils/Reactions.java new file mode 100644 index 0000000..5fa7b6c --- /dev/null +++ b/src/main/java/net/locusworks/discord/eighttrack/utils/Reactions.java @@ -0,0 +1,37 @@ +/** + * + * Project: Pseudo-Bot, File: Reactions.java + * + * Copyright 2019 Locusworks LLC. + * All rights reserved. Federal copyright law prohibits unauthorized reproduction by + * any means and imposes fines up to $25,000 for violation. No part of this material + * may be reproduced, transmitted, transcribed, stored in a retrieval system, copied, + * modified, duplicated, adapted or translated into another program language in any + * form or by any means, electronic, mechanical, photocopying, recording, or + * otherwise, without the prior written permission from Locusworks. Locusworks + * affirms that PSEUDO-BOT(R) software and data is subject to United States + * Government Purpose Rights. Contact Locusworks, 1313 Lawnview Drive + * Forney TX 75126, (802) 488-0438, for commercial licensing opportunities. + * + * IN NO EVENT SHALL LOCUSWORKS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, + * INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT + * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF LOCUSWORKS HAS BEEN + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. NO RESPONSIBILITY IS ASSUMED BY + * LOCUSWORKS FOR ITS USE, OR FOR ANY INFRINGEMENTS OF PATENTS OR OTHER RIGHTS OF + * THIRD PARTIES RESULTING FROM ITS USE. LOCUSWORKS SPECIFICALLY DISCLAIMS ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND + * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS + * IS". LOCUSWORKS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + */ +package net.locusworks.discord.eighttrack.utils; + +public class Reactions { + + public static final String CHECK_MARK_BUTTON = "\u2705"; + public static final String CHECK_MARK = "\u2714"; + public static final String CROSS_MARK_BUTTON = "\u274e"; + public static final String CROSS_MARK = "\u274c"; + +} From 0426c16c450f4dd7fd09c059532bafc3cad4b09c Mon Sep 17 00:00:00 2001 From: Isaac Parenteau Date: Fri, 11 Oct 2019 23:26:11 -0500 Subject: [PATCH 4/5] More development #6 Added ability to play a playlist --- .../handlers/GuildMusicHandler.java | 126 ++++++++++++------ .../eighttrack/scheduler/TrackScheduler.java | 5 +- 2 files changed, 87 insertions(+), 44 deletions(-) diff --git a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java index 8e64e3d..8093949 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java +++ b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java @@ -35,7 +35,6 @@ import java.nio.file.Path; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Date; -import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.Set; @@ -95,6 +94,8 @@ public class GuildMusicHandler { private ReactionHandler reactionHandler; private long guildId; + private GuildPlaylist currentPlaylist; + public GuildMusicHandler(Path musicDir, long guildId, Mp3UploadHandler uploadHandler, ReactionHandler reactionHandler, GuildSongRepoService guildSongRepoService) throws IOException { this.logger = ApplicationLoggerFactory.getLogger(GuildMusicHandler.class); this.playing = new AtomicBoolean(false); @@ -113,6 +114,26 @@ public class GuildMusicHandler { player.addListener(ts); } + public void playlist(GuildMessageReceivedEvent event, List commands) throws Exception { + + String command = commands.remove(0); + + switch(command) { + case "add": + addPlayList(event, commands); + return; + case "delete": + deletePlayList(event, commands); + return; + case "list": + listPlayList(event, commands); + return; + case "play": + playPlayList(event, commands); + return; + } + } + public Long getCurrentVoiceChannelId() { return voiceChannelId; } @@ -186,14 +207,21 @@ public class GuildMusicHandler { } playing.set(false); - stop(event); + stop(event, true); play(event, commands); playing.set(true); } public void stop(GuildMessageReceivedEvent event) { + stop(event, false); + } + + public void stop(GuildMessageReceivedEvent event, boolean stoppedFromRepeat) { player.stopTrack(); voiceChannelId = null; + if (!stoppedFromRepeat) { + currentPlaylist = null; + } } public void play(GuildMessageReceivedEvent event) throws Exception { @@ -237,7 +265,7 @@ public class GuildMusicHandler { event.getChannel().sendMessageFormat("Unable to find song with identifier: %s", uuid).queue(); return; } - stop(event); + stop(event, true); } if (!ts.hasTracks()) { @@ -300,6 +328,30 @@ public class GuildMusicHandler { } } + private void playPlayList(GuildMessageReceivedEvent event, List commands) throws Exception { + if (commands == null || commands.isEmpty()) { + event.getChannel().sendMessage(event.getAuthor().getAsMention() + " please provide the name of the playlist to play").queue(); + return; + } + String playlist = commands.remove(0); + currentPlaylist = guildSongRepoService.getGuildPlaylistRepo().findByGuildAndUserIdAndPlaylist(guildId, event.getMember().getIdLong(), playlist); + if (currentPlaylist == null) { + event.getChannel().sendMessage(event.getAuthor().getAsMention() + ", no playlist with the name of " + playlist + " exists").queue(); + return; + } + + ts.clearTracks(); + + for(GuildPlaylistSong gpls : guildSongRepoService.getGuildPlaylistSongRepo().findByGuildPlaylist(currentPlaylist)) { + Song s = gpls.getGuildSong().getSong(); + apm.loadItem(s.getFilePath(), ts).get(); + } + + playing.set(false); + play(event); + playing.set(true); + } + private MessageEmbed error(GuildMessageReceivedEvent event, Throwable ex, String fileName) { return new EmbedBuilder() .setTitle("Unable to upload file: " + fileName) @@ -418,7 +470,13 @@ public class GuildMusicHandler { } private void loadRandomSong() throws Exception { - List gsList = guildSongRepoService.getGuildSongRepo().findByGuild(guildId); + List gsList = null; + + if (currentPlaylist == null) { + gsList = guildSongRepoService.getGuildSongRepo().findByGuild(guildId); + } else { + gsList = guildSongRepoService.getGuildPlaylistSongRepo().findByGuildPlaylist(currentPlaylist).stream().map(gpl -> gpl.getGuildSong()).collect(Collectors.toList()); + } if (gsList == null || gsList.isEmpty()) return; Random random = new Random(System.currentTimeMillis()); @@ -438,24 +496,6 @@ public class GuildMusicHandler { return true; } - public void playlist(GuildMessageReceivedEvent event, List commands) { - - String command = commands.remove(0); - - switch(command) { - case "add": - addPlayList(event, commands); - return; - case "delete": - deletePlayList(event, commands); - return; - case "list": - listPlayList(event, commands); - return; - } - - } - private void listPlayList(GuildMessageReceivedEvent event, List commands) { if (commands == null || commands.isEmpty()) { List userPlaylist = guildSongRepoService.getGuildPlaylistRepo().findByGuildAndUserIdFetchSongs(event.getGuild().getIdLong(), event.getMember().getIdLong()); @@ -481,24 +521,24 @@ public class GuildMusicHandler { } sb.append("```"); event.getChannel().sendMessage("**" + "Currently available playlists for " + event.getMember().getAsMention() + "**").queue((succcess)-> - event.getChannel().sendMessage(sb.toString()).queue() - ); + event.getChannel().sendMessage(sb.toString()).queue() + ); return; } - + String playlist = commands.remove(0); - + GuildPlaylist gpl = guildSongRepoService.getGuildPlaylistRepo().findGuildUserPlaylistFetchSongs(event.getGuild().getIdLong(), event.getMember().getIdLong(), playlist); if (gpl == null) { event.getChannel().sendMessage(event.getMember().getAsMention() + " you have no defined playlists on this server by the name of " + playlist).queue(); return; } - + if (gpl.getGuildPlaylistSongList().isEmpty()) { event.getChannel().sendMessage(event.getMember().getAsMention() + " you have no defined songs for playlist " + playlist).queue(); return; } - + int longestSong = 0; int longestArtist = 0; for(GuildPlaylistSong gpls : gpl.getGuildPlaylistSongList()) { @@ -519,11 +559,11 @@ public class GuildMusicHandler { } sb.append("```"); event.getChannel().sendMessage("**" + "Current songs in playlists " + playlist + " for " + event.getMember().getAsMention() + "**").queue((succcess)-> - event.getChannel().sendMessage(sb.toString()).queue() - ); + event.getChannel().sendMessage(sb.toString()).queue() + ); } - + private void deletePlayList(GuildMessageReceivedEvent event, List commands) { if (commands == null || commands.isEmpty()) { event.getChannel().sendMessage(event.getAuthor().getAsMention() + " you have to provide the playlist to create a new playlist (no spaces in name) and optionally a list of uuids for songs to add").queue(); @@ -545,7 +585,7 @@ public class GuildMusicHandler { GuildPlaylist gpl = guildSongRepoService.getGuildPlaylistRepo().findByGuildAndUserIdAndPlaylist(guild, userId, playlist); if (commands.isEmpty()) { - + MessageEmbed embed = new EmbedBuilder() .setColor(Color.RED) .setTitle("Delete Playlist " + playlist) @@ -553,27 +593,27 @@ public class GuildMusicHandler { .setTimestamp(OffsetDateTime.now()) .setFooter(event.getGuild().getSelfMember().getEffectiveName(), event.getGuild().getSelfMember().getUser().getAvatarUrl()) .build(); - + event.getChannel().sendMessage(embed).queue((msg) -> { - + ReactionListener handler = new ReactionListener<>(userId, msg.getId()); handler.setExpiresIn(TimeUnit.MINUTES, 1); handler.registerReaction(Reactions.CHECK_MARK_BUTTON, (ret) -> deletePlayListConfirm(gpl, event.getMember().getAsMention(), msg)); handler.registerReaction(Reactions.CROSS_MARK_BUTTON, (ret) -> deleteMessage(msg)); - + reactionHandler.addReactionListener(guildId, msg, handler); }); return; } - + List songs = guildSongRepoService.getGuildPlaylistSongRepo().findByGuildPlaylistAndSongIds(gpl, commands); - + StringBuilder sb = new StringBuilder("Are you sure you want to delete the following songs from playlist " + playlist + "?\n"); for (GuildPlaylistSong gpls : songs) { GuildSong gs = gpls.getGuildSong(); sb.append(String.format("**%s** by __%s__%n", gs.getSong().getTitle(), gs.getSong().getArtist())); } - + MessageEmbed embed = new EmbedBuilder() .setColor(Color.RED) .setTitle("Delete Playlist Songs") @@ -581,14 +621,14 @@ public class GuildMusicHandler { .setTimestamp(OffsetDateTime.now()) .setFooter(event.getGuild().getSelfMember().getEffectiveName(), event.getGuild().getSelfMember().getUser().getAvatarUrl()) .build(); - + event.getChannel().sendMessage(embed).queue((msg) -> { - + ReactionListener handler = new ReactionListener<>(userId, msg.getId()); handler.setExpiresIn(TimeUnit.MINUTES, 1); handler.registerReaction(Reactions.CHECK_MARK_BUTTON, (ret) -> deletePlayListSongConfirm(songs, event.getMember().getAsMention(), msg)); handler.registerReaction(Reactions.CROSS_MARK_BUTTON, (ret) -> deleteMessage(msg)); - + reactionHandler.addReactionListener(guildId, msg, handler); }); } @@ -618,7 +658,7 @@ public class GuildMusicHandler { } finally { deleteMessage(msg); } - + } private void addPlayList(GuildMessageReceivedEvent event, List commands) { @@ -702,7 +742,7 @@ public class GuildMusicHandler { event.getChannel().sendMessage(embed).queue(); } - + private void deleteMessage(Message msg) { try { msg.delete().complete(); diff --git a/src/main/java/net/locusworks/discord/eighttrack/scheduler/TrackScheduler.java b/src/main/java/net/locusworks/discord/eighttrack/scheduler/TrackScheduler.java index f18e043..e57965f 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/scheduler/TrackScheduler.java +++ b/src/main/java/net/locusworks/discord/eighttrack/scheduler/TrackScheduler.java @@ -78,6 +78,10 @@ public class TrackScheduler extends AudioEventAdapter implements AudioEventListe trackQueue.add(at); } } + + public void clearTracks() { + trackQueue.clear(); + } public void noMatches() { // TODO Auto-generated method stub @@ -106,6 +110,5 @@ public class TrackScheduler extends AudioEventAdapter implements AudioEventListe public boolean playing() { return started; } - } From 26c3b03a2e5c403c6fcd6dd0bd7b8c6a32ee7307 Mon Sep 17 00:00:00 2001 From: Isaac Parenteau Date: Sat, 12 Oct 2019 19:49:03 -0500 Subject: [PATCH 5/5] adjusted display --- .../discord/eighttrack/handlers/GuildMusicHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java index 8093949..8bf32f5 100644 --- a/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java +++ b/src/main/java/net/locusworks/discord/eighttrack/handlers/GuildMusicHandler.java @@ -513,7 +513,7 @@ public class GuildMusicHandler { StringBuilder sb = new StringBuilder("```"); sb.append(String.format(fmt, "", "Playlist", "Song Count")); - sb.append(StringUtils.repeat("-", 18 + longestPlaylist)).append("\n"); + sb.append(StringUtils.repeat("-", 24 + longestPlaylist)).append("\n"); int count = 0; for(GuildPlaylist gpl : userPlaylist) {