member = event.getMessage().getMentionedMembers();
+ if (member.size() == 1) {
+ Role trainedRole = event.getGuild().getRoleById(BotMain.ROLES.get("Trained"));
+ if (trainedRole != null) {
+ try {
+ event.getGuild().addRoleToMember(member.get(0), trainedRole)
+ .queue(); // add the role to the User
+ MessageBuilder messageBuilder = new MessageBuilder();
+ messageBuilder.append(member.get(0)).append(" has the Role `Trained`");
+ channel.sendMessage(messageBuilder.build())
+ .queue(); // confirm to the User that the role was added
+ logger.info(
+ "Added Trained Role to " + BotEvents.getServerName(member.get(0)));
+ } catch (HierarchyException e) { // The Trained Role is above the Bot role
+ errorMessage = "Could not add the Role to User because Bot has his Role under the Trained role";
+ channel.sendMessage(errorMessage).queue(BotEvents::addTrashcan);
+ } catch (InsufficientPermissionException e) { // The Bot doesn't have permissions to add Roles
+ errorMessage = "Bot doesn't have the Permission Manage Roles";
+ channel.sendMessage(errorMessage).queue(BotEvents::addTrashcan);
+ }
+ } else { // The Trained Role ID is wrong in the config
+ errorMessage = "Can't find the Trained Role. Please update the role ID's";
+ channel.sendMessage(errorMessage).queue(BotEvents::addTrashcan);
+ }
+ } else if (member.size() == 0) { // No members mentioned
+ channel.sendMessage("You have to mention a User that is Trained").queue();
+ } else { // Too many members mentioned
+ channel.sendMessage(
+ "Can't mention multiple members at once, please mention one member at a time")
+ .queue(message -> BotEvents.deleteMessageAfterXTime(message, 10));
+ }
+ if (errorMessage != null) {
+ logger.error(errorMessage); // There was an Error so send it as well in the Log
+ }
+ } else // User isn't an Instructor
+ channel.sendMessage("You don't have permission for this command").queue();
+ }
+}
diff --git a/src/main/java/Commands/package-info.java b/src/main/java/Commands/package-info.java
new file mode 100644
index 0000000..ea227e8
--- /dev/null
+++ b/src/main/java/Commands/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * This Package contains all Commands available.
+ *
+ * Each Command (or command group) is an own Class
+ */
+package Commands;
\ No newline at end of file
diff --git a/src/main/java/Countdowns.java b/src/main/java/Countdowns.java
deleted file mode 100644
index 13622b6..0000000
--- a/src/main/java/Countdowns.java
+++ /dev/null
@@ -1,295 +0,0 @@
-import net.dv8tion.jda.api.JDA;
-import net.dv8tion.jda.api.entities.MessageChannel;
-import net.dv8tion.jda.api.entities.TextChannel;
-import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.time.Instant;
-import java.util.Stack;
-import java.util.concurrent.TimeUnit;
-
-public class Countdowns {
-
- /**
- * Stack of all the Countdown Threads
- */
- private static final Stack countdowns = new Stack<>();
- /**
- * Stack of all the Ids of the Countdown messages
- */
- public static final Stack messageIds = new Stack<>();
- /**
- * The Logger for Log Messages
- */
- private static final Logger logger = LoggerFactory.getLogger("Countdown");
-
- /**
- * Start a new Countdown with the given Information in the Event
- * @param event The Event with the Countdown Information
- */
- public static void startNewCountdown(MessageReceivedEvent event) {
- logger.debug("detected countdown command");
- String content = event.getMessage().getContentRaw();
- MessageChannel channel = event.getChannel();
- try {
- channel.deleteMessageById(event.getMessageId()).queue();
- content = content.substring(11);
-
- // take the time information of the countdown
- int day = Integer.parseInt(content.substring(0, content.indexOf('.')));
- content = content.substring(content.indexOf('.') + 1);
- int month = Integer.parseInt(content.substring(0, content.indexOf('.')));
- content = content.substring(content.indexOf('.') + 1);
- int year = Integer.parseInt(content.substring(0, content.indexOf(' ')));
- content = content.substring(content.indexOf(' ') + 1);
- int hour = Integer.parseInt(content.substring(0, content.indexOf(':')));
- content = content.substring(content.indexOf(':') + 1);
- int minutes;
-
- // take the extra text that needs to be displayed after the countdown
- if(content.length() <= 2) {
- minutes = Integer.parseInt(content);
- content = "";
- } else {
- minutes = Integer.parseInt(content.substring(0, content.indexOf(' ')));
- content = content.substring(content.indexOf(' '));
- }
-
- // Parse the date to a Instant Instance
- Instant date = Instant.parse(year + "-" + String.format("%02d", month) + "-" +
- String.format("%02d", day) + "T" + String.format("%02d", hour) + ":" +
- String.format("%02d", minutes) + ":00Z");
-
- // Check if the time is not in th past
- if(date.getEpochSecond() < Instant.now().getEpochSecond()) {
- channel.sendMessage("You tried making a countdown in the past. (Message will delete after 10 sec)")
- .queue(message -> BotEvents.deleteMessageAfterXTime(message, 10));
- logger.warn("User tried making a countdown in the past");
- return;
- }
- logger.info("Starting countdown thread");
- // start the Thread that changes the Message to the current Countdown
- CountdownsThread countdownsThread = new CountdownsThread(channel, content, date);
- countdownsThread.start();
- countdowns.push(countdownsThread);
- } catch (NumberFormatException | StringIndexOutOfBoundsException ignored) { // The command had some parsing error
- channel.sendMessage("Something went wrong with your command try again\n" +
- "Format is: `!countdown DD.MM.YYYY HH:mm `").queue();
- }
- }
-
- /**
- * Restarts the Countdowns that are in the Countdowns.cfg file
- * @param jda The JDA to get the Channels and Messages
- */
- public static void restartCountdowns(JDA jda) {
- Config.getCountdowns().forEach(countdownInfos -> {
- // Pick up the channel where the Message is
- TextChannel channel = jda.getTextChannelById(countdownInfos[0]);
- if(channel != null) { // check if the Channel is still there, if not ignore this Countdown
- channel.retrieveMessageById(countdownInfos[1]).queue(message -> { // message still exists
- Instant date = Instant.parse(countdownInfos[3]);
- if(date.getEpochSecond() < Instant.now().getEpochSecond()) { // check if the Countdown is in the past
- message.editMessage("Countdown finished").queue();
- BotEvents.addTrashcan(message); // add a reaction to make it easy to delete the post
- } else {
- logger.info("Added Countdown Thread from existing Countdown");
- CountdownsThread countdownsThread =
- new CountdownsThread(channel, countdownInfos[1], countdownInfos[2], date);
- countdownsThread.start();
- countdowns.push(countdownsThread);
- }}, throwable -> logger.warn("Removing one Countdown where Message is deleted")); // If the message is deleted
- } else {
- logger.warn("Removing one Countdown where Channel is deleted");
- }
- });
- }
-
- /**
- * Save all active Countdowns in the Countdowns.cfg
- */
- public static void saveAllCountdowns() {
- Config.saveCountdowns(countdowns);
- }
-
- /**
- * Close a specific Countdown with its Message ID
- * @param messageId The Message ID of the Countdown that needs to be closed
- */
- public static void closeSpecificThread(String messageId) {
- int index = messageIds.indexOf(messageId);
- if(index == -1) return;
- CountdownsThread thread = countdowns.get(index);
- thread.interrupt();
- countdowns.remove(index);
- messageIds.remove(index);
- }
-
- /**
- * Close all Thread and save them in Countdowns.cfg
- */
- public static void closeAllThreads() {
- saveAllCountdowns();
- countdowns.forEach(Thread::interrupt);
- }
-
- /**
- * Thread Class for the Countdowns
- */
- static class CountdownsThread extends Thread {
-
- /**
- * Lock for {@link #stop}
- */
- private final Object lock = new Object();
- /**
- * boolean to know if the Thread should be closed
- */
- private boolean stop = false;
-
- /**
- * The Channel where the Message of the Countdown is
- */
- private final MessageChannel channel;
- /**
- * The Message ID of the Countdown
- */
- private String messageId;
- /**
- * The Text that is added at the end of the Countdown
- */
- private final String text;
- /**
- * The Date of the End of the Countdown
- */
- private final Instant date;
-
- /**
- * Constructor when the Countdown is restored after a restart
- * @param channel The Channel where the Message of the Countdown is
- * @param messageId The Message ID of the Countdown
- * @param text The Text that is added at the end of the Countdown
- * @param date The Date of the End of the Countdown
- */
- private CountdownsThread(MessageChannel channel, String messageId, String text, Instant date) {
- this.text = text;
- this.date = date;
- this.channel = channel;
- this.messageId = messageId;
- messageIds.push(this.messageId);
- }
-
- /**
- * Constructor for a new Countdown
- * @param channel The Channel where the Message of the Countdown will be
- * @param text The Text that is added at the end of the Countdown
- * @param date The Date of the End of the Countdown
- */
- private CountdownsThread(MessageChannel channel, String text, Instant date) {
- this.text = text;
- this.date = date;
- this.channel = channel;
-
- logger.info("sending message");
- channel.sendMessage(computeLeftTime()[0] + text).queue(message -> {
- synchronized (lock) {
- this.messageId = message.getId();
- messageIds.push(this.messageId);
- lock.notify(); // notify the Thread that the Message Id is available
- }
- });
- }
-
- /**
- * Compute the String Array with the information for the Countdowns.cfg File
- * @return A String Array with following layout: {@code {channelId, messageId, text, date}}
- */
- public String[] getInfos() {
- return new String[]{channel.getId(), messageId, text, date.toString()};
- }
-
- /**
- * The main Function of this Thread
- */
- @Override
- public void run() {
- synchronized (lock) {
- if (messageId == null) { // If don't yet have a message ID wait for it
- try {
- lock.wait();
- } catch (InterruptedException ignored) {
- }
- }
- }
- while(!stop) { // don't stop until wanted
- Object[] info = computeLeftTime();
- if(info[0] instanceof Boolean) { // if this countdown is at it's end
- logger.info("Countdown finished removing it from the Threads List");
- channel.editMessageById(messageId, "Countdown finished")
- .queue(BotEvents::addTrashcan, // add a reaction to make it easy to delete the post
- throwable -> logger.warn("Removing one Countdown where Message is deleted")); // Message was deleted, don't do anything here
- countdowns.remove(this);
- return;
- }
- logger.info("editing message: " + info[0]);
- channel.editMessageById(messageId, info[0] + text).queue(message -> {}, throwable -> {
- // Message was deleted, delete this Thread
- stop = true;
- countdowns.remove(this);
- logger.warn("Removing one Countdown where Message is deleted");
- });
- try{
- long sleepTime = (Long) info[1]; // sleep until the next change
- //noinspection BusyWait
- sleep(sleepTime < 5000?60000:sleepTime);
- } catch (InterruptedException ignored) {}
- }
- }
-
- @Override
- public void interrupt() {
- stop = true;
- super.interrupt();
- }
-
- /**
- * Compute the time until the next change
- * @return An Array with: {@code {String countdownMessage, Long timeUntilNextChange}}
- */
- private Object[] computeLeftTime() {
- long differenceOG = date.getEpochSecond() - Instant.now().getEpochSecond() ;
- long dayDiff = TimeUnit.DAYS.convert(differenceOG, TimeUnit.SECONDS);
- long differenceHour = differenceOG - TimeUnit.SECONDS.convert(dayDiff, TimeUnit.DAYS);
- long hourDiff = TimeUnit.HOURS.convert(differenceHour, TimeUnit.SECONDS);
- long differenceMinutes = differenceHour - TimeUnit.SECONDS.convert(hourDiff, TimeUnit.HOURS);
- long minutesDiff = TimeUnit.MINUTES.convert(differenceMinutes, TimeUnit.SECONDS);
- long differenceSeconds = differenceMinutes - TimeUnit.SECONDS.convert(minutesDiff, TimeUnit.MINUTES);
-
- if(dayDiff > 7) {
- if(dayDiff % 7 == 0)
- return new Object[]{(dayDiff / 7) + " weeks left",
- TimeUnit.MILLISECONDS.convert(differenceHour, TimeUnit.SECONDS)};
- return new Object[]{(dayDiff / 7) + " weeks and " + (dayDiff % 7) + " days left",
- TimeUnit.MILLISECONDS.convert(differenceHour, TimeUnit.SECONDS)};
- }
- if(dayDiff > 3 || dayDiff > 0 && hourDiff == 0)
- return new Object[]{dayDiff + " days left",
- TimeUnit.MILLISECONDS.convert(differenceHour, TimeUnit.SECONDS)};
- if(dayDiff > 0)
- return new Object[]{dayDiff + " days and " + hourDiff + " left",
- TimeUnit.MILLISECONDS.convert(differenceMinutes, TimeUnit.SECONDS)};
- if(hourDiff > 6 || hourDiff > 0 && minutesDiff == 0)
- return new Object[]{hourDiff + " hours left",
- TimeUnit.MILLISECONDS.convert(differenceMinutes, TimeUnit.SECONDS)};
- if(hourDiff > 0)
- return new Object[]{hourDiff + " hours and " + minutesDiff + " minutes left",
- TimeUnit.MILLISECONDS.convert(differenceSeconds, TimeUnit.SECONDS)};
- if(minutesDiff > 0)
- return new Object[]{minutesDiff + " minutes left",
- TimeUnit.MILLISECONDS.convert(differenceSeconds, TimeUnit.SECONDS)};
- return new Object[]{true};
- }
- }
-
-}
diff --git a/src/main/java/EmbedMessages.java b/src/main/java/EmbedMessages.java
deleted file mode 100644
index c6b3f29..0000000
--- a/src/main/java/EmbedMessages.java
+++ /dev/null
@@ -1,110 +0,0 @@
-import net.dv8tion.jda.api.EmbedBuilder;
-
-import java.awt.*;
-import java.time.Instant;
-
-public class EmbedMessages {
-
- /**
- * Build the basic Help Page
- * @return The Basic Help Page, still needs to be Build
- */
- public static EmbedBuilder getHelpPage() {
- EmbedBuilder eb = new EmbedBuilder();
-
- eb.setColor(Color.YELLOW);
-
- eb.setTitle("Commands:");
-
- eb.setDescription("List off all known Commands");
-
- eb.addField("`!help`", "shows this page", true);
-
- eb.addField("`!timezones`", "Lists all Timezones with their Users", true);
-
- eb.addField("`!time`", """
- Will show the local time of the given Users.
- The Time is calculated using the Zulu offset of the Nickname.
- Users can be given as Tags or as a `,` separated List of Names.
- When Names are given, only a partial match is necessary.
- Example layouts:
- `!time User, User1`
- `!time @User @User2`""", false);
-
- eb.setTimestamp(Instant.now());
-
- return eb;
- }
-
- /**
- * Adds the Instructor Portion to the Help Page
- * @param eb the already pre-build Help Page
- */
- public static void getInstructor(EmbedBuilder eb) {
- eb.setDescription(eb.getDescriptionBuilder().append(" with commands for Instructors"));
-
- eb.addBlankField(false);
-
- eb.addField("Instructor Commands", "Commands that are only for Instructors", false);
-
- eb.addField("`!trained`", """
- Will add the Role `Trained` the mentioned Member.
- The mentioned Member must be mentioned with a Tag.
- Only one Member at a time can be mentioned with the Command.
- Syntax:
- `!trained @User`""", false);
- }
-
- /**
- * Adds the Event Organizer Portion to the Help Page
- * @param eb the already pre-build Help Page
- */
- public static void getEventOrganizer(EmbedBuilder eb) {
- StringBuilder desc = eb.getDescriptionBuilder();
- if(desc.toString().contains("with commands for"))
- eb.setDescription(desc.append(" and Event Organizers"));
- else
- eb.setDescription(desc.append(" with commands for Event Organizers"));
-
- eb.addBlankField(false);
-
- eb.addField("Event Organizer Commands", "Commands that are only for event organizers", false);
-
- eb.addField("`!countdown`", """
- adds a countdown to the next event in the welcome channel
- Syntax:
- `!countdown DD.MM.YYYY HH:mm `
- The time is always in UTC""", false);
- }
-
- /**
- * Adds the Admin Portion to the Help page
- * @param eb the already pre-build Help Page
- */
- public static void getAdminHelpPage(EmbedBuilder eb) {
- StringBuilder desc = eb.getDescriptionBuilder();
- if(desc.toString().contains("with commands for"))
- eb.setDescription(desc.append(" and Admins"));
- else
- eb.setDescription(desc.append(" with commands for Admins"));
-
- eb.addBlankField(false);
-
- eb.addField("Admin Commands", "Commands that are only for admins:", false);
-
- eb.addField("`!restart`", "restarts the bot", true);
-
- //eb.addField("!stop", "stops the bot", true);
-
- eb.addField("`!reload XY`", """
- reloads all config files
- Following arguments are available:
- `config`, `timezones`""", true);
-
-// eb.addField("`!purge`", """
-// purges the given amount of Messages from the channel not including the command.
-// Layout:
-// `!purge 10`
-// `!purge all`""", true);
- }
-}
diff --git a/src/main/java/Timezones.java b/src/main/java/Timezones.java
deleted file mode 100644
index 053f872..0000000
--- a/src/main/java/Timezones.java
+++ /dev/null
@@ -1,168 +0,0 @@
-import net.dv8tion.jda.api.JDA;
-import net.dv8tion.jda.api.entities.Guild;
-import net.dv8tion.jda.api.entities.Member;
-import net.dv8tion.jda.api.entities.User;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.*;
-
-public class Timezones {
-
- /**
- * Map with all the Timezones of the Users
- */
- private static Map timezones = new HashMap<>();
- /**
- * The Logger for Log Messages
- */
- private static final Logger logger = LoggerFactory.getLogger("Timezones");
-
- /**
- * Update the Timezone of every User of the Guild
- * @param jda The JDA Instance of the Bot
- */
- public static void updateTimezones(JDA jda) {
- Guild guild = jda.getGuildById(BotMain.ROLES.get("Guild"));
- if(guild != null) {
- Map timezonesTemp = new HashMap<>();
- List members = guild.getMembers();
- members.forEach(member -> {
- User user = member.getUser();
- String nickname = BotEvents.getServerName(member);
- nickname = nickname.toLowerCase(Locale.ROOT);
- if(!user.isBot() && !nickname.contains("[alt") && nickname.contains("[z")) {
- try {
- String offset = getTimezone(nickname.substring(nickname.indexOf("[z")));
- timezonesTemp.put(member.getIdLong(), offset);
- logger.info("Updated Timezone of User: " + member.getNickname());
- } catch (NumberFormatException ignored) {
- if(timezones.containsKey(member.getIdLong()))
- timezonesTemp.put(member.getIdLong(), timezones.get(member.getIdLong()));
- logger.error("Could not save Timezone of User: " + member.getNickname());
- }
- }
- });
- timezones = timezonesTemp;
- } else {
- logger.error("Bot is not on the specified Server");
- }
- }
-
- /**
- * Will update the Timezone of a User
- * @param userId The ID of the User
- * @param timezone The new Timezone String of the User in following format: [Z+/-0]
- * @return {@code true} if the update was successful or {@code false} if Timezone was not correctly parsed
- */
- public static boolean updateSpecificTimezone(long userId, String timezone) {
- try {
- timezones.put(userId, getTimezone(timezone));
- } catch (NumberFormatException ignored) {
- timezones.remove(userId);
- return false;
- }
- return true;
- }
-
- public static void saveTimezones() {
- Config.saveTimezones(timezones);
- }
-
- public static void loadTimezones() {
- timezones = Config.getTimezones();
- }
-
- /**
- * Return the timezone offset from the String
- * @param timezone String with the following format: [Z+/-0]
- * @return The offset
- * @throws NumberFormatException when the Timezone couldn't be parsed
- */
- private static String getTimezone(String timezone) throws NumberFormatException {
- try {
- String offset = timezone.substring(timezone.indexOf('z') + 1, timezone.indexOf(']'));
- if(offset.contains("--") || offset.contains("++"))
- offset = offset.substring(2);
- else if(offset.equals("+-0") || offset.equals("-+0") ||
- offset.equals("+/-0") || offset.equals("-/+0") ||
- offset.equals("±0"))
- offset = "0";
- else if(offset.contains(":")) {
- if(offset.indexOf(':') == 2)
- offset = offset.charAt(0) + "0" + offset.substring(1);
- }
- float offsetNumber;
- if(offset.contains(":")) {
- offsetNumber = Integer.parseInt(offset.substring(0, offset.indexOf(':'))) + Integer.parseInt(offset.substring(offset.indexOf(':') + 1)) / 60f;
- } else {
- offsetNumber = Integer.parseInt(offset);
- }
- if(offsetNumber > 18 || offsetNumber < -18)
- throw new NumberFormatException();
- return offset; // this is done to confirm that the offset is a valid number
- } catch (IndexOutOfBoundsException ignored) {
- throw new NumberFormatException();
- }
- }
-
- private static final DateTimeFormatter sdf = DateTimeFormatter.ofPattern("EEE HH:mm").withLocale(Locale.ENGLISH);
-
- public static String printUserLocalTime(long memberID, Guild guild) {
- Member member = guild.getMemberById(memberID);
- if(member != null) {
- String name = BotEvents.getServerName(member);
- if (timezones.containsKey(memberID)) {
- String timezone = timezones.get(memberID);
- ZonedDateTime localTime = ZonedDateTime.now(ZoneOffset.of(timezone));
- return "It is currently " + localTime.format(sdf) + " (Z" + timezone + ") for " + name;
- } else {
- return name + " does not have a Timezone set.";
- }
- } else {
- logger.error("Could not find user with ID: " + memberID);
- return "";
- }
- }
-
- public static String printAllUsers(Guild guild) {
- Map> timezoneGroups = new HashMap<>();
- timezones.forEach((memberID, offset) -> {
- ArrayList members;
- if(timezoneGroups.containsKey(offset)) {
- members = timezoneGroups.get(offset);
- } else {
- members = new ArrayList<>();
- timezoneGroups.put(offset, members);
- }
- Member member = guild.getMemberById(memberID);
- if(member != null) {
- members.add(BotEvents.getServerName(member));
- }
- });
-
- Map sortedTimezones = new TreeMap<>();
- timezoneGroups.forEach((offset, list) -> {
- float offsetNumber;
- if(offset.contains(":")) {
- offsetNumber = Integer.parseInt(offset.substring(0, offset.indexOf(':'))) + Integer.parseInt(offset.substring(offset.indexOf(':') + 1)) / 60f;
- } else {
- offsetNumber = Integer.parseInt(offset);
- }
- sortedTimezones.put(offsetNumber, offset);
- });
-
- StringBuilder output = new StringBuilder("```\n");
- for(Map.Entry entry: sortedTimezones.entrySet()) {
- ZonedDateTime localTime = ZonedDateTime.now(ZoneOffset.of(entry.getValue()));
- output.append(localTime.format(sdf)).append(" (Z").append(entry.getValue()).append("):\n");
- timezoneGroups.get(entry.getValue()).forEach(member -> output.append("\t").append(member).append("\n"));
- output.append("\n");
- }
- return output.append("```").toString();
- }
-
-}