Java tutorial
/** * Copyright 2015-2016 Austin Keener * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.dv8tion.discord.commands; import net.dv8tion.discord.util.Database; import net.dv8tion.jda.core.JDA; import net.dv8tion.jda.core.MessageBuilder; import net.dv8tion.jda.core.entities.Message; import net.dv8tion.jda.core.entities.User; import net.dv8tion.jda.core.events.message.MessageReceivedEvent; import org.apache.commons.lang3.StringUtils; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; public class TodoCommand extends Command { //Database Methods public static final String ADD_TODO_LIST = "addTodoList"; public static final String ADD_TODO_ENTRY = "addTodoEntry"; public static final String ADD_TODO_USER = "addTodoUser"; public static final String GET_TODO_LISTS = "getTodoLists"; public static final String GET_TODO_ENTRIES = "getTodoEntries"; public static final String GET_TODO_USERS = "getTodoUsers"; public static final String SET_TODO_LIST_LOCKED = "setTodoListLocked"; public static final String SET_TODO_ENTRY_CHECKED = "setTodoEntryChecked"; public static final String SET_TODO_ENTRIES_CHECKED = "setTodoEntriesChecked"; public static final String REMOVE_TODO_LIST = "removeTodoList"; public static final String REMOVE_TODO_ENTRY = "removeTodoEntry"; public static final String REMOVE_TODO_USER = "removeTodoUser"; private JDA api; private HashMap<String, TodoList> todoLists = new HashMap<>(); public TodoCommand(JDA api) { this.api = api; try { ResultSet sqlTodoLists = Database.getInstance().getStatement(GET_TODO_LISTS).executeQuery(); while (sqlTodoLists.next()) { String label = sqlTodoLists.getString(2); TodoList todoList = new TodoList(sqlTodoLists.getInt(1), //Id label, sqlTodoLists.getString(3), //OwnerId sqlTodoLists.getBoolean(4) //Locked ); todoLists.put(label, todoList); PreparedStatement getEntries = Database.getInstance().getStatement(GET_TODO_ENTRIES); getEntries.setInt(1, todoList.id); ResultSet sqlTodoEntries = getEntries.executeQuery(); while (sqlTodoEntries.next()) { TodoEntry todoEntry = new TodoEntry(sqlTodoEntries.getInt(1), //Id sqlTodoEntries.getString(2), //Content sqlTodoEntries.getBoolean(3) //Checked ); todoList.entries.add(todoEntry); } getEntries.clearParameters(); PreparedStatement getUsers = Database.getInstance().getStatement(GET_TODO_USERS); getUsers.setInt(1, todoList.id); ResultSet sqlTodoUsers = getUsers.executeQuery(); while (sqlTodoUsers.next()) { todoList.allowedUsers.add(sqlTodoUsers.getString(1)); //UserId } getUsers.clearParameters(); } } catch (SQLException e) { e.printStackTrace(); } } @Override public void onCommand(MessageReceivedEvent e, String[] args) { try { checkArgs(args, 1, "No Action argument was provided. Please use `.help " + getAliases().get(0) + "` for more information."); switch (args[1].toLowerCase()) { case "show": handleShow(e, args); break; case "lists": handleLists(e, args); break; case "create": handleCreate(e, args); break; case "add": handleAdd(e, args); break; case "mark": case "check": handleCheck(e, args, true); break; case "unmark": case "uncheck": handleCheck(e, args, false); break; case "lock": handleLock(e, args, true); break; case "unlock": handleLock(e, args, false); break; case "users": handleUsers(e, args); break; case "clear": handleClear(e, args); break; case "remove": handleRemove(e, args); break; default: sendMessage(e, "Unknown Action argument: `" + args[1] + "` was provided. " + "Please use `.help " + getAliases().get(0) + "` for more information."); } } catch (SQLException e1) { sendMessage(e, "An SQL error occured while processing command.\nError Message: " + e1.getMessage()); e1.printStackTrace(); } catch (IllegalArgumentException e2) { sendMessage(e, e2.getMessage()); } } @Override public List<String> getAliases() { return Arrays.asList(".todo"); } @Override public String getDescription() { return "Used to create todo lists that can be checked off as things are completed."; } @Override public String getName() { return "Todo Command"; } @Override public List<String> getUsageInstructions() { return Arrays.asList(String.format("%1$s [Action] <Action Arguments>\n" + "__Actions:__\n" + "\n" + "__**show [ListName]** - Shows all todo entries in the [ListName] TodoList.__\n" + " Example: `%1$s show shopping-list` would display all entries in the `shopping-list` list.\n" + "\n" + "__**lists <Mentions...>** - Displays the todo lists owned by the provided user(s).__\n" + " Example 1: `%1$s lists` Displays lists owned by the User that executed the command.\n" + " Example 2: `%1$s lists @DV8FromTheWorld` Displays lists owned by DV8FromTheWorld.\n" + "\n" + "__**create [ListName]** - Creates a new todo list with name [ListName]__\n" + " Example: `%1$s list project5` would create a todo list with the name `project5`\n" + "\n" + "__**add [ListName] [Content...]** - Adds a todo entry to the [ListName] todo list.__\n" + " Example: `%1$s add project5 Fix bug where Users can delete System32`\n" + "\n" + "__**mark/unmark [TodoList] [EntryIndex]** - Marks a todo entry as **complete** or *incomplete**.__\n" + " Example 1: `%1$s mark project5 2` Marks the second entry in the project5 list as compelted.\n" + " Example 2: `%1$s unmark project5 3` Marks the third entry in the project5 list as incomplete.\n" + " Example 3: `%1$s mark project5 *` Marks **all** todo entries in the project5 list as completed.\n" + " **Note:** You can also use `check` and `uncheck`.\n" + "\n" + "__**lock/unlock [ListName]** - Used to lock a todo list such that only Auth'd users can modify it.__\n" + " Example 1: `%1$s lock project5` Locks the project5 list such that only Auth'd users can use `add`,`mark` and `clear`\n" + " Example 2: `%1$s unlock project5` Unlocks the project5 list so that all users can modify it.\n", getAliases().get(0)), //Second Usage Message String.format( "__**users [SubAction] [ListName] <SubAction Args>** Used add, remove and list the Auth'd users for a todo list.__\n" + " __SubActions__:\n" + "\n" + " __**add [ListName] [@mentions...]** Adds the mentions users to the Auth'd users for ListName list.__\n" + " Example: `%1$s users add project5 @Joe @DudeMan` Adds Joe and DudeMan Auth'd users for the project5 list.\n" + " __**remove [ListName] [@mentions...]** Removes the mentioned users from the Auth'd users for ListName list.__\n" + " Example: `%1$s users remove project5 @MrCatMan` Removes MrCatMan from the Auth'd users for the project5 list.\n" + " __**list [ListName]** Lists the Owner and Auth'd users for the ListName list.__\n" + " Example: `%1$s users list project5` Lists the owner and all Auth'd users for the project5 list.\n" + "\n" + "__**clear [ListName]** - Clears all **completed** todo entries from a list.__\n" + " Example: `%1$s clear project5` Clears all **completed** todo entries in the project5 list\n" + "\n" + "__**remove [ListName]** - Completely deletes the ListName list. Only the list owner can do this.__\n" + " Example: `%1$s remove project5` Completely deletes the project5 todo list.\n", getAliases().get(0))); } //alias show [ListName] private void handleShow(MessageReceivedEvent e, String[] args) { checkArgs(args, 2, "No todo ListName was specified. Usage: `" + getAliases().get(0) + " show [ListName]`"); String label = args[2].toLowerCase(); TodoList todoList = todoLists.get(label); if (todoList == null) { sendMessage(e, "Sorry, `" + label + "` isn't a known todo list."); return; } // Discord messages can only be 2000 characters. List<Message> todoMessages = new ArrayList<Message>(); MessageBuilder builder = new MessageBuilder(); builder.append("__Todo for: `" + label + "`__\n"); for (int i = 0; i < todoList.entries.size(); i++) { TodoEntry todoEntry = todoList.entries.get(i); String todoEntryString = todoEntry.content; if (todoEntry.checked) todoEntryString = "~~" + todoEntryString + "~~"; todoEntryString = (i + 1) + ") " + todoEntryString + "\n"; if (builder.length() + todoEntryString.length() > 2000) { todoMessages.add(builder.build()); builder = new MessageBuilder(); } builder.append(todoEntryString); } todoMessages.forEach(message -> sendMessage(e, message)); sendMessage(e, builder.build()); } //alias lists //alias lists [mentions...] private void handleLists(MessageReceivedEvent e, String[] args) { List<User> mentionedUsers = e.getMessage().getMentionedUsers(); if (mentionedUsers.size() == 0) mentionedUsers = Collections.singletonList(e.getAuthor()); List<Message> messages = new LinkedList<Message>(); for (User u : mentionedUsers) { MessageBuilder builder = new MessageBuilder(); List<TodoList> lists = todoLists.values().stream().filter(list -> list.ownerId.equals(u.getId())) .collect(Collectors.toList()); builder.append("__" + u.getName() + " owns **" + lists.size() + "** todo lists.__\n"); for (TodoList list : lists) { String listString = " - " + list.labelName + "\n"; if (builder.length() + listString.length() > 2000) { messages.add(builder.build()); builder = new MessageBuilder(); } builder.append(listString); } messages.add(builder.build()); } messages.forEach(msg -> sendMessage(e, msg)); } //alias create [ListName] private void handleCreate(MessageReceivedEvent e, String[] args) throws SQLException { checkArgs(args, 2, "No ListName for the new todo list was provided. Usage: `" + getAliases().get(0) + " create [ListName]`"); String label = args[2].toLowerCase(); TodoList todoList = todoLists.get(label); if (todoList != null) { sendMessage(e, "A todo list already exists with the name `" + label + "`."); return; } PreparedStatement addTodoList = Database.getInstance().getStatement(ADD_TODO_LIST); addTodoList.setString(1, label); //Label addTodoList.setString(2, e.getAuthor().getId());//OwnerId addTodoList.setBoolean(3, false); //Locked if (addTodoList.executeUpdate() == 0) throw new SQLException(ADD_TODO_LIST + " reported no modified rows!"); todoList = new TodoList(Database.getAutoIncrement(addTodoList, 1), label, e.getAuthor().getId(), false); todoLists.put(label, todoList); addTodoList.clearParameters(); sendMessage(e, "Created `" + label + "` todo list. Use `" + getAliases().get(0) + " add " + label + " [content...]` " + "to add entries to this todo list."); } //alias add [ListName] [Content ... ] private void handleAdd(MessageReceivedEvent e, String[] args) throws SQLException { checkArgs(args, 2, "No todo ListName was specified. Usage: `" + getAliases().get(0) + " add [ListName] [content...]`"); checkArgs(args, 3, "No content was specified. Cannot create an empty todo entry!" + "Usage: `" + getAliases().get(0) + " add [ListName] [content...]`"); String label = args[2].toLowerCase(); String content = StringUtils.join(args, " ", 3, args.length); TodoList todoList = todoLists.get(label); if (todoList == null) { sendMessage(e, "Sorry, `" + label + "` isn't a known todo list. " + "Try using `" + getAliases().get(0) + " create " + label + "` to create a new list by this name."); return; } if (todoList.locked && !todoList.isAuthUser(e.getAuthor())) { sendMessage(e, "Sorry, `" + label + "` is a locked todo list and you do not have permission to modify it."); return; } PreparedStatement addTodoEntry = Database.getInstance().getStatement(ADD_TODO_ENTRY); addTodoEntry.setInt(1, todoList.id); addTodoEntry.setString(2, content); addTodoEntry.setBoolean(3, false); if (addTodoEntry.executeUpdate() == 0) throw new SQLException(ADD_TODO_ENTRY + " reported no modified rows!"); todoList.entries.add(new TodoEntry(Database.getAutoIncrement(addTodoEntry, 1), content, false)); addTodoEntry.clearParameters(); sendMessage(e, "Added to `" + label + "` todo list."); } //alias check [ListName] [EntryIndex] //alias mark [ListName] [EntryIndex] //alias uncheck [ListName] [EntryIndex] //alias unmark [ListName] [EntryIndex] private void handleCheck(MessageReceivedEvent e, String[] args, boolean completed) throws SQLException { checkArgs(args, 2, "No todo ListName was specified. Usage: `" + getAliases().get(0) + " mark/unmark [ListName] [EntryIndex]`"); checkArgs(args, 3, "No todo EntryIndex was specified. Usage: `" + getAliases().get(0) + " mark/unmark [ListName] [EntryIndex]`"); String label = args[2].toLowerCase(); TodoList todoList = todoLists.get(label); if (todoList == null) { sendMessage(e, "Sorry, `" + label + "` isn't a known todo list."); return; } String todoEntryString = args[3]; if (todoEntryString.equals("*")) { PreparedStatement setTodoEntryChecked = Database.getInstance().getStatement(SET_TODO_ENTRIES_CHECKED); setTodoEntryChecked.setBoolean(1, completed); setTodoEntryChecked.setInt(2, todoList.id); if (setTodoEntryChecked.executeUpdate() == 0) throw new SQLException(SET_TODO_ENTRIES_CHECKED + " reported no updated rows!"); todoList.entries.forEach(todoEntry -> todoEntry.checked = completed); sendMessage(e, "Set all entries in the `" + label + "` todo list to **" + (completed ? "complete**" : "incomplete**")); } else { int todoEntryIndex; try { //We subtract 1 from the provided value because entries are listed from 1 and higher. // People don't start counting from 0, so when we display the list of entries, we start from. // This means that the entry index they enter will actually be 1 greater than the actual entry. todoEntryIndex = Integer.parseInt(todoEntryString) - 1; } catch (NumberFormatException ex) { sendMessage(e, "The provided value as an index to mark was not a number. Value provided: `" + todoEntryString + "`"); return; } if (todoEntryIndex < 0 || todoEntryIndex + 1 > todoList.entries.size()) { //We add 1 back to the todoEntry because we subtracted 1 from it above. (Basically, we make it human readable again) sendMessage(e, "The provided index to mark does not exist in this Todo list. Value provided: `" + (todoEntryIndex + 1) + "`"); return; } TodoEntry todoEntry = todoList.entries.get(todoEntryIndex); if (todoEntry.checked != completed) { PreparedStatement setTodoEntryChecked = Database.getInstance().getStatement(SET_TODO_ENTRY_CHECKED); setTodoEntryChecked.setBoolean(1, completed); setTodoEntryChecked.setInt(2, todoEntry.id); if (setTodoEntryChecked.executeUpdate() == 0) throw new SQLException(SET_TODO_ENTRY_CHECKED + " reported no updated rows!"); todoEntry.checked = completed; } sendMessage(e, "Item `" + (todoEntryIndex + 1) + "` in `" + label + "` was marked as **" + (completed ? "completed**" : "incomplete**")); } } //alias lock [ListName] private void handleLock(MessageReceivedEvent e, String[] args, boolean locked) throws SQLException { checkArgs(args, 2, "No todo ListName was specified. Usage: `" + getAliases().get(0) + " lock/unlock [ListName]`"); String label = args[2].toLowerCase(); TodoList todoList = todoLists.get(label); if (todoList == null) { sendMessage(e, "Sorry, `" + label + "` isn't a known todo list."); return; } if (!todoList.isAuthUser(e.getAuthor())) { sendMessage(e, "Sorry, you do not have permission to lock or unlock the `" + label + "` todo list."); return; } PreparedStatement setTodoListLocked = Database.getInstance().getStatement(SET_TODO_LIST_LOCKED); setTodoListLocked.setBoolean(1, locked); setTodoListLocked.setInt(2, todoList.id); if (setTodoListLocked.executeUpdate() == 0) throw new SQLException(SET_TODO_LIST_LOCKED + " reported no updated rows!"); setTodoListLocked.clearParameters(); todoList.locked = locked; sendMessage(e, "The `" + label + "` todo list was `" + (locked ? "locked`" : "unlocked`")); } //alias users add [ListName] @mention @mention ... //alias users remove [ListName] @mention @mention ... //alias users list [ListName] private void handleUsers(MessageReceivedEvent e, String[] args) throws SQLException { checkArgs(args, 2, "No SubAction was specified. Usage: `" + getAliases().get(0) + " users [SubAction] [ListName]`"); checkArgs(args, 3, "No todo ListName was specified. Usage: `" + getAliases().get(0) + " users [SubAction] [ListName]`"); String action = args[2].toLowerCase(); String label = args[3].toLowerCase(); TodoList todoList = todoLists.get(label); if (todoList == null) { sendMessage(e, "Sorry, `" + label + "` isn't a known todo list."); return; } switch (action) { case "add": { if (!todoList.ownerId.equals(e.getAuthor().getId())) { sendMessage(e, "Sorry, but only the Owner of a list has permission add users to a todo list."); return; } if (e.getMessage().getMentionedUsers().size() == 0) { sendMessage(e, "No users were specified to add to the `" + label + "` todo list."); return; } int addedUsers = 0; PreparedStatement addTodoUser = Database.getInstance().getStatement(ADD_TODO_USER); for (User u : e.getMessage().getMentionedUsers()) { if (!todoList.isAuthUser(u)) { addTodoUser.setInt(1, todoList.id); addTodoUser.setString(2, u.getId()); if (addTodoUser.executeUpdate() == 0) throw new SQLException(ADD_TODO_LIST + " reported no updated rows!"); addTodoUser.clearParameters(); todoList.allowedUsers.add(u.getId()); addedUsers++; } } sendMessage(e, "Added **" + addedUsers + "** users to the `" + label + "` todo list."); break; } case "remove": { if (!todoList.ownerId.equals(e.getAuthor().getId())) { sendMessage(e, "Sorry, but only the Owner of a list has permission remove users from a todo list."); return; } if (e.getMessage().getMentionedUsers().size() == 0) { sendMessage(e, "No users were specified to add to the `" + label + "` todo list."); return; } int removedUsers = 0; PreparedStatement removeTodoUser = Database.getInstance().getStatement(REMOVE_TODO_USER); for (User u : e.getMessage().getMentionedUsers()) { if (todoList.allowedUsers.stream().anyMatch(id -> u.getId().equals(id))) { removeTodoUser.setInt(1, todoList.id); removeTodoUser.setString(2, u.getId()); if (removeTodoUser.executeUpdate() == 0) throw new SQLException(REMOVE_TODO_USER + " reported no updated rows!"); removeTodoUser.clearParameters(); todoList.allowedUsers.remove(u.getId()); removedUsers++; } } sendMessage(e, "Removed **" + removedUsers + "** users from the `" + label + "` todo list."); break; } case "list": { MessageBuilder builder = new MessageBuilder(); builder.append("__Owner of `" + label + "`__\n"); User owner = api.getUserById(todoList.ownerId); if (owner != null) builder.append(" - " + owner.getName()); else builder.append(" - Unknown User ID: " + todoList.ownerId); builder.append("\n"); builder.append("__Other Auth'd Users__\n"); for (String id : todoList.allowedUsers) { User u = api.getUserById(id); if (u != null) builder.append(" - " + u.getName()); else builder.append(" - Unknown User ID: " + id); builder.append("\n"); } if (todoList.allowedUsers.isEmpty()) builder.append(" - None."); sendMessage(e, builder.build()); break; } default: { sendMessage(e, "Sorry, the provided sub-action argument for the `users` action is not recognized. " + "Provided argument: `" + action + "`"); return; } } } //alias clear [ListName] public void handleClear(MessageReceivedEvent e, String[] args) throws SQLException { checkArgs(args, 2, "No todo ListName was specified. Usage: `" + getAliases().get(0) + " clear [ListName]`"); String label = args[2]; TodoList todoList = todoLists.get(label); if (todoList == null) { sendMessage(e, "Sorry, `" + label + "` isn't a known todo list."); return; } if (todoList.locked && !todoList.isAuthUser(e.getAuthor())) { sendMessage(e, "Sorry, the `" + label + "` todo list is locked and you do not have permission to modify it."); return; } int clearedEntries = 0; PreparedStatement removeTodoEntry = Database.getInstance().getStatement(REMOVE_TODO_ENTRY); for (Iterator<TodoEntry> it = todoList.entries.iterator(); it.hasNext();) { TodoEntry todoEntry = it.next(); if (todoEntry.checked) { removeTodoEntry.setInt(1, todoEntry.id); if (removeTodoEntry.executeUpdate() == 0) throw new SQLException(REMOVE_TODO_ENTRY + " reported no updated rows!"); removeTodoEntry.clearParameters(); it.remove(); clearedEntries++; } } sendMessage(e, "Cleared **" + clearedEntries + "** completed entries from the `" + label + "` todo list."); } //alias remove [ListName] public void handleRemove(MessageReceivedEvent e, String[] args) throws SQLException { checkArgs(args, 2, "No todo ListName was specified. Usage: `" + getAliases().get(0) + " remove [ListName]`"); String label = args[2].toLowerCase(); TodoList todoList = todoLists.get(label); if (todoList == null) { sendMessage(e, "Sorry, `" + label + "` isn't a known todo list."); return; } if (todoList.locked && !todoList.isAuthUser(e.getAuthor())) { sendMessage(e, "Sorry, the `" + label + "` todo list is locked and you do not have permission to modify it."); return; } PreparedStatement removeTodoList = Database.getInstance().getStatement(REMOVE_TODO_LIST); removeTodoList.setInt(1, todoList.id); if (removeTodoList.executeUpdate() == 0) throw new SQLException(REMOVE_TODO_LIST + " reported no updated rows!"); removeTodoList.clearParameters(); todoLists.remove(label); sendMessage(e, "Deleted the `" + label + "` todo list."); } private void checkArgs(String[] args, int index, String failMessage) { if (args.length < (index + 1)) throw new IllegalArgumentException(failMessage); } private static class TodoList { int id; String labelName; String ownerId; boolean locked; List<TodoEntry> entries; List<String> allowedUsers; TodoList(int id, String labelName, String ownerId, boolean locked) { this.id = id; this.labelName = labelName; this.ownerId = ownerId; this.locked = locked; this.entries = new ArrayList<>(); this.allowedUsers = new ArrayList<>(); } public boolean isAuthUser(User user) { return ownerId.equals(user.getId()) || allowedUsers.stream().anyMatch(id -> id.equals(user.getId())); } @Override public boolean equals(Object o) { if (!(o instanceof TodoList)) return false; TodoList tl = (TodoList) o; return tl.id == this.id && tl.labelName.equals(this.labelName) && tl.locked == this.locked; } @Override public int hashCode() { return toString().hashCode(); } @Override public String toString() { return "TodoLabel: Id: " + id + " Name: " + labelName + " Size: " + entries.size() + " Locked: " + locked; } } private static class TodoEntry { int id; String content; boolean checked; TodoEntry(int id, String content, boolean checked) { this.id = id; this.content = content; this.checked = checked; } @Override public boolean equals(Object o) { if (!(o instanceof TodoEntry)) return false; TodoEntry te = (TodoEntry) o; return te.id == this.id && te.content.equals(this.content) && te.checked == this.checked; } @Override public int hashCode() { return toString().hashCode(); } @Override public String toString() { return "TodoEntry: Id: " + id + " Checked: " + checked + " Content: " + content; } } }