diff --git a/pom.xml b/pom.xml index bbdbb1a1b..836341252 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,11 @@ json 20220320 + + commons-validator + commons-validator + 1.7 + club.minnced discord-webhooks diff --git a/src/main/java/de/presti/ree6/commands/impl/community/Birthday.java b/src/main/java/de/presti/ree6/commands/impl/community/Birthday.java new file mode 100644 index 000000000..e10dd5c95 --- /dev/null +++ b/src/main/java/de/presti/ree6/commands/impl/community/Birthday.java @@ -0,0 +1,95 @@ +package de.presti.ree6.commands.impl.community; + +import de.presti.ree6.commands.Category; +import de.presti.ree6.commands.CommandEvent; +import de.presti.ree6.commands.interfaces.Command; +import de.presti.ree6.commands.interfaces.ICommand; +import de.presti.ree6.main.Main; +import net.dv8tion.jda.api.Permission; +import net.dv8tion.jda.api.interactions.commands.build.CommandData; +import org.apache.commons.validator.GenericValidator; + +/** + * This command is used to let the bot remember your Birthday. + */ +@Command(name = "birthday", description = "Let the bot remember your Birthday.", category = Category.COMMUNITY) +public class Birthday implements ICommand { + + /** + * @inheritDoc + */ + @Override + public void onPerform(CommandEvent commandEvent) { + if (commandEvent.isSlashCommand()) { + Main.getInstance().getCommandManager().sendMessage("This Command doesn't support slash commands yet.", commandEvent.getChannel(), commandEvent.getInteractionHook()); + return; + } + + if (commandEvent.getArguments().length == 1) { + if (commandEvent.getArguments()[0].equalsIgnoreCase("remove")) { + Main.getInstance().getSqlConnector().getSqlWorker().removeBirthday(commandEvent.getGuild().getId(), commandEvent.getMember().getId()); + Main.getInstance().getCommandManager().sendMessage("Your Birthday has been removed!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } else { + Main.getInstance().getCommandManager().sendMessage("Please use " + Main.getInstance().getSqlConnector().getSqlWorker().getSetting(commandEvent.getGuild().getId(), "chatprefix").getStringValue() + "birthday add/remove [Birthday(day.month.year)] [@User]", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } + if (commandEvent.getArguments().length == 2) { + if (commandEvent.getArguments()[0].equalsIgnoreCase("remove")) { + if (commandEvent.getMember().hasPermission(Permission.ADMINISTRATOR)) { + if (commandEvent.getMessage() != null && + commandEvent.getMessage().getMentions().getMembers().isEmpty()) { + Main.getInstance().getCommandManager().sendMessage("Please mention a user!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } else { + Main.getInstance().getSqlConnector().getSqlWorker().removeBirthday(commandEvent.getGuild().getId(), commandEvent.getMessage().getMentions().getMembers().get(0).getId()); + Main.getInstance().getCommandManager().sendMessage("The Birthday of <@" + commandEvent.getMessage().getMentions().getMembers().get(0).getId() + "> has been removed!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } else { + Main.getInstance().getCommandManager().sendMessage("You don't have the permission to remove a Birthday!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } else if (commandEvent.getArguments()[0].equalsIgnoreCase("add")) { + if (GenericValidator.isDate(commandEvent.getArguments()[1], "dd.MM.yyyy", true)) { + Main.getInstance().getSqlConnector().getSqlWorker().addBirthday(commandEvent.getGuild().getId(), commandEvent.getChannel().getId(), commandEvent.getMember().getId(), commandEvent.getArguments()[1]); + Main.getInstance().getCommandManager().sendMessage("Your Birthday has been added!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } else { + Main.getInstance().getCommandManager().sendMessage("Please use a valid Date!\nNote that we use the the format dd.MM.yyyy (day.month.year)!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } else { + Main.getInstance().getCommandManager().sendMessage("Please use " + Main.getInstance().getSqlConnector().getSqlWorker().getSetting(commandEvent.getGuild().getId(), "chatprefix").getStringValue() + "birthday add/remove [Birthday(day.month.year)] [@User]", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } else if (commandEvent.getArguments().length == 3) { + if (commandEvent.getArguments()[0].equalsIgnoreCase("add")) { + if (GenericValidator.isDate(commandEvent.getArguments()[1], "dd.MM.yyyy", true)) { + if (commandEvent.getMessage() != null && + commandEvent.getMessage().getMentions().getMembers().isEmpty()) { + Main.getInstance().getCommandManager().sendMessage("Please mention a user!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } else { + Main.getInstance().getSqlConnector().getSqlWorker().addBirthday(commandEvent.getGuild().getId(), commandEvent.getChannel().getId(), commandEvent.getMessage().getMentions().getMembers().get(0).getId(), commandEvent.getArguments()[1]); + Main.getInstance().getCommandManager().sendMessage("The Birthday of <@" + commandEvent.getMessage().getMentions().getMembers().get(0).getId() + "> has been added!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } else { + Main.getInstance().getCommandManager().sendMessage("Please use a valid Date!\nNote that we use the the format dd.MM.yyyy (day.month.year)!", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } else { + Main.getInstance().getCommandManager().sendMessage("Please use " + Main.getInstance().getSqlConnector().getSqlWorker().getSetting(commandEvent.getGuild().getId(), "chatprefix").getStringValue() + "birthday add/remove [Birthday(day.month.year)] [@User]", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } else { + Main.getInstance().getCommandManager().sendMessage("Please use " + Main.getInstance().getSqlConnector().getSqlWorker().getSetting(commandEvent.getGuild().getId(), "chatprefix").getStringValue() + "birthday add/remove [Birthday(day.month.year)] [@User]", 5, commandEvent.getChannel(), commandEvent.getInteractionHook()); + } + } + + /** + * @inheritDoc + */ + @Override + public CommandData getCommandData() { + return null; + } + + /** + * @inheritDoc + */ + @Override + public String[] getAlias() { + return new String[]{"bday"}; + } +} diff --git a/src/main/java/de/presti/ree6/main/Main.java b/src/main/java/de/presti/ree6/main/Main.java index 206b075b2..7e012b80e 100644 --- a/src/main/java/de/presti/ree6/main/Main.java +++ b/src/main/java/de/presti/ree6/main/Main.java @@ -17,14 +17,18 @@ import de.presti.ree6.utils.apis.Notifier; import de.presti.ree6.utils.data.ArrayUtil; import de.presti.ree6.utils.data.Config; +import de.presti.ree6.utils.others.ThreadUtil; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.entities.Activity; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.TextChannel; import net.dv8tion.jda.api.entities.VoiceChannel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.Calendar; import java.util.Date; /** @@ -82,11 +86,6 @@ public class Main { */ Config config; - /** - * A Thread used to check if a day has passed, and if so to clean the cache. - */ - Thread checker; - /** * String used to identify the last day. */ @@ -250,13 +249,6 @@ private void shutdown() { getNotifier().getTwitchClient().close(); instance.logger.info("[Main] Twitch API Instance closed!"); - if (checker != null && !checker.isInterrupted()) { - // Shutdown Checker Thread. - instance.logger.info("[Main] Interrupting the Checker thread!"); - checker.interrupt(); - instance.logger.info("[Main] Interrupted the Checker thread!"); - } - // Shutdown the Bot instance. instance.logger.info("[Main] JDA Instance shutdown init. !"); BotWorker.shutdown(); @@ -271,7 +263,7 @@ private void shutdown() { * Method creates a Thread used to create a Checker Thread. */ public void createCheckerThread() { - checker = new Thread(() -> { + ThreadUtil.createNewThread(x -> { while (BotWorker.getState() != BotState.STOPPED) { if (!lastDay.equalsIgnoreCase(new SimpleDateFormat("dd").format(new Date()))) { @@ -293,6 +285,22 @@ public void createCheckerThread() { instance.logger.info("[Stats] Guilds: {}", BotWorker.getShardManager().getGuilds().size()); instance.logger.info("[Stats] Overall Users: {}", BotWorker.getShardManager().getGuilds().stream().mapToInt(Guild::getMemberCount).sum()); instance.logger.info("[Stats] "); + + Calendar currentCalendar = Calendar.getInstance(); + + getSqlConnector().getSqlWorker() + .getBirthdays().stream().filter(birthday -> { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(birthday.getBirthdate()); + return calendar.get(Calendar.MONTH) == currentCalendar.get(Calendar.MONTH) && + calendar.get(Calendar.DAY_OF_MONTH) == currentCalendar.get(Calendar.DAY_OF_MONTH); + }).forEach(birthday -> { + TextChannel textChannel = BotWorker.getShardManager().getTextChannelById(birthday.getChannelId()); + + if (textChannel != null && textChannel.canTalk()) + textChannel.sendMessage("Happy birthday to <@" + birthday.getUserId() + ">!").queue(); + }); + lastDay = new SimpleDateFormat("dd").format(new Date()); } @@ -314,15 +322,8 @@ public void createCheckerThread() { getLogger().error("Error accessing the AudioPlayer.", ex); } } - - try { - Thread.sleep((10 * (60000L))); - } catch (InterruptedException exception) { - getInstance().getLogger().error("Checker Thread interrupted", exception); - } } - }); - checker.start(); + }, null, Duration.ofMinutes(1), true, false); } /** @@ -416,15 +417,6 @@ public Logger getAnalyticsLogger() { return analyticsLogger; } - /** - * Retrieve the Instance of the Checker-Thread. - * - * @return {@link Thread} Instance of the Checker-Thread. - */ - public Thread getChecker() { - return checker; - } - /** * Retrieve the Instance of the Config. * diff --git a/src/main/java/de/presti/ree6/sql/SQLConnector.java b/src/main/java/de/presti/ree6/sql/SQLConnector.java index 5e5785bdc..dd65c507c 100644 --- a/src/main/java/de/presti/ree6/sql/SQLConnector.java +++ b/src/main/java/de/presti/ree6/sql/SQLConnector.java @@ -12,6 +12,7 @@ import java.sql.*; import java.util.Base64; +import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -192,6 +193,8 @@ public StoredResultSet querySQL(String sqlQuery, Object... objcObjects) { preparedStatement.setObject(index++, SQLUtil.convertJSONToBlob(jsonElement), Types.BLOB); } else if (obj instanceof byte[] byteArray) { preparedStatement.setObject(index++, Base64.getEncoder().encodeToString(byteArray), Types.VARCHAR); + } else if (obj instanceof Date date) { + preparedStatement.setObject(index++, date.getTime(), Types.BIGINT); } } diff --git a/src/main/java/de/presti/ree6/sql/SQLWorker.java b/src/main/java/de/presti/ree6/sql/SQLWorker.java index cde846ceb..67fa8cecb 100644 --- a/src/main/java/de/presti/ree6/sql/SQLWorker.java +++ b/src/main/java/de/presti/ree6/sql/SQLWorker.java @@ -11,6 +11,7 @@ import de.presti.ree6.sql.base.data.SQLParameter; import de.presti.ree6.sql.base.data.SQLResponse; import de.presti.ree6.sql.base.data.SQLUtil; +import de.presti.ree6.sql.entities.BirthdayWish; import de.presti.ree6.sql.entities.Blacklist; import de.presti.ree6.sql.entities.Invite; import de.presti.ree6.sql.entities.Setting; @@ -27,6 +28,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.*; /** @@ -1784,6 +1787,76 @@ public void optIn(String guildId, String userId) { //endregion + //region Birthday + + /** + * Store the birthday of the user in the database + * @param guildId the ID of the Guild. + * @param channelId the ID of the Channel. + * @param userId the ID of the User. + * @param birthday the birthday of the user. + */ + public void addBirthday(String guildId, String channelId, String userId, String birthday) { + try { + if (isBirthdaySaved(guildId, userId)) { + BirthdayWish newBirthday = new BirthdayWish(guildId, channelId, userId, new SimpleDateFormat("dd.MM.yyyy").parse(birthday)); + updateEntity(getBirthday(guildId, userId), newBirthday, true); + } else { + saveEntity(new BirthdayWish(guildId, channelId, userId, new SimpleDateFormat("dd.MM.yyyy").parse(birthday))); + } + } catch (ParseException ignore) {} + } + + /** + * Check if there is any saved birthday for the given User. + * @param guildId the ID of the Guild. + * @param userId the ID of the User. + */ + public void removeBirthday(String guildId, String userId) { + if (isBirthdaySaved(guildId, userId)) { + sqlConnector.querySQL("DELETE FROM BirthdayWish WHERE GID=? AND UID=?", guildId, userId); + } + } + + /** + * Check if a birthday is saved for the given User. + * @param guildId the ID of the Guild. + * @param userId the ID of the User. + * @return {@link Boolean} as result. If true, there is data saved in the Database | If false, there is no data saved. + */ + public boolean isBirthdaySaved(String guildId, String userId) { + return sqlConnector.querySQL("SELECT * FROM BirthdayWish WHERE GID=? AND UID=?", guildId, userId).hasResults(); + } + + /** + * Get the birthday of the given User. + * @param guildId the ID of the Guild. + * @param userId the ID of the User. + * @return {@link BirthdayWish} as result. If true, there is data saved in the Database | If false, there is no data saved. + */ + public BirthdayWish getBirthday(String guildId, String userId) { + return (BirthdayWish) getEntity(BirthdayWish.class, "SELECT * FROM BirthdayWish WHERE GID=? AND UID=?", guildId, userId).getEntity(); + } + + /** + * Get all saved birthdays. + * @param guildId the ID of the Guild. + * @return {@link List} of {@link BirthdayWish} as result. If true, there is data saved in the Database | If false, there is no data saved. + */ + public List getBirthdays(String guildId) { + return getEntity(BirthdayWish.class, "SELECT * FROM BirthdayWish WHERE GID=?", guildId).getEntities().stream().map(BirthdayWish.class::cast).toList(); + } + + /** + * Get all saved birthdays. + * @return {@link List} of {@link BirthdayWish} as result. If true, there is data saved in the Database | If false, there is no data saved. + */ + public List getBirthdays() { + return getEntity(BirthdayWish.class, "SELECT * FROM BirthdayWish").getEntities().stream().map(BirthdayWish.class::cast).toList(); + } + + //endregion + //region Data delete /** diff --git a/src/main/java/de/presti/ree6/sql/base/data/SQLUtil.java b/src/main/java/de/presti/ree6/sql/base/data/SQLUtil.java index 6a7580862..baea63fd2 100644 --- a/src/main/java/de/presti/ree6/sql/base/data/SQLUtil.java +++ b/src/main/java/de/presti/ree6/sql/base/data/SQLUtil.java @@ -10,10 +10,7 @@ import java.io.InputStreamReader; import java.lang.reflect.Field; import java.sql.Blob; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.List; +import java.util.*; /** * SQLUtil class to help with SQL-Queries. @@ -70,7 +67,7 @@ public static String mapJavaToSQL(Class javaObjectClass) { javaObjectClass.isAssignableFrom(char.class)) { return "CHAR(1)"; } else if (javaObjectClass.isAssignableFrom(java.util.Date.class)) { - return "DATETIME"; + return "BIGINT"; } else if (javaObjectClass.isAssignableFrom(java.sql.Date.class)) { return "DATE"; } else if (javaObjectClass.isAssignableFrom(java.sql.Time.class)) { @@ -225,7 +222,7 @@ public static List getAllSQLParameter(Object entity, boolean onlyU * @param entityClass the Entity. * @param entityInstance the Entity instance. * @param onlyUpdateField if fields with the updateQuery value set to false should still be included. - * @param ignoreNull if null values should be ignored. + * @param ignoreNull if null values should be ignored. * @return {@link List} of {@link Object} as the {@link SQLParameter} value. */ public static List getValuesFromSQLEntity(Class entityClass, Object entityInstance, boolean onlyUpdateField, boolean ignoreNull) { @@ -241,22 +238,7 @@ public static List getValuesFromSQLEntity(Class entityClass, Object e return !onlyUpdateField || property.updateQuery(); }).map(field -> { try { - if (!field.canAccess(entityInstance)) field.trySetAccessible(); - - Property property = field.getAnnotation(Property.class); - - Object value = field.get(entityInstance); - - if (!property.keepOriginalValue()) { - if (value instanceof String valueString && - field.getType().isAssignableFrom(byte[].class)) { - value = Base64.getDecoder().decode(valueString); - } else if (value instanceof Blob blob) { - value = SQLUtil.convertBlobToJSON(blob); - } - } - - return value; + return getValueFromField(field, entityInstance); } catch (IllegalAccessException e) { throw new RuntimeException(e); } @@ -278,22 +260,7 @@ public static List getValuesFromSQLEntity(Class entityClass, Object e return !onlyUpdateField || property.updateQuery(); }).map(field -> { try { - if (!field.canAccess(entityInstance)) field.trySetAccessible(); - - Property property = field.getAnnotation(Property.class); - - Object value = field.get(entityInstance); - - if (!property.keepOriginalValue()) { - if (value instanceof String valueString && - field.getType().isAssignableFrom(byte[].class)) { - value = Base64.getDecoder().decode(valueString); - } else if (value instanceof Blob blob) { - value = SQLUtil.convertBlobToJSON(blob); - } - } - - return value; + return getValueFromField(field, entityInstance); } catch (IllegalAccessException e) { throw new RuntimeException(e); } @@ -308,8 +275,50 @@ public static List getValuesFromSQLEntity(Class entityClass, Object e return args; } + /** + * Get the value of the Field. + * + * @param field the Field. + * @param objectInstance the object instance. + * @return the value of the Field. + * @throws IllegalAccessException if the Field is not accessible. + */ + public static Object getValueFromField(Field field, Object objectInstance) throws IllegalAccessException { + if (!field.canAccess(objectInstance)) field.trySetAccessible(); + + Object value = field.get(objectInstance); + + value = mapCustomField(field, value); + + return value; + } + + + /** + * @param field the Field. + * @param currentValue the current value of the Field. + * @return the value of the Field. + */ + public static Object mapCustomField(Field field, Object currentValue) { + Property property = field.getAnnotation(Property.class); + if (property.keepOriginalValue()) return currentValue; + + if (currentValue instanceof String valueString && + field.getType().isAssignableFrom(byte[].class)) { + currentValue = Base64.getDecoder().decode(valueString); + } else if (currentValue instanceof Blob blob) { + currentValue = SQLUtil.convertBlobToJSON(blob); + } else if (currentValue instanceof Long longValue && + field.getType().isAssignableFrom(Date.class)) { + currentValue = new Date(longValue); + } + + return currentValue; + } + /** * Convert a Blob to a {@link JsonElement} + * * @param blob the Blob to convert. * @return the {@link JsonElement} or {@link JsonNull} if the Blob is null. */ @@ -335,6 +344,7 @@ public static JsonElement convertBlobToJSON(Blob blob) { /** * Convert a {@link JsonElement} to a Blob. + * * @param jsonElement the {@link JsonElement} to convert. * @return the Blob or null if the {@link JsonElement} is null. */ diff --git a/src/main/java/de/presti/ree6/sql/entities/BirthdayWish.java b/src/main/java/de/presti/ree6/sql/entities/BirthdayWish.java new file mode 100644 index 000000000..25e9a1b53 --- /dev/null +++ b/src/main/java/de/presti/ree6/sql/entities/BirthdayWish.java @@ -0,0 +1,95 @@ +package de.presti.ree6.sql.entities; + +import de.presti.ree6.sql.base.annotations.Property; +import de.presti.ree6.sql.base.annotations.Table; +import de.presti.ree6.sql.base.data.SQLEntity; + +import java.util.Date; + +/** + * This class is used to represent a Birthday-Wish, in our Database. + */ +@Table(name = "BirthdayWish") +public class BirthdayWish extends SQLEntity { + + /** + * The Guild ID. + */ + @Property(name = "gid") + String guildId; + + /** + * The Channel ID. + */ + @Property(name = "cid") + String channelId; + + /** + * The User ID. + */ + @Property(name = "uid") + String userId; + + /** + * The Birthday. + */ + @Property(name = "birthday", keepOriginalValue = false) + Date birthdate; + + /** + * Constructor. + */ + public BirthdayWish() { + } + + /** + * Constructor. + * + * @param guildId the Guild ID. + * @param channelId the Channel ID. + * @param userId the User ID. + * @param birthdate the Birthday. + */ + public BirthdayWish(String guildId, String channelId, String userId, Date birthdate) { + this.guildId = guildId; + this.channelId = channelId; + this.userId = userId; + this.birthdate = birthdate; + } + + /** + * Get the Guild ID. + * + * @return the Guild ID. + */ + public String getGuildId() { + return guildId; + } + + /** + * Get the Channel ID. + * + * @return the Channel ID. + */ + public String getChannelId() { + return channelId; + } + + /** + * Get the User ID. + * + * @return the User ID. + */ + public String getUserId() { + return userId; + } + + /** + * Get the Birthday. + * + * @return the Birthday. + */ + public Date getBirthdate() { + return birthdate; + } +} diff --git a/src/main/java/de/presti/ree6/sql/mapper/EntityMapper.java b/src/main/java/de/presti/ree6/sql/mapper/EntityMapper.java index 063046efc..68ef88cbe 100644 --- a/src/main/java/de/presti/ree6/sql/mapper/EntityMapper.java +++ b/src/main/java/de/presti/ree6/sql/mapper/EntityMapper.java @@ -8,9 +8,7 @@ import de.presti.ree6.sql.base.data.StoredResultSet; import java.lang.reflect.Field; -import java.sql.Blob; import java.util.ArrayList; -import java.util.Base64; /** * This class is used to map an SQL Result into the right Class-Entity. @@ -93,16 +91,7 @@ public void setAllFields(StoredResultSet.StoredData resultSet, Object entity, Fi try { if (!field.canAccess(entity)) field.trySetAccessible(); - Object value = resultSet.getValue(columnName); - - if (!property.keepOriginalValue()) { - if (value instanceof String valueString && - field.getType().isAssignableFrom(byte[].class)) { - value = Base64.getDecoder().decode(valueString); - } else if (value instanceof Blob blob) { - value = SQLUtil.convertBlobToJSON(blob); - } - } + Object value = SQLUtil.mapCustomField(field, resultSet.getValue(columnName)); field.set(entity, value); } catch (Exception e) {