diff --git a/pom.xml b/pom.xml index 8ee6253f..d35c6a6f 100644 --- a/pom.xml +++ b/pom.xml @@ -143,7 +143,7 @@ org.postgresql postgresql - 42.2.25 + 42.3.3 org.reflections @@ -181,7 +181,7 @@ com.fasterxml.jackson.core jackson-databind - 2.12.3 + 2.12.6.1 com.fasterxml.jackson.core diff --git a/src/main/java/de/voidtech/gerald/GlobalConstants.java b/src/main/java/de/voidtech/gerald/GlobalConstants.java index 4689873d..84352543 100644 --- a/src/main/java/de/voidtech/gerald/GlobalConstants.java +++ b/src/main/java/de/voidtech/gerald/GlobalConstants.java @@ -4,5 +4,5 @@ public class GlobalConstants { public static final String STREAM_URL = "https://twitch.tv/elementalmp4"; public static final String LINKTREE_URL = "https://linktr.ee/GeraldBot"; public static final String INVITE_URL = "https://discord.com/api/oauth2/authorize?client_id=555816892141404163&permissions=805694544&scope=bot%20applications.commands"; - public static final String VERSION = "1.2.8 - Mucho Mocha"; + public static final String VERSION = "1.3.0 - Delightful Doppio"; } diff --git a/src/main/java/de/voidtech/gerald/commands/actions/ActionsCommand.java b/src/main/java/de/voidtech/gerald/commands/actions/ActionsCommand.java index fe50038b..0841aa75 100644 --- a/src/main/java/de/voidtech/gerald/commands/actions/ActionsCommand.java +++ b/src/main/java/de/voidtech/gerald/commands/actions/ActionsCommand.java @@ -1,27 +1,31 @@ package main.java.de.voidtech.gerald.commands.actions; -import main.java.de.voidtech.gerald.commands.AbstractCommand; -import main.java.de.voidtech.gerald.commands.CommandContext; -import main.java.de.voidtech.gerald.entities.ActionStats; -import main.java.de.voidtech.gerald.service.ServerService; -import net.dv8tion.jda.api.EmbedBuilder; -import net.dv8tion.jda.api.entities.MessageEmbed; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.json.JSONException; -import org.json.JSONObject; -import org.springframework.beans.factory.annotation.Autowired; - -import java.awt.*; +import java.awt.Color; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import org.hibernate.Session; +import org.hibernate.SessionFactory; +import org.json.JSONException; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; + +import main.java.de.voidtech.gerald.commands.AbstractCommand; +import main.java.de.voidtech.gerald.commands.CommandContext; +import main.java.de.voidtech.gerald.entities.ActionStats; +import main.java.de.voidtech.gerald.entities.Server; +import main.java.de.voidtech.gerald.service.ServerService; +import main.java.de.voidtech.gerald.util.ParsingUtils; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.entities.MessageEmbed; + public abstract class ActionsCommand extends AbstractCommand { private static final String API_URL = "http://api.nekos.fun:8080/api/"; @@ -63,6 +67,32 @@ private ActionStats getOrCreateProfile(String id, ActionType action, long server } return stats; } + + @SuppressWarnings("unchecked") + private List getTopGivenInServer(ActionType type, long serverID) { + try(Session session = sessionFactory.openSession()) + { + return (List) session + .createQuery("FROM ActionStats WHERE type = :type AND serverID = :serverID AND givenCount > 0 ORDER BY givenCount DESC") + .setMaxResults(5) + .setParameter("type", type.getType()) + .setParameter("serverID", serverID) + .list(); + } + } + + @SuppressWarnings("unchecked") + private List getTopReceivedInServer(ActionType type, long serverID) { + try(Session session = sessionFactory.openSession()) + { + return (List) session + .createQuery("FROM ActionStats WHERE type = :type AND serverID = :serverID AND receivedCount > 0 ORDER BY receivedCount DESC") + .setMaxResults(5) + .setParameter("type", type.getType()) + .setParameter("serverID", serverID) + .list(); + } + } private void updateStatsProfile(ActionStats stats) { try(Session session = sessionFactory.openSession()) @@ -103,29 +133,71 @@ private String getStatsString(String giver, String receiver, ActionType action, } public void sendAction(CommandContext context, ActionType action) { - if(context.getMentionedMembers().isEmpty()) { + if (context.getArgs().get(0).equals("leaderboard")) { + sendActionLeaderboard(context, action); + return; + } + if (context.getMentionedMembers().isEmpty()) { context.reply("You need to mention someone to " + action.getType() + "!"); - } else { - String gifURL = getActionGif(action.getType()); - if (gifURL != null) - { - updateActionStats(context.getAuthor().getId(), context.getMentionedMembers().get(0).getId(), action, context); - String phrase = String.format("%s %s %s", context.getMember().getEffectiveName(), conjugateAction(action.getType()), - context.getMentionedMembers().get(0).getId().equals(context.getAuthor().getId()) ? "themself" : context.getMentionedMembers().get(0).getEffectiveName()); - - EmbedBuilder actionEmbedBuilder = new EmbedBuilder(); - actionEmbedBuilder.setTitle(phrase); - actionEmbedBuilder.setColor(Color.ORANGE); - if (!gifURL.equals("")) { - actionEmbedBuilder.setImage(gifURL); - } - actionEmbedBuilder.setFooter(getStatsString(context.getAuthor().getId(), context.getMentionedMembers().get(0).getId(), action, context)); - MessageEmbed actionEmbed = actionEmbedBuilder.build(); - context.reply(actionEmbed); - } + return; + } + String gifURL = getActionGif(action.getType()); + if (gifURL != null) { + updateActionStats(context.getAuthor().getId(), context.getMentionedMembers().get(0).getId(), action, context); + String phrase = String.format("%s %s %s", + context.getMember().getEffectiveName(), + conjugateAction(action.getType()), + context.getMentionedMembers().get(0).getId().equals(context.getAuthor().getId()) ? + "themself" : context.getMentionedMembers().get(0).getEffectiveName()); + //Wow that was a big one + EmbedBuilder actionEmbedBuilder = new EmbedBuilder(); + actionEmbedBuilder.setTitle(phrase); + actionEmbedBuilder.setColor(Color.ORANGE); + if (!gifURL.equals("")) actionEmbedBuilder.setImage(gifURL); + actionEmbedBuilder.setFooter(getStatsString(context.getAuthor().getId(), context.getMentionedMembers().get(0).getId(), action, context)); + MessageEmbed actionEmbed = actionEmbedBuilder.build(); + context.reply(actionEmbed); } } + private void sendActionLeaderboard(CommandContext context, ActionType action) { + Server server = serverService.getServer(context.getGuild().getId()); + List topGiven = getTopGivenInServer(action, server.getId()); + List topReceived = getTopReceivedInServer(action, server.getId()); + + StringBuilder leaderboardBuilder = new StringBuilder(); + int i = 1; + leaderboardBuilder.append("**Top 5 " + action.getType() + " givers**\n\n"); + for (ActionStats stat : topGiven) { + leaderboardBuilder.append(ParsingUtils.convertSingleDigitToEmoji(String.valueOf(i))); + leaderboardBuilder.append(" "); + leaderboardBuilder.append(context.getGuild().retrieveMemberById(stat.getMember()).complete().getAsMention()); + leaderboardBuilder.append(" - `"); + leaderboardBuilder.append(stat.getGivenCount()); + leaderboardBuilder.append("`\n"); + i++; + } + if (i == 1) leaderboardBuilder.append("Nobody to show! Go " + action.getType() + " someone!\n"); + i = 1; + leaderboardBuilder.append("**\nTop 5 " + action.getType() + " receivers**\n\n"); + for (ActionStats stat : topReceived) { + leaderboardBuilder.append(ParsingUtils.convertSingleDigitToEmoji(String.valueOf(i))); + leaderboardBuilder.append(" "); + leaderboardBuilder.append(context.getGuild().retrieveMemberById(stat.getMember()).complete().getAsMention()); + leaderboardBuilder.append(" - `"); + leaderboardBuilder.append(stat.getReceivedCount()); + leaderboardBuilder.append("`\n"); + i++; + } + if (i == 1) leaderboardBuilder.append("Nobody to show! Go " + action.getType() + " someone!"); + MessageEmbed leaderboardEmbed = new EmbedBuilder() + .setColor(Color.ORANGE) + .setTitle(context.getGuild().getName() + "'s " + action.getType() + " leaderboard") + .setDescription(leaderboardBuilder.toString()) + .build(); + context.reply(leaderboardEmbed); + } + private String conjugateAction(String action) { String conjugatedAction = action; diff --git a/src/main/java/de/voidtech/gerald/commands/fun/ImpersonateCommand.java b/src/main/java/de/voidtech/gerald/commands/fun/ImpersonateCommand.java index 19a1243a..2c35c1b6 100644 --- a/src/main/java/de/voidtech/gerald/commands/fun/ImpersonateCommand.java +++ b/src/main/java/de/voidtech/gerald/commands/fun/ImpersonateCommand.java @@ -49,7 +49,7 @@ private void sendWebhookMessage(CommandContext context, List args, Membe messageToBeSent.append(args.get(i)).append(" "); } Webhook impersonateHook = webhookManager.getOrCreateWebhook((TextChannel) context.getChannel(), "BGImpersonate", context.getJDA().getSelfUser().getId()); - webhookManager.postMessage(messageToBeSent.toString(), memberToBeImpersonated.getUser().getAvatarUrl(), memberToBeImpersonated.getUser().getName(), impersonateHook); + webhookManager.postMessage(messageToBeSent.toString(), null, memberToBeImpersonated.getUser().getAvatarUrl(), memberToBeImpersonated.getUser().getName(), impersonateHook); //TODO (from: Franziska): Message needs to be deleted, message context does not have a message object. Should we add one? Do we do this somehow else? Should this command be available through slashes at all!? //context.delete().queue(); diff --git a/src/main/java/de/voidtech/gerald/commands/utils/ExperienceCommand.java b/src/main/java/de/voidtech/gerald/commands/utils/ExperienceCommand.java index 5688b4be..86f4ca11 100644 --- a/src/main/java/de/voidtech/gerald/commands/utils/ExperienceCommand.java +++ b/src/main/java/de/voidtech/gerald/commands/utils/ExperienceCommand.java @@ -1,7 +1,9 @@ package main.java.de.voidtech.gerald.commands.utils; import java.awt.Color; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; @@ -16,10 +18,11 @@ import main.java.de.voidtech.gerald.service.ServerService; import main.java.de.voidtech.gerald.util.ParsingUtils; import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; -import net.dv8tion.jda.api.entities.MessageChannel; import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.TextChannel; @Command public class ExperienceCommand extends AbstractCommand { @@ -33,55 +36,201 @@ public class ExperienceCommand extends AbstractCommand { @Override public void executeInternal(CommandContext context, List args) { Server server = serverService.getServer(context.getGuild().getId()); - if (args.isEmpty()) sendLevelCard(context.getChannel(), context.getMember(), server.getId()); + if (args.isEmpty()) sendLevelCard(context, context.getMember(), server.getId()); else { String ID = ParsingUtils.filterSnowflake(args.get(0)); if (ParsingUtils.isSnowflake(ID)) { - Member mentionedMember = context.getGuild().getMemberById(ID); + Member mentionedMember = context.getGuild().retrieveMemberById(ID).complete(); if (mentionedMember == null) context.getChannel().sendMessage("**You need to mention another member to see their XP!**").queue(); - else sendLevelCard(context.getChannel(), mentionedMember, server.getId()); + else sendLevelCard(context, mentionedMember, server.getId()); } else { switch (args.get(0)) { case "levels": - showAllLevelRoles(context.getChannel(), server, context.getGuild()); + showAllLevelRoles(context, server, context.getGuild()); break; case "addrole": - addLevelUpRole(context.getChannel(), args, server); + addLevelUpRole(context, args, server); break; case "removerole": - removeLevelUpRole(context.getChannel(), args, server); + removeLevelUpRole(context, args, server); + break; + case "leaderboard": + showServerLeaderboard(context, server); + break; + case "lb": + showServerLeaderboard(context, server); + break; + case "togglemsg": + toggleLevelUpMessages(context, server); + break; + case "noxp": + handleNoXpChannelSettings(context, server); + break; + default: + context.reply("**That's not a valid subcommand!**\n" + this.getUsage()); break; } } } } + + private void showNoXpHelp(CommandContext context) { + context.reply("**No XP Settings:**\n" + + "list - shows all channels where xp will not be gained\n" + + "add - add a channel that will not gain xp\n" + + "clear - remove all no xp channels\n" + + "remove - remove a channel that will not gain xp\n\n" + + "When adding or removing a channel from the no xp list, you will be prompted to enter a channel mention or ID."); + } + + private void handleNoXpChannelSettings(CommandContext context, Server server) { + String mode; + if (context.getArgs().size() < 2) mode = "help"; + else mode = context.getArgs().get(1); + + switch (mode) { + case "list": + showNoXPChannels(context, server); + break; + case "add": + addNoXPChannel(context, server); + break; + case "remove": + removeNoXPChannel(context, server); + break; + case "clear": + clearNoXPChannels(context, server); + break; + case "help": + showNoXpHelp(context); + break; + default: + showNoXpHelp(context); + break; + } + } + + private void clearNoXPChannels(CommandContext context, Server server) { + if (!context.getMember().getPermissions().contains(Permission.MANAGE_SERVER)) { + context.reply("**You need the Manage Server Permission to do that!**"); + return; + } + xpService.clearNoXpChannels(server.getId()); + context.reply("**No XP Channels have been cleared!**"); + } - private void addLevelUpRole(MessageChannel channel, List args, Server server) { + private void removeNoXPChannel(CommandContext context, Server server) { + if (!context.getMember().getPermissions().contains(Permission.MANAGE_SERVER)) { + context.reply("**You need the Manage Server Permission to do that!**"); + return; + } + if (context.getArgs().size() < 3) { + context.reply("**You need to specify a channel to remove!**"); + return; + } + String channelID = ParsingUtils.filterSnowflake(context.getArgs().get(2)); + TextChannel channel = context.getGuild().getTextChannelById(channelID); + if (channel == null) { + context.reply("**You need to specify a valid text channel!**"); + return; + } + xpService.deleteNoXpChannel(channelID, server.getId()); + context.reply("**No XP channel** <#" + channelID + "> **has been removed!**"); + } + + private void addNoXPChannel(CommandContext context, Server server) { + if (!context.getMember().getPermissions().contains(Permission.MANAGE_SERVER)) { + context.reply("**You need the Manage Server Permission to do that!**"); + return; + } + if (context.getArgs().size() < 3) { + context.reply("**You need to specify a channel to add!**"); + return; + } + String channelID = ParsingUtils.filterSnowflake(context.getArgs().get(2)); + TextChannel channel = context.getGuild().getTextChannelById(channelID); + if (channel == null) { + context.reply("**You need to specify a valid text channel!**"); + return; + } + xpService.addNoXpChannel(channelID, server.getId()); + context.reply("**No XP channel** <#" + channelID + "> **has been added!**"); + } + + private void showNoXPChannels(CommandContext context, Server server) { + List channels = xpService.getNoExperienceChannelsForServer(server.getId(), context.getJDA()) + .stream().map(channel -> "<#" + channel + ">").collect(Collectors.toList()); + + String messageText = channels.isEmpty() ? "No channels to show!" : String.join("\n", channels); + + MessageEmbed noXPChannelsEmbed = new EmbedBuilder() + .setDescription(messageText) + .setColor(Color.ORANGE) + .build(); + context.reply(noXPChannelsEmbed); + } + + private void toggleLevelUpMessages(CommandContext context, Server server) { + boolean userCanToggle = context.getMember().hasPermission(Permission.MANAGE_SERVER); + if (!userCanToggle) context.reply("**You need the Manage Server permission to toggle level up messages!**"); + else { + boolean enabled = xpService.toggleLevelUpMessages(server.getId()); + context.reply("**Level up messages are now " + (enabled ? "enabled**" : "disabled**")); + } + } + + private void showServerLeaderboard(CommandContext context, Server server) { + List topTenMembers = xpService.getServerLeaderboardChunk(server.getId(), 5, 0); + int userPosition = xpService.getUserLeaderboardPosition(server.getId(), context.getAuthor().getId()); + Experience userXP = xpService.getUserExperience(context.getAuthor().getId(), server.getId()); + + String leaderboard = ""; + int rank = 1; + + for (Experience xp : topTenMembers) { + leaderboard += numberToEmoji(rank) + " <@" + xp.getUserID() + ">\n"; + leaderboard += "```js\nLevel " + xp.getCurrentLevel() + " | XP " + xp.getTotalExperience() + "\n```"; + rank++; + } + + leaderboard += "\n**Your Position**\n"; + leaderboard += numberToEmoji(userPosition) + " <@" + userXP.getUserID() + ">\n"; + leaderboard += "```js\nLevel " + userXP.getCurrentLevel() + " | XP " + userXP.getTotalExperience() + "\n```"; + + MessageEmbed leaderboardEmbed = new EmbedBuilder() + .setColor(Color.ORANGE) + .setDescription(leaderboard) + .setTitle(context.getGuild().getName() + "'s Most Active Members") + .build(); + context.reply(leaderboardEmbed); + } + + private void addLevelUpRole(CommandContext context, List args, Server server) { if (args.size() < 3) { - channel.sendMessage("**You need to supply more arguments! Make sure you have a level and a role mention or ID!**").queue(); + context.reply("**You need to supply more arguments! Make sure you have a level and a role mention or ID!**"); return; } String level = args.get(1); if (!ParsingUtils.isInteger(level)) { - channel.sendMessage("**You need to provide a valid number for the level!**").queue(); + context.reply("**You need to provide a valid number for the level!**"); return; } if (xpService.serverHasRoleForLevel(server.getId(), Integer.parseInt(level))) { - channel.sendMessage("**There is already a role set up for this level!**").queue(); + context.reply("**There is already a role set up for this level!**"); return; } String roleID = ParsingUtils.filterSnowflake(args.get(2)); if (!ParsingUtils.isSnowflake(roleID)) { - channel.sendMessage("**Please provide a valid role mention or role ID**").queue(); + context.reply("**Please provide a valid role mention or role ID**"); return; } - Guild guild = channel.getJDA().getGuildById(server.getGuildID()); + Guild guild = context.getJDA().getGuildById(server.getGuildID()); if (guild.getRoleById(roleID) == null) { - channel.sendMessage("**Please provide a valid role mention or role ID**").queue(); + context.reply("**Please provide a valid role mention or role ID**"); return; } @@ -92,23 +241,32 @@ private void addLevelUpRole(MessageChannel channel, List args, Server se .setTitle("Added Level Up Role!") .setDescription("Level: `" + level + "`\nRole: <@&" + roleID + ">") .build(); - channel.sendMessageEmbeds(newRoleEmbed).queue(); + context.reply(newRoleEmbed); } - private void removeLevelUpRole(MessageChannel channel, List args, Server server) { + private String numberToEmoji(int number) { + List digits = Arrays.asList(String.valueOf(number).split("")); //Wowzer + String finalNumber = ""; + for (String digit : digits) { + finalNumber += ParsingUtils.convertSingleDigitToEmoji(digit); + } + return finalNumber; + } + + private void removeLevelUpRole(CommandContext context, List args, Server server) { if (args.size() < 2) { - channel.sendMessage("**You need to provide a level to remove the role from!**").queue(); + context.reply("**You need to provide a level to remove the role from!**"); return; } String level = args.get(1); if (!ParsingUtils.isInteger(level)) { - channel.sendMessage("**You need to provide a valid number for the level!**").queue(); + context.reply("**You need to provide a valid number for the level!**"); return; } if (!xpService.serverHasRoleForLevel(server.getId(), Integer.parseInt(level))) { - channel.sendMessage("**There isn't a role set up for this level!**").queue(); + context.reply("**There isn't a role set up for this level!**"); return; } @@ -118,13 +276,13 @@ private void removeLevelUpRole(MessageChannel channel, List args, Server .setTitle("Removed Level Up Role!") .setDescription("Role for level `" + level + "` has been removed") .build(); - channel.sendMessageEmbeds(roleDeletedEmbed).queue(); + context.reply(roleDeletedEmbed); } - private void showAllLevelRoles(MessageChannel channel, Server server, Guild guild) { + private void showAllLevelRoles(CommandContext context, Server server, Guild guild) { List levelUpRoles = xpService.getAllLevelUpRolesForServer(server.getId()); if (levelUpRoles.isEmpty()) - channel.sendMessage("**There are no level roles set up in this server! See the help page for more info!**").queue(); + context.reply("**There are no level roles set up in this server! See the help page for more info!**"); else { String roleMessage = ""; for (LevelUpRole role : levelUpRoles) { @@ -135,18 +293,29 @@ private void showAllLevelRoles(MessageChannel channel, Server server, Guild guil .setTitle("Level up roles for " + guild.getName()) .setDescription(roleMessage) .build(); - channel.sendMessageEmbeds(levelRolesEmbed).queue(); + context.reply(levelRolesEmbed); } } - private void sendLevelCard(MessageChannel channel, Member member, long serverID) { + private void sendLevelCard(CommandContext context, Member member, long serverID) { Experience userXP = xpService.getUserExperience(member.getId(), serverID); - byte[] xpCard = xpService.getExperienceCard(member.getUser().getAvatarUrl(), - userXP.getCurrentExperience(), xpService.xpNeededForLevel(userXP.getLevel()), - userXP.getLevel(), 1, member.getUser().getName() + "#" + member.getUser().getDiscriminator(), - "#FF0000", "#2E2E2E"); - channel.sendFile(xpCard, "xpcard.png").queue(); + if (userXP == null) { + context.reply("**User is not ranked yet!**"); + return; + } + + String avatarURL = member.getUser().getAvatarUrl(); + long xpAchieved = userXP.getCurrentExperience(); + long xpNeeded = xpService.xpNeededForLevel(userXP.getNextLevel()); + long level = userXP.getCurrentLevel(); + long rank = xpService.getUserLeaderboardPosition(serverID, userXP.getUserID()); + String username = member.getUser().getName(); + String discriminator = member.getUser().getDiscriminator(); + + byte[] xpCard = xpService.getExperienceCard(avatarURL, + xpAchieved, xpNeeded, level, rank, username, discriminator, "#F24548", "#3B43D5", "#2F3136"); + context.replyWithFile(xpCard, "xpcard.png"); } @Override @@ -155,7 +324,9 @@ public String getDescription() { + "You can gain up to 15 experience per minute.\n" + "Server admins can configure roles that are given to you when you reach a certain level.\n" + "To stop people from checking their XP, you can disable the XP command.\n" - + "If you want to stop people from gaining XP, disable the r-xp routine."; + + "If you want to stop people from gaining XP, disable the r-xp routine.\n" + + "To disable the level up messages, use the togglemsg subcommand.\n" + + "To control which channels will not allow members to gain XP, use 'xp noxp help' to see how to set it up!"; } @Override @@ -164,7 +335,9 @@ public String getUsage() { + "xp levels\n" + "xp addrole [level] [role]\n" + "xp removerole [level]\n" - + "xp leaderboard"; + + "xp togglemsg\n" + + "xp leaderboard\n" + + "xp noxp [help/list/add/remove/clear]"; } @Override diff --git a/src/main/java/de/voidtech/gerald/entities/Experience.java b/src/main/java/de/voidtech/gerald/entities/Experience.java index a60b5d05..d569fbb4 100644 --- a/src/main/java/de/voidtech/gerald/entities/Experience.java +++ b/src/main/java/de/voidtech/gerald/entities/Experience.java @@ -33,6 +33,9 @@ public class Experience { @Column private long lastMessageTime; + @Column + private long totalExperience; + @Deprecated //ONLY FOR HIBERNATE, DO NOT USE Experience() { @@ -51,6 +54,11 @@ public void setLevel(long level) { this.level = level; } + public void incrementExperience(long xp) { + this.experienceGainedToNextLevel = this.experienceGainedToNextLevel + xp; + this.totalExperience = this.totalExperience + xp; + } + public void setCurrentXP(long xp) { this.experienceGainedToNextLevel = xp; } @@ -63,14 +71,22 @@ public void incrementMessageCount() { this.messageCount++; } - public long getLevel() { + public long getCurrentLevel() { return this.level; } + public long getNextLevel() { + return this.level + 1; + } + public long getCurrentExperience() { return this.experienceGainedToNextLevel; } + public long getTotalExperience() { + return this.totalExperience; + } + public long getLastMessageTime() { return this.lastMessageTime; } diff --git a/src/main/java/de/voidtech/gerald/entities/ServerExperienceConfig.java b/src/main/java/de/voidtech/gerald/entities/ServerExperienceConfig.java index 86794a63..1b3602e2 100644 --- a/src/main/java/de/voidtech/gerald/entities/ServerExperienceConfig.java +++ b/src/main/java/de/voidtech/gerald/entities/ServerExperienceConfig.java @@ -1,12 +1,20 @@ package main.java.de.voidtech.gerald.entities; +import java.util.HashSet; +import java.util.Set; + import javax.persistence.Column; +import javax.persistence.ElementCollection; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import org.hibernate.annotations.Cascade; +import org.hibernate.annotations.CascadeType; + @Entity @Table(name = "serverexperienceconfig") public class ServerExperienceConfig { @@ -21,14 +29,36 @@ public class ServerExperienceConfig { @Column private boolean levelUpMessagesEnabled; + //TODO: Don't fetch EAGER for this. + @ElementCollection(fetch = FetchType.EAGER) + @Cascade(CascadeType.REMOVE) + private Set noExperienceChannels; + @Deprecated //ONLY FOR HIBERNATE, DO NOT USE ServerExperienceConfig() { } + public Set getNoXPChannels() { + if(this.noExperienceChannels == null) return new HashSet(); + else return this.noExperienceChannels; + } + + public void addNoExperienceChannel(String ID) { + this.noExperienceChannels.add(ID); + } + + public void removeNoExperienceChannel(String ID) { + this.noExperienceChannels.remove(ID); + } + + public void clearNoExperienceChannels() { + this.noExperienceChannels.clear(); + } + public ServerExperienceConfig(long serverID) { this.serverID = serverID; - this.levelUpMessagesEnabled = true; + this.levelUpMessagesEnabled = false; } public boolean levelUpMessagesEnabled() { diff --git a/src/main/java/de/voidtech/gerald/listeners/MemberListener.java b/src/main/java/de/voidtech/gerald/listeners/MemberListener.java index fc43379b..2156ca1d 100644 --- a/src/main/java/de/voidtech/gerald/listeners/MemberListener.java +++ b/src/main/java/de/voidtech/gerald/listeners/MemberListener.java @@ -3,7 +3,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import main.java.de.voidtech.gerald.service.ExperienceService; import main.java.de.voidtech.gerald.service.JoinLeaveMessageService; +import main.java.de.voidtech.gerald.service.ServerService; import net.dv8tion.jda.api.events.GenericEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberJoinEvent; import net.dv8tion.jda.api.events.guild.member.GuildMemberRemoveEvent; @@ -15,11 +17,19 @@ public class MemberListener implements EventListener { @Autowired private JoinLeaveMessageService JLMService; + @Autowired + private ExperienceService xpService; + + @Autowired + private ServerService serverService; + @Override public void onEvent(GenericEvent event) { if (event instanceof GuildMemberJoinEvent) { GuildMemberJoinEvent joinEvent = (GuildMemberJoinEvent) event; JLMService.sendJoinMessage(joinEvent); + xpService.addRolesOnServerJoin(serverService.getServer(joinEvent.getGuild().getId()), joinEvent.getMember()); + } if (event instanceof GuildMemberRemoveEvent) { diff --git a/src/main/java/de/voidtech/gerald/routines/utils/ExperienceRoutine.java b/src/main/java/de/voidtech/gerald/routines/utils/ExperienceRoutine.java index 1f9da189..97ab9723 100644 --- a/src/main/java/de/voidtech/gerald/routines/utils/ExperienceRoutine.java +++ b/src/main/java/de/voidtech/gerald/routines/utils/ExperienceRoutine.java @@ -1,11 +1,15 @@ package main.java.de.voidtech.gerald.routines.utils; +import java.util.List; + import org.springframework.beans.factory.annotation.Autowired; import main.java.de.voidtech.gerald.annotations.Routine; +import main.java.de.voidtech.gerald.entities.Server; import main.java.de.voidtech.gerald.routines.AbstractRoutine; import main.java.de.voidtech.gerald.routines.RoutineCategory; import main.java.de.voidtech.gerald.service.ExperienceService; +import main.java.de.voidtech.gerald.service.ServerService; import net.dv8tion.jda.api.entities.ChannelType; import net.dv8tion.jda.api.entities.Message; @@ -15,9 +19,15 @@ public class ExperienceRoutine extends AbstractRoutine { @Autowired private ExperienceService xpService; + @Autowired + private ServerService serverService; + @Override public void executeInternal(Message message) { if (message.getChannel().getType().equals(ChannelType.PRIVATE)) return; + Server server = serverService.getServer(message.getGuild().getId()); + List noxp = xpService.getNoExperienceChannelsForServer(server.getId(), message.getJDA()); + if (noxp.contains(message.getChannel().getId())) return; xpService.updateUserExperience(message.getMember(), message.getGuild().getId(), message.getChannel().getId()); } diff --git a/src/main/java/de/voidtech/gerald/routines/utils/TunnelRoutine.java b/src/main/java/de/voidtech/gerald/routines/utils/TunnelRoutine.java index a508b5b2..d64d7496 100644 --- a/src/main/java/de/voidtech/gerald/routines/utils/TunnelRoutine.java +++ b/src/main/java/de/voidtech/gerald/routines/utils/TunnelRoutine.java @@ -68,15 +68,8 @@ private void sendWebhookMessage(Webhook webhook, String content, Message message } content = contentBuilder.toString(); } - - if (message.getReferencedMessage() != null) { - content = "> " + (message.getReferencedMessage().getContentDisplay().length() > 100 ? - message.getReferencedMessage().getContentDisplay().substring(0, 100) + "..." : - message.getReferencedMessage().getContentDisplay()) - + "\n" + content; - } - webhookManager.postMessage(content, message.getAuthor().getAvatarUrl(), message.getAuthor().getName(), webhook); + webhookManager.postMessage(content, message.getReferencedMessage(), message.getAuthor().getAvatarUrl(), message.getAuthor().getName(), webhook); } private void sendTunnelMessage(Tunnel tunnel, Message message) { diff --git a/src/main/java/de/voidtech/gerald/service/ExperienceService.java b/src/main/java/de/voidtech/gerald/service/ExperienceService.java index dcb122d2..4ee98ea0 100644 --- a/src/main/java/de/voidtech/gerald/service/ExperienceService.java +++ b/src/main/java/de/voidtech/gerald/service/ExperienceService.java @@ -1,13 +1,16 @@ package main.java.de.voidtech.gerald.service; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Instant; +import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; import javax.xml.bind.DatatypeConverter; @@ -22,6 +25,7 @@ import main.java.de.voidtech.gerald.entities.Server; import main.java.de.voidtech.gerald.entities.ServerExperienceConfig; import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.Role; @@ -38,29 +42,29 @@ public class ExperienceService { @Autowired private GeraldConfig config; - private static final int EXPERIENCE_DELAY = 0; //Delay between incrementing XP in seconds + private static final Logger LOGGER = Logger.getLogger(ExperienceService.class.getName()); + private static final int EXPERIENCE_DELAY = 60; //Delay between incrementing XP in seconds public byte[] getExperienceCard(String avatarURL, long xpAchieved, long xpNeeded, - long level, long rank, String username, String barColour, String background) { + long level, long rank, String username, String discriminator, String barFromColour, + String barToColour, String background) { try { - String cardURL = config.getExperienceCardApiURL() + "?avatar_url=" + avatarURL + + String cardURL = config.getExperienceCardApiURL() + "xpcard/?avatar_url=" + avatarURL + "&xp=" + xpAchieved + "&xp_needed=" + xpNeeded + "&level=" + level + "&rank=" + rank + "&username=" + URLEncoder.encode(username, StandardCharsets.UTF_8.toString()) - + "&bar_colour=" + URLEncoder.encode(barColour, StandardCharsets.UTF_8.toString()) + + "&discriminator=" + URLEncoder.encode(discriminator, StandardCharsets.UTF_8.toString()) + + "&bar_colour_from=" + URLEncoder.encode(barFromColour, StandardCharsets.UTF_8.toString()) + + "&bar_colour_to=" + URLEncoder.encode(barToColour, StandardCharsets.UTF_8.toString()) + "&bg_colour=" + URLEncoder.encode(background, StandardCharsets.UTF_8.toString()); URL url = new URL(cardURL); //Remove the data:image/png;base64 part String response = Jsoup.connect(url.toString()).get().toString().split(",")[1]; byte[] imageBytes = DatatypeConverter.parseBase64Binary(response); return imageBytes; - } catch (MalformedURLException e) { - e.printStackTrace(); } catch (IOException e) { - e.printStackTrace(); + LOGGER.log(Level.SEVERE, e.getMessage()); } - return null; - } public Experience getUserExperience(String userID, long serverID) { @@ -70,12 +74,21 @@ public Experience getUserExperience(String userID, long serverID) { .setParameter("userID", userID) .setParameter("serverID", serverID) .uniqueResult(); - - if (xp == null) xp = new Experience(userID, serverID); return xp; } } + public List getNoExperienceChannelsForServer(long serverID, JDA jda) { + List channels = new ArrayList(); + ServerExperienceConfig config = getServerExperienceConfig(serverID); + config.getNoXPChannels() + .stream() + .filter(channel -> jda.getTextChannelById(channel) == null) + .forEach(channel -> config.removeNoExperienceChannel(channel)); + channels = config.getNoXPChannels().stream().collect(Collectors.toList()); + return channels; + } + private ServerExperienceConfig getServerExperienceConfig(long serverID) { try(Session session = sessionFactory.openSession()) { @@ -91,6 +104,45 @@ private ServerExperienceConfig getServerExperienceConfig(long serverID) { } } + public List getServerLeaderboard(long serverID) { + try(Session session = sessionFactory.openSession()) + { + @SuppressWarnings("unchecked") + List leaderboard = session.createQuery("FROM Experience WHERE serverID = :serverID ORDER BY totalExperience DESC") + .setParameter("serverID", serverID) + .list(); + return leaderboard; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public List getServerLeaderboardChunk(long serverID, int limit, int offset) { + try(Session session = sessionFactory.openSession()) + { + @SuppressWarnings("unchecked") + List leaderboard = session.createQuery("FROM Experience WHERE serverID = :serverID ORDER BY totalExperience DESC") + .setParameter("serverID", serverID) + .setMaxResults(limit) + .setFirstResult(offset) + .list(); + return leaderboard; + } + } + + public int getUserLeaderboardPosition(long serverID, String userID) { + List leaderboard = getServerLeaderboard(serverID); + int position = 0; + + for (Experience xp : leaderboard) { + position++; + if (xp.getUserID().equals(userID)) break; + } + + return position; + } + private List getRolesForLevelFromServer(long id, long level) { try(Session session = sessionFactory.openSession()) { @@ -165,12 +217,29 @@ public void removeLevelUpRole(long level, long serverID) { } } + public void deleteNoXpChannel(String channelID, long serverID) { + ServerExperienceConfig config = getServerExperienceConfig(serverID); + config.removeNoExperienceChannel(channelID); + saveServerExperienceConfig(config); + } + + public void clearNoXpChannels(long serverID) { + ServerExperienceConfig config = getServerExperienceConfig(serverID); + config.clearNoExperienceChannels(); + saveServerExperienceConfig(config); + } + + public void addNoXpChannel(String channelID, long serverID) { + ServerExperienceConfig config = getServerExperienceConfig(serverID); + config.addNoExperienceChannel(channelID); + saveServerExperienceConfig(config); + } + public long xpNeededForLevel(long level) { - return 5 * (level ^ 2) + (50 * level) + 100; + return (long) Math.ceil(Math.sqrt(5000 * (Math.pow(level, 3)))); } - private long xpToNextLevel(long currentLevel, long currentXP) { - long nextLevel = currentLevel + 1; + private long xpToNextLevel(long nextLevel, long currentXP) { return xpNeededForLevel(nextLevel) - currentXP; } @@ -181,6 +250,11 @@ private int generateExperience() { public void updateUserExperience(Member member, String guildID, String channelID) { Server server = serverService.getServer(guildID); Experience userXP = getUserExperience(member.getId(), server.getId()); + + if (userXP == null) { + userXP = new Experience(member.getId(), server.getId()); + } + userXP.incrementMessageCount(); if ((userXP.getLastMessageTime() + EXPERIENCE_DELAY) > Instant.now().getEpochSecond()) { @@ -188,11 +262,12 @@ public void updateUserExperience(Member member, String guildID, String channelID return; } - long currentExperience = userXP.getCurrentExperience() + generateExperience(); - long xpToNextLevel = xpToNextLevel(userXP.getLevel(), currentExperience); + userXP.incrementExperience(generateExperience()); + long currentExperience = userXP.getCurrentExperience(); + long xpToNextLevel = xpToNextLevel(userXP.getNextLevel(), currentExperience); if (xpToNextLevel <= 0) { - userXP.setLevel(userXP.getLevel() + 1); + userXP.setLevel(userXP.getNextLevel()); userXP.setCurrentXP(-1 * xpToNextLevel); performLevelUpActions(userXP, server, member, channelID); } else userXP.setCurrentXP(currentExperience); @@ -205,7 +280,7 @@ public void updateUserExperience(Member member, String guildID, String channelID private void performLevelUpActions(Experience userXP, Server server, Member member, String channelID) { ServerExperienceConfig config = getServerExperienceConfig(server.getId()); - List roles = getRolesForLevelFromServer(server.getId(), userXP.getLevel()); + List roles = getRolesForLevelFromServer(server.getId(), userXP.getCurrentLevel()); if (roles.isEmpty()) return; List memberRoles = member.getRoles(); @@ -221,6 +296,20 @@ private void performLevelUpActions(Experience userXP, Server server, Member memb } } } + + public void addRolesOnServerJoin(Server server, Member member) { + Experience userXP = getUserExperience(member.getId(), server.getId()); + List roles = getRolesForLevelFromServer(server.getId(), userXP.getCurrentLevel()); + + if (roles.isEmpty()) return; + + List memberRoles = member.getRoles(); + for (LevelUpRole role : roles) { + Role roleToBeGiven = member.getGuild().getRoleById(role.getRoleID()); + if (roleToBeGiven == null) removeLevelUpRole(role.getLevel(), role.getServerID()); + else if (!memberRoles.contains(roleToBeGiven)) member.getGuild().addRoleToMember(member, roleToBeGiven).complete(); + } + } private void sendLevelUpMessage(LevelUpRole role, Member member, Role roleToBeGiven, String channelID) { MessageEmbed levelUpEmbed = new EmbedBuilder() @@ -231,4 +320,12 @@ private void sendLevelUpMessage(LevelUpRole role, Member member, Role roleToBeGi .build(); member.getGuild().getTextChannelById(channelID).sendMessageEmbeds(levelUpEmbed).queue(); } + + public boolean toggleLevelUpMessages(long id) { + ServerExperienceConfig config = getServerExperienceConfig(id); + boolean nowEnabled = !config.levelUpMessagesEnabled(); + config.setLevelUpMessagesEnabled(nowEnabled); + saveServerExperienceConfig(config); + return nowEnabled; + } } diff --git a/src/main/java/de/voidtech/gerald/service/GeraldConfig.java b/src/main/java/de/voidtech/gerald/service/GeraldConfig.java index cd39c8fc..04d80091 100644 --- a/src/main/java/de/voidtech/gerald/service/GeraldConfig.java +++ b/src/main/java/de/voidtech/gerald/service/GeraldConfig.java @@ -99,7 +99,7 @@ public String getMemeApiURL() { } public String getExperienceCardApiURL() { - String url = config.getProperty("api.gavin_url"); + String url = config.getProperty("api.xp_url"); return url != null ? url : "http://localhost:3000/api/"; } } \ No newline at end of file diff --git a/src/main/java/de/voidtech/gerald/service/NitroliteService.java b/src/main/java/de/voidtech/gerald/service/NitroliteService.java index 22f5eba2..5d773898 100644 --- a/src/main/java/de/voidtech/gerald/service/NitroliteService.java +++ b/src/main/java/de/voidtech/gerald/service/NitroliteService.java @@ -109,7 +109,7 @@ public String constructEmoteString(NitroliteEmote emote) { private void sendWebhookMessage(Message message, String content) { Webhook webhook = webhookManager.getOrCreateWebhook((TextChannel) message.getChannel(), "BGNitrolite", message.getJDA().getSelfUser().getId()); - webhookManager.postMessage(content, message.getAuthor().getAvatarUrl(), message.getMember().getEffectiveName(), webhook); + webhookManager.postMessage(content, message.getReferencedMessage(), message.getAuthor().getAvatarUrl(), message.getMember().getEffectiveName(), webhook); } public List processNitroliteMessage(Message message) { diff --git a/src/main/java/de/voidtech/gerald/service/WebhookManager.java b/src/main/java/de/voidtech/gerald/service/WebhookManager.java index 82725de0..dec1c0ac 100644 --- a/src/main/java/de/voidtech/gerald/service/WebhookManager.java +++ b/src/main/java/de/voidtech/gerald/service/WebhookManager.java @@ -1,15 +1,6 @@ package main.java.de.voidtech.gerald.service; -import main.java.de.voidtech.gerald.commands.CommandContext; -import net.dv8tion.jda.api.Permission; -import net.dv8tion.jda.api.entities.GuildChannel; -import net.dv8tion.jda.api.entities.TextChannel; -import net.dv8tion.jda.api.entities.Webhook; -import org.json.JSONObject; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.net.ssl.HttpsURLConnection; +import java.awt.Color; import java.io.OutputStream; import java.net.URL; import java.util.EnumSet; @@ -17,6 +8,22 @@ import java.util.logging.Level; import java.util.logging.Logger; +import javax.net.ssl.HttpsURLConnection; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import main.java.de.voidtech.gerald.commands.CommandContext; +import net.dv8tion.jda.api.EmbedBuilder; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.entities.GuildChannel; +import net.dv8tion.jda.api.entities.Message; +import net.dv8tion.jda.api.entities.MessageEmbed; +import net.dv8tion.jda.api.entities.TextChannel; +import net.dv8tion.jda.api.entities.Webhook; + @Service public class WebhookManager { @@ -36,7 +43,7 @@ public Webhook getOrCreateWebhook(TextChannel targetChannel, String webhookName, return targetChannel.createWebhook(webhookName).complete(); } - private void executeWebhookPost(String content, String avatarUrl, String username, Webhook webhook) { + private void executeWebhookPost(String content, Message referencedMessage, String avatarUrl, String username, Webhook webhook) { String messageToBeSent = content.replaceAll("@everyone", "``@``everyone").replaceAll("@here", "``@``here"); JSONObject webhookPayload = new JSONObject(); @@ -44,8 +51,19 @@ private void executeWebhookPost(String content, String avatarUrl, String usernam webhookPayload.put("username", username); webhookPayload.put("avatar_url", avatarUrl); webhookPayload.put("tts", false); + + + if (referencedMessage != null) { + MessageEmbed replyTextEmbed = new EmbedBuilder() + .setTitle("Replying to this message", referencedMessage.getJumpUrl()) + .setDescription(referencedMessage.getContentRaw()) + .setColor(new Color(47,49,54)) + .setFooter("Original message from " + referencedMessage.getAuthor().getName(), referencedMessage.getAuthor().getAvatarUrl()) + .build(); + webhookPayload.put("embeds", new JSONArray().put(new JSONObject(replyTextEmbed.toData().toString()))); + } + try { - URL url = new URL(webhook.getUrl()); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); @@ -53,7 +71,7 @@ private void executeWebhookPost(String content, String avatarUrl, String usernam connection.addRequestProperty("User-Agent", "Barista-Gerald"); connection.setDoOutput(true); connection.setRequestMethod("POST"); - + OutputStream stream = connection.getOutputStream(); stream.write(webhookPayload.toString().getBytes()); stream.flush(); @@ -68,8 +86,8 @@ private void executeWebhookPost(String content, String avatarUrl, String usernam } } - public void postMessage(String content, String avatarUrl, String username, Webhook webhook) { - Runnable webhookThreadRunnable = () -> executeWebhookPost(content, avatarUrl, username, webhook); + public void postMessage(String content, Message referencedMessage, String avatarUrl, String username, Webhook webhook) { + Runnable webhookThreadRunnable = () -> executeWebhookPost(content, referencedMessage, avatarUrl, username, webhook); threadManager.getThreadByName("T-Webhook").execute(webhookThreadRunnable); } @@ -77,10 +95,7 @@ public void postMessageWithFallback(CommandContext context, String content, Stri EnumSet perms = context.getGuild().getSelfMember().getPermissions((GuildChannel) context.getChannel()); if (perms.contains(Permission.MANAGE_WEBHOOKS)) { - postMessage( - content, - avatarUrl, - username, + postMessage(content, null, avatarUrl, username, getOrCreateWebhook((TextChannel) context.getChannel(), webhookName, context.getJDA().getSelfUser().getId()) diff --git a/src/main/java/de/voidtech/gerald/util/ParsingUtils.java b/src/main/java/de/voidtech/gerald/util/ParsingUtils.java index 65120a16..2a5789f5 100644 --- a/src/main/java/de/voidtech/gerald/util/ParsingUtils.java +++ b/src/main/java/de/voidtech/gerald/util/ParsingUtils.java @@ -11,6 +11,35 @@ public class ParsingUtils { private static final Pattern HEX_PATTERN = Pattern.compile("^([a-fA-F0-9]{6})$"); + public static String convertSingleDigitToEmoji(String digit) { + switch (digit) { + case "0": + return ":zero:"; + case "1": + return ":one:"; + case "2": + return ":two:"; + case "3": + return ":three:"; + case "4": + return ":four:"; + case "5": + return ":five:"; + case "6": + return ":six:"; + case "7": + return ":seven:"; + case "8": + return ":eight:"; + case "9": + return ":nine:"; + case "10": + return ":ten:"; + default: + return ":zero:"; + } + } + public static boolean isInteger(String str) { if (str == null) { return false;