Added ability to delete playlists
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Locusworks Team/eight-track/pipeline/head There was a failure building this commit
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Locusworks Team/eight-track/pipeline/head There was a failure building this commit
				
			This commit is contained in:
		@@ -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());
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Long> 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<String> 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<String> 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<GuildPlaylistSong> 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<String> 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<GuildPlaylistSong> 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<String> commands) {
 | 
			
		||||
    if (commands == null || commands.isEmpty()) {
 | 
			
		||||
@@ -558,18 +666,15 @@ public class GuildMusicHandler {
 | 
			
		||||
 | 
			
		||||
    List<GuildPlaylistSong> gplsList = new ArrayList<>();
 | 
			
		||||
    StringBuilder sb = new StringBuilder();
 | 
			
		||||
    for(Iterator<String> 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;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Long, ConcurrentHashMap<Long, ReactionListener<?>>> 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<Void> 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<Map.Entry<Long, ConcurrentHashMap<Long, ReactionListener<?>>>> iterator = reactions.entrySet().iterator(); iterator.hasNext(); ) {
 | 
			
		||||
      Map.Entry<Long, ConcurrentHashMap<Long, ReactionListener<?>>> mapEntry = iterator.next();
 | 
			
		||||
      mapEntry.getValue().values().removeIf(listener -> !listener.isActive() || listener.getExpiresInTimestamp() < now);
 | 
			
		||||
      if (mapEntry.getValue().values().isEmpty()) {
 | 
			
		||||
        reactions.remove(mapEntry.getKey());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -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<T> {
 | 
			
		||||
 | 
			
		||||
  private final Map<String, Consumer<Message>> 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<Message> consumer) {
 | 
			
		||||
    reactions.put(emote, consumer);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return list of all emotes used in this reaction listener
 | 
			
		||||
   */
 | 
			
		||||
  public Set<String> 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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user