From dd087956d3db52f6c0a1f3b854b2204d2346b834 Mon Sep 17 00:00:00 2001 From: Dennis van den Brock Date: Tue, 3 Sep 2024 21:27:36 +0200 Subject: [PATCH] Added Poll Voting --- .idea/uiDesigner.xml | 124 +++++++ src/main/java/com/shweit/poll/Poll.java | 28 +- .../poll/commands/CreatePollCommand.java | 24 +- .../PollDetailGuiListener.java | 306 ++++++++++++++++++ .../pollDetailsCommand/PollDetails.java | 46 +++ .../PollDetailsCommand.java | 217 +++++++++++++ .../commands/pollsCommand/PollsCommand.java | 147 +++++++++ .../pollsCommand/PollsGuiListener.java | 67 ++++ .../shweit/poll/utils/ConnectionManager.java | 15 + src/main/resources/plugin.yml | 5 + .../{schema.sql => sql/polls_table.sql} | 3 + src/main/resources/sql/votes_table.sql | 8 + 12 files changed, 968 insertions(+), 22 deletions(-) create mode 100644 .idea/uiDesigner.xml create mode 100644 src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetailGuiListener.java create mode 100644 src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetails.java create mode 100644 src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetailsCommand.java create mode 100644 src/main/java/com/shweit/poll/commands/pollsCommand/PollsCommand.java create mode 100644 src/main/java/com/shweit/poll/commands/pollsCommand/PollsGuiListener.java create mode 100644 src/main/java/com/shweit/poll/utils/ConnectionManager.java rename src/main/resources/{schema.sql => sql/polls_table.sql} (84%) create mode 100644 src/main/resources/sql/votes_table.sql diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/shweit/poll/Poll.java b/src/main/java/com/shweit/poll/Poll.java index 4a41e13..4a67157 100644 --- a/src/main/java/com/shweit/poll/Poll.java +++ b/src/main/java/com/shweit/poll/Poll.java @@ -1,6 +1,10 @@ package com.shweit.poll; import com.shweit.poll.commands.CreatePollCommand; +import com.shweit.poll.commands.pollDetailsCommand.PollDetailGuiListener; +import com.shweit.poll.commands.pollsCommand.PollsCommand; +import com.shweit.poll.commands.pollsCommand.PollsGuiListener; +import com.shweit.poll.utils.ConnectionManager; import com.shweit.poll.utils.Logger; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.plugin.java.JavaPlugin; @@ -10,7 +14,6 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.sql.Connection; -import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; @@ -18,14 +21,19 @@ public final class Poll extends JavaPlugin { public static Connection connection; public static FileConfiguration config; + private static Poll instance; @Override public void onEnable() { createConfig(); config = getConfig(); + instance = this; setupDatabase(); getCommand("createpoll").setExecutor(new CreatePollCommand()); + getCommand("polls").setExecutor(new PollsCommand()); + getServer().getPluginManager().registerEvents(new PollsGuiListener(), this); + getServer().getPluginManager().registerEvents(new PollDetailGuiListener(), this); } @Override @@ -36,33 +44,34 @@ public void onDisable() { } } catch (SQLException e) { Logger.error(e.toString()); + e.printStackTrace(); } } + public static Poll getInstance() { + return instance; + } + private void setupDatabase() { - // Überprüfen, ob die Datei polls.sqlite existiert File dbFile = new File(getDataFolder(), "polls.sqlite"); if (!dbFile.exists()) { - // Wenn sie nicht existiert, erstelle sie try { if (!getDataFolder().exists()) { getDataFolder().mkdirs(); } - connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getPath()); - Logger.debug("Connected to new polls.sqlite database."); + Connection connection = new ConnectionManager().getConnection(); - // Lade und führe das SQL-Skript aus - executeSqlScript(connection, "schema.sql"); + executeSqlScript(connection, "sql/polls_table.sql"); + executeSqlScript(connection, "sql/votes_table.sql"); } catch (SQLException e) { e.printStackTrace(); } } else { - // Wenn die Datei existiert, verbinde mit der Datenbank try { - connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getPath()); + Connection connection = new ConnectionManager().getConnection(); Logger.debug("Connected to existing polls.sqlite database."); } catch (SQLException e) { Logger.error(e.toString()); @@ -72,7 +81,6 @@ private void setupDatabase() { private void executeSqlScript(Connection connection, String fileName) { try { - // Lade die SQL-Datei aus dem Ressourcen-Ordner InputStream is = getResource(fileName); if (is == null) { Logger.error("SQL file not found: " + fileName); diff --git a/src/main/java/com/shweit/poll/commands/CreatePollCommand.java b/src/main/java/com/shweit/poll/commands/CreatePollCommand.java index 7a0c44d..23b2c3d 100644 --- a/src/main/java/com/shweit/poll/commands/CreatePollCommand.java +++ b/src/main/java/com/shweit/poll/commands/CreatePollCommand.java @@ -1,13 +1,13 @@ package com.shweit.poll.commands; import com.shweit.poll.Poll; -import com.shweit.poll.utils.Logger; -import org.bukkit.ChatColor; +import com.shweit.poll.utils.ConnectionManager; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; +import com.google.gson.Gson; import java.sql.Connection; import java.sql.PreparedStatement; @@ -16,17 +16,16 @@ import java.util.List; import java.util.UUID; -public class CreatePollCommand implements CommandExecutor, TabExecutor -{ +public class CreatePollCommand implements CommandExecutor, TabExecutor { + private final Gson gson = new Gson(); + @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (!(sender instanceof Player)) { + if (!(sender instanceof Player player)) { sender.sendMessage("This command can only be used by players."); return true; } - Player player = (Player) sender; - if (args.length < 3) { player.sendMessage("Usage: /createpoll \"\" \"\" \"\" ... [--multi]"); return false; @@ -90,18 +89,19 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } private String convertListToJson(List list) { - return "[" + String.join(",", list) + "]"; + return gson.toJson(list); } - private void savePollToDatabase(UUID uniqueId, String string, String answersAsJsonString, boolean multi) throws SQLException { - Connection connection = Poll.connection; - String insertPollQuery = "INSERT INTO polls (uuid, question, answers, allowMultiple) VALUES (?, ?, ?, ?)"; + private void savePollToDatabase(UUID uniqueId, String question, String answersAsJsonString, boolean multi) throws SQLException { + Connection connection = new ConnectionManager().getConnection(); + String insertPollQuery = "INSERT INTO polls (uuid, question, answers, allowMultiple, isOpen) VALUES (?, ?, ?, ?, ?)"; try (PreparedStatement preparedStatement = connection.prepareStatement(insertPollQuery)) { preparedStatement.setString(1, uniqueId.toString()); - preparedStatement.setString(2, string); + preparedStatement.setString(2, question); preparedStatement.setString(3, answersAsJsonString); preparedStatement.setBoolean(4, multi); + preparedStatement.setBoolean(5, true); preparedStatement.executeUpdate(); } } diff --git a/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetailGuiListener.java b/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetailGuiListener.java new file mode 100644 index 0000000..5e60844 --- /dev/null +++ b/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetailGuiListener.java @@ -0,0 +1,306 @@ +package com.shweit.poll.commands.pollDetailsCommand; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.shweit.poll.utils.ConnectionManager; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class PollDetailGuiListener implements Listener { + private final Gson gson = new Gson(); + + /** + * Handles the click event in the Poll Details inventory. + * + * @param event The InventoryClickEvent triggered when a player clicks inside the inventory. + */ + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + Inventory inventory = event.getClickedInventory(); + + if (inventory == null || !event.getView().getTitle().equals(ChatColor.BLUE + "Poll Details")) { + return; + } + + event.setCancelled(true); + + ItemStack clickedItem = event.getCurrentItem(); + if (clickedItem == null || !clickedItem.hasItemMeta()) { + return; + } + + String displayName = clickedItem.getItemMeta().getDisplayName(); + if (!displayName.startsWith(ChatColor.YELLOW.toString())) { + return; + } + + String answer = ChatColor.stripColor(displayName); + Player player = (Player) event.getWhoClicked(); + UUID playerUUID = player.getUniqueId(); + int pollId = getPollIdFromInventory(inventory); + + if (pollId == -1) { + player.sendMessage(ChatColor.RED + "Could not find the poll ID."); + return; + } + + try (Connection connection = new ConnectionManager().getConnection()) { + if (hasVoted(playerUUID, pollId, connection)) { + if (isSelectedAnswer(playerUUID, pollId, answer, connection)) { + // Remove the answer if it is already selected + removeVote(playerUUID, pollId, answer, connection); + player.sendMessage(ChatColor.RED + "You deselected: " + answer); + } else if (allowsMultipleAnswers(pollId, connection)) { + // Add a new answer if multiple answers are allowed + addVote(playerUUID, pollId, answer, connection); + player.sendMessage(ChatColor.GREEN + "You selected: " + answer); + } else { + player.sendMessage(ChatColor.RED + "You have already voted. Multiple answers are not allowed."); + } + } else { + // Add the answer if no answer has been selected yet + addVote(playerUUID, pollId, answer, connection); + player.sendMessage(ChatColor.GREEN + "You selected: " + answer); + } + } catch (SQLException e) { + e.printStackTrace(); + player.sendMessage(ChatColor.RED + "An error occurred while processing your vote."); + } + + // Reopen the poll details inventory to reflect the updated votes + new PollDetailsCommand().openPollDetails(player, pollId); + } + + /** + * Retrieves the poll ID from the inventory based on the lore of the item in slot 13. + * + * @param inventory The inventory containing the poll details. + * @return The poll ID or -1 if not found. + */ + private int getPollIdFromInventory(Inventory inventory) { + ItemStack item = inventory.getItem(13); // Get the item in slot 13 + + if (item != null && item.hasItemMeta() && item.getItemMeta().hasLore()) { + List lore = item.getItemMeta().getLore(); + + if (lore != null) { + for (String line : lore) { + if (line.startsWith(ChatColor.GRAY + "ID: ")) { + try { + return Integer.parseInt(ChatColor.stripColor(line).replace("ID: ", "")); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + } + } + } + } + + return -1; // Return -1 if no ID is found + } + + /** + * Checks if the player has already voted in the poll. + * + * @param playerUUID The UUID of the player. + * @param pollId The ID of the poll. + * @param connection The database connection. + * @return True if the player has voted, false otherwise. + * @throws SQLException If an SQL error occurs. + */ + private boolean hasVoted(UUID playerUUID, int pollId, Connection connection) throws SQLException { + String query = "SELECT answers FROM votes WHERE uuid = ? AND poll_id = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, playerUUID.toString()); + statement.setInt(2, pollId); + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + String votes = resultSet.getString("answers"); + return votes != null && !votes.isEmpty(); + } + } + } + return false; + } + + /** + * Checks if a specific answer is selected by the player in the poll. + * + * @param playerUUID The UUID of the player. + * @param pollId The ID of the poll. + * @param answer The answer to check. + * @param connection The database connection. + * @return True if the answer is selected, false otherwise. + * @throws SQLException If an SQL error occurs. + */ + private boolean isSelectedAnswer(UUID playerUUID, int pollId, String answer, Connection connection) throws SQLException { + String query = "SELECT answers FROM votes WHERE uuid = ? AND poll_id = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, playerUUID.toString()); + statement.setInt(2, pollId); + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + String votes = resultSet.getString("answers"); + if (votes != null && !votes.isEmpty()) { + if (votes.startsWith("[")) { + // JSON array + List voteList = gson.fromJson(votes, new TypeToken>() {}.getType()); + return voteList.contains(answer); + } else { + // Simple string answer + return votes.equals(answer); + } + } + } + } + } + return false; + } + + /** + * Checks if the poll allows multiple answers. + * + * @param pollId The ID of the poll. + * @param connection The database connection. + * @return True if multiple answers are allowed, false otherwise. + * @throws SQLException If an SQL error occurs. + */ + private boolean allowsMultipleAnswers(int pollId, Connection connection) throws SQLException { + String query = "SELECT allowMultiple FROM polls WHERE id = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setInt(1, pollId); + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + return resultSet.getBoolean("allowMultiple"); + } + } + } + return false; + } + + /** + * Adds a vote for the player to the poll. + * + * @param playerUUID The UUID of the player. + * @param pollId The ID of the poll. + * @param answer The answer to add. + * @param connection The database connection. + * @throws SQLException If an SQL error occurs. + */ + private void addVote(UUID playerUUID, int pollId, String answer, Connection connection) throws SQLException { + String existingVotes = getPlayerVoteJson(playerUUID, pollId, connection); + List votes; + + if (existingVotes != null && !existingVotes.isEmpty()) { + votes = gson.fromJson(existingVotes, new TypeToken>() {}.getType()); + } else { + votes = new ArrayList<>(); + } + + if (!votes.contains(answer)) { + votes.add(answer); + } + + String updateQuery = "UPDATE votes SET answers = ?, created_at = CURRENT_TIMESTAMP WHERE poll_id = ? AND uuid = ?"; + String insertQuery = "INSERT INTO votes (poll_id, uuid, answers, created_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)"; + + try (PreparedStatement updateStatement = connection.prepareStatement(updateQuery)) { + updateStatement.setString(1, gson.toJson(votes)); + updateStatement.setInt(2, pollId); + updateStatement.setString(3, playerUUID.toString()); + + int rowsAffected = updateStatement.executeUpdate(); + if (rowsAffected == 0) { + // If no row was updated, insert a new record + try (PreparedStatement insertStatement = connection.prepareStatement(insertQuery)) { + insertStatement.setInt(1, pollId); + insertStatement.setString(2, playerUUID.toString()); + insertStatement.setString(3, gson.toJson(votes)); + insertStatement.executeUpdate(); + } + } + } + } + + /** + * Removes a vote for the player from the poll. + * + * @param playerUUID The UUID of the player. + * @param pollId The ID of the poll. + * @param answer The answer to remove. + * @param connection The database connection. + * @throws SQLException If an SQL error occurs. + */ + private void removeVote(UUID playerUUID, int pollId, String answer, Connection connection) throws SQLException { + String existingVotes = getPlayerVoteJson(playerUUID, pollId, connection); + if (existingVotes == null || existingVotes.isEmpty()) { + return; + } + + List votes; + if (existingVotes.startsWith("[")) { + // JSON array + votes = gson.fromJson(existingVotes, new TypeToken>() {}.getType()); + } else { + // Simple string answer + votes = new ArrayList<>(); + votes.add(existingVotes); + } + + votes.remove(answer); + + String query; + if (votes.isEmpty()) { + query = "DELETE FROM votes WHERE poll_id = ? AND uuid = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setInt(1, pollId); + statement.setString(2, playerUUID.toString()); + statement.executeUpdate(); + } + } else { + query = "UPDATE votes SET answers = ?, created_at = CURRENT_TIMESTAMP WHERE poll_id = ? AND uuid = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, gson.toJson(votes)); + statement.setInt(2, pollId); + statement.setString(3, playerUUID.toString()); + statement.executeUpdate(); + } + } + } + + /** + * Retrieves the player's votes for a specific poll as a JSON string. + * + * @param playerUUID The UUID of the player. + * @param pollId The ID of the poll. + * @param connection The database connection. + * @return The votes as a JSON string, or null if none are found. + * @throws SQLException If an SQL error occurs. + */ + private String getPlayerVoteJson(UUID playerUUID, int pollId, Connection connection) throws SQLException { + String query = "SELECT answers FROM votes WHERE uuid = ? AND poll_id = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, playerUUID.toString()); + statement.setInt(2, pollId); + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + return resultSet.getString("answers"); + } + } + } + return null; + } +} diff --git a/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetails.java b/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetails.java new file mode 100644 index 0000000..b4f8c0c --- /dev/null +++ b/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetails.java @@ -0,0 +1,46 @@ +package com.shweit.poll.commands.pollDetailsCommand; + +import java.util.List; + +public class PollDetails { + + private final int pollId; + private final String question; + private final List answers; + private final String creator; + private final String createdAt; + private final boolean multi; + + public PollDetails(int pollId, String question, List answers, String creator, String createdAt, boolean multi) { + this.pollId = pollId; + this.question = question; + this.answers = answers; + this.creator = creator; + this.createdAt = createdAt; + this.multi = multi; + } + + public int getPollId() { + return pollId; + } + + public String getQuestion() { + return question; + } + + public List getAnswers() { + return answers; + } + + public String getCreator() { + return creator; + } + + public String getCreatedAt() { + return createdAt; + } + + public boolean isMulti() { + return multi; + } +} diff --git a/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetailsCommand.java b/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetailsCommand.java new file mode 100644 index 0000000..b2e63e4 --- /dev/null +++ b/src/main/java/com/shweit/poll/commands/pollDetailsCommand/PollDetailsCommand.java @@ -0,0 +1,217 @@ +package com.shweit.poll.commands.pollDetailsCommand; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.shweit.poll.utils.ConnectionManager; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.lang.reflect.Type; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +public class PollDetailsCommand { + + private final Gson gson = new Gson(); + + /** + * Opens a detailed view of a poll in an inventory GUI for the player. + * @param player The player for whom the GUI is opened. + * @param pollId The ID of the poll to display. + */ + public void openPollDetails(Player player, int pollId) { + Inventory pollDetailsInventory = Bukkit.createInventory(null, 54, ChatColor.BLUE + "Poll Details"); + + PollDetails pollDetails = getPollDetails(pollId); + List answers = pollDetails.getAnswers(); + List playerVotes = getPlayerVotes(player.getUniqueId(), pollId); + Map voteCounts = getVoteCounts(pollId); + + // Add a border around the inventory + ItemStack borderItem = new ItemStack(Material.LIGHT_GRAY_STAINED_GLASS_PANE); + ItemMeta borderMeta = borderItem.getItemMeta(); + borderMeta.setDisplayName(" "); + borderItem.setItemMeta(borderMeta); + + for (int i = 0; i < 54; i++) { + if (i < 10 || i > 43 || i % 9 == 0 || (i + 1) % 9 == 0) { + pollDetailsInventory.setItem(i, borderItem); + } + } + + // Add the question at the top center + ItemStack questionItem = new ItemStack(Material.PAPER); + ItemMeta questionMeta = questionItem.getItemMeta(); + questionMeta.setDisplayName(ChatColor.GOLD + pollDetails.getQuestion()); + + List questionLore = new ArrayList<>(); + OfflinePlayer creator = Bukkit.getOfflinePlayer(UUID.fromString(pollDetails.getCreator())); + questionLore.add(ChatColor.GRAY + "Created by: " + ChatColor.GREEN + creator.getName()); + questionLore.add(ChatColor.GRAY + "Created on: " + ChatColor.GREEN + pollDetails.getCreatedAt()); + questionLore.add(ChatColor.GRAY + "ID: " + ChatColor.GREEN + pollDetails.getPollId()); + questionLore.add(""); + questionLore.add(ChatColor.GRAY + "Your vote: " + ChatColor.AQUA + (!playerVotes.isEmpty() ? String.join(", ", playerVotes) : "None")); + if (pollDetails.isMulti()) { + questionLore.add(""); + questionLore.add(ChatColor.GREEN + "You can vote for multiple answers."); + } + questionMeta.setLore(questionLore); + + questionItem.setItemMeta(questionMeta); + pollDetailsInventory.setItem(13, questionItem); + + // Add the answers + int startSlot = 19; // First slot for answers + for (String answer : answers) { + Material material = playerVotes.contains(answer) ? Material.GREEN_TERRACOTTA : Material.RED_TERRACOTTA; + ItemStack answerItem = new ItemStack(material); + ItemMeta meta = answerItem.getItemMeta(); + meta.setDisplayName(ChatColor.YELLOW + answer); + + List lore = new ArrayList<>(); + lore.add(ChatColor.GRAY + "Votes: " + ChatColor.AQUA + voteCounts.getOrDefault(answer, 0)); + meta.setLore(lore); + + answerItem.setItemMeta(meta); + pollDetailsInventory.setItem(startSlot++, answerItem); + + if (startSlot == 26) { + startSlot = 28; // Skip the middle slot to the next row + } + } + + player.openInventory(pollDetailsInventory); + } + + /** + * Retrieves detailed information about a poll from the database. + * @param pollId The ID of the poll to retrieve. + * @return A PollDetails object containing the poll's details. + */ + private PollDetails getPollDetails(int pollId) { + PollDetails pollDetails = null; + String query = "SELECT question, answers, uuid, created_at, allowMultiple FROM polls WHERE id = ?"; + + try (Connection connection = new ConnectionManager().getConnection(); + PreparedStatement statement = connection.prepareStatement(query)) { + statement.setInt(1, pollId); + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + String question = resultSet.getString("question"); + String answersJson = resultSet.getString("answers"); + String creator = resultSet.getString("uuid"); + String createdAt = resultSet.getString("created_at"); + boolean multi = resultSet.getBoolean("allowMultiple"); + + List answers = decodeAnswers(answersJson); + pollDetails = new PollDetails(pollId, question, answers, creator, createdAt, multi); + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return pollDetails; + } + + /** + * Retrieves the votes of a player for a specific poll. + * @param playerUUID The UUID of the player. + * @param pollId The ID of the poll. + * @return A list of answers the player voted for. + */ + private List getPlayerVotes(UUID playerUUID, int pollId) { + try (Connection connection = new ConnectionManager().getConnection()) { + String votesJson = getPlayerVoteJson(playerUUID, pollId, connection); + if (votesJson != null && !votesJson.isEmpty()) { + if (votesJson.startsWith("[")) { + // The votes are stored as a JSON array + return gson.fromJson(votesJson, new TypeToken>() {}.getType()); + } else { + // The votes are stored as a simple string + List singleVoteList = new ArrayList<>(); + singleVoteList.add(votesJson); + return singleVoteList; + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + return new ArrayList<>(); + } + + /** + * Retrieves the votes of a player for a specific poll as a JSON string. + * @param playerUUID The UUID of the player. + * @param pollId The ID of the poll. + * @param connection The database connection. + * @return The votes as a JSON string. + * @throws SQLException If an SQL error occurs. + */ + private String getPlayerVoteJson(UUID playerUUID, int pollId, Connection connection) throws SQLException { + String query = "SELECT answers FROM votes WHERE uuid = ? AND poll_id = ?"; + try (PreparedStatement statement = connection.prepareStatement(query)) { + statement.setString(1, playerUUID.toString()); + statement.setInt(2, pollId); + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + return resultSet.getString("answers"); + } + } + } + return null; + } + + /** + * Retrieves the vote counts for each answer in a poll. + * @param pollId The ID of the poll. + * @return A map of answers to their corresponding vote counts. + */ + private Map getVoteCounts(int pollId) { + Map voteCounts = new HashMap<>(); + String query = "SELECT answers FROM votes WHERE poll_id = ?"; + + try (Connection connection = new ConnectionManager().getConnection(); + PreparedStatement statement = connection.prepareStatement(query)) { + statement.setInt(1, pollId); + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + String answers = resultSet.getString("answers"); + if (answers != null && !answers.isEmpty()) { + if (answers.startsWith("[")) { + List answerList = gson.fromJson(answers, new TypeToken>() {}.getType()); + for (String answer : answerList) { + voteCounts.put(answer, voteCounts.getOrDefault(answer, 0) + 1); + } + } else { + voteCounts.put(answers, voteCounts.getOrDefault(answers, 0) + 1); + } + } + } + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return voteCounts; + } + + /** + * Decodes a JSON string of answers into a list. + * @param answersJson The JSON string of answers. + * @return A list of answers. + */ + private List decodeAnswers(String answersJson) { + Type listType = new TypeToken>() {}.getType(); + return gson.fromJson(answersJson, listType); + } +} diff --git a/src/main/java/com/shweit/poll/commands/pollsCommand/PollsCommand.java b/src/main/java/com/shweit/poll/commands/pollsCommand/PollsCommand.java new file mode 100644 index 0000000..db73f03 --- /dev/null +++ b/src/main/java/com/shweit/poll/commands/pollsCommand/PollsCommand.java @@ -0,0 +1,147 @@ +package com.shweit.poll.commands.pollsCommand; + +import com.shweit.poll.utils.ConnectionManager; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.Material; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; + +public class PollsCommand implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("This command can only be used by players."); + return true; + } + + Player player = (Player) sender; + int page = 0; + + if (args.length > 0) { + try { + page = Integer.parseInt(args[0]) - 1; + } catch (NumberFormatException e) { + player.sendMessage(ChatColor.RED + "Invalid page number."); + return false; + } + } + + openPollsGUI(player, page); + + return true; + } + + /** + * Opens the Polls GUI for the player, displaying a paginated list of open polls. + * + * @param player The player to whom the GUI is shown. + * @param page The page number to display. + */ + private void openPollsGUI(Player player, int page) { + List> openPolls = getOpenPolls(); + + int pollsPerPage = 28; // 28 slots for polls, 26 slots for borders and navigation + int totalPages = (int) Math.ceil((double) openPolls.size() / pollsPerPage); + + if (page < 0 || page >= totalPages) { + page = 0; + } + + Inventory pollsInventory = Bukkit.createInventory(null, 54, ChatColor.GREEN + "Open Polls (Page " + (page + 1) + "/" + totalPages + ")"); + + // Add border around the inventory + ItemStack borderItem = new ItemStack(Material.LIGHT_GRAY_STAINED_GLASS_PANE); + ItemMeta borderMeta = borderItem.getItemMeta(); + borderMeta.setDisplayName(" "); + borderItem.setItemMeta(borderMeta); + + for (int i = 0; i < 54; i++) { + if (i < 10 || i > 43 || i % 9 == 0 || (i + 1) % 9 == 0) { + pollsInventory.setItem(i, borderItem); + } + } + + int start = page * pollsPerPage; + int end = Math.min(start + pollsPerPage, openPolls.size()); + + for (int i = start; i < end; i++) { + Map poll = openPolls.get(i); + ItemStack pollItem = new ItemStack(Material.PAPER); + ItemMeta meta = pollItem.getItemMeta(); + meta.setDisplayName(ChatColor.YELLOW + poll.get("question")); + + List lore = new ArrayList<>(); + OfflinePlayer creator = Bukkit.getOfflinePlayer(UUID.fromString(poll.get("creator"))); + lore.add(ChatColor.GRAY + "Created by: " + ChatColor.GREEN + (creator.getName() != null ? creator.getName() : "Unknown")); + lore.add(ChatColor.GRAY + "Created on: " + ChatColor.GREEN + poll.get("created_at")); + lore.add(ChatColor.GRAY + "ID: " + ChatColor.GREEN + poll.get("id")); + lore.add(""); + lore.add(ChatColor.GREEN + "Click to vote!"); + meta.setLore(lore); + + pollItem.setItemMeta(meta); + pollsInventory.addItem(pollItem); + } + + // Add pagination items + if (page > 0) { + ItemStack previousPage = new ItemStack(Material.ARROW); + ItemMeta previousMeta = previousPage.getItemMeta(); + previousMeta.setDisplayName(ChatColor.AQUA + "Previous Page"); + previousPage.setItemMeta(previousMeta); + pollsInventory.setItem(45, previousPage); + } + + if (page < totalPages - 1) { + ItemStack nextPage = new ItemStack(Material.ARROW); + ItemMeta nextMeta = nextPage.getItemMeta(); + nextMeta.setDisplayName(ChatColor.AQUA + "Next Page"); + nextPage.setItemMeta(nextMeta); + pollsInventory.setItem(53, nextPage); + } + + player.openInventory(pollsInventory); + } + + /** + * Retrieves a list of open polls from the database. + * + * @return A list of maps, where each map contains poll information. + */ + private List> getOpenPolls() { + List> openPolls = new ArrayList<>(); + String query = "SELECT id, question, uuid, created_at FROM polls WHERE isOpen = 1"; + + try (Connection connection = new ConnectionManager().getConnection(); + PreparedStatement statement = connection.prepareStatement(query); + ResultSet results = statement.executeQuery()) { + + while (results.next()) { + Map pollData = new HashMap<>(); + pollData.put("id", String.valueOf(results.getInt("id"))); + pollData.put("question", results.getString("question")); + pollData.put("creator", results.getString("uuid")); + pollData.put("created_at", results.getString("created_at")); + openPolls.add(pollData); + } + } catch (SQLException e) { + e.printStackTrace(); + } + + return openPolls; + } +} diff --git a/src/main/java/com/shweit/poll/commands/pollsCommand/PollsGuiListener.java b/src/main/java/com/shweit/poll/commands/pollsCommand/PollsGuiListener.java new file mode 100644 index 0000000..2d1e973 --- /dev/null +++ b/src/main/java/com/shweit/poll/commands/pollsCommand/PollsGuiListener.java @@ -0,0 +1,67 @@ +package com.shweit.poll.commands.pollsCommand; + +import com.shweit.poll.Poll; +import com.shweit.poll.commands.pollDetailsCommand.PollDetails; +import com.shweit.poll.commands.pollDetailsCommand.PollDetailsCommand; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +public class PollsGuiListener implements Listener { + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + Inventory inventory = event.getClickedInventory(); + + if (inventory == null || !event.getView().getTitle().startsWith(ChatColor.GREEN + "Open Polls")) { + return; + } + + event.setCancelled(true); + + ItemStack clickedItem = event.getCurrentItem(); + if (clickedItem == null || !clickedItem.hasItemMeta()) { + return; + } + + String displayName = clickedItem.getItemMeta().getDisplayName(); + + if (displayName.equals(ChatColor.AQUA + "Next Page")) { + handlePageChange(event, 1); + } else if (displayName.equals(ChatColor.AQUA + "Previous Page")) { + handlePageChange(event, -1); + } else { + String lore = clickedItem.getItemMeta().getLore().get(2); + int pollId = Integer.parseInt(ChatColor.stripColor(lore).replace("ID: ", "")); + new PollDetailsCommand().openPollDetails((Player) event.getWhoClicked(), pollId); + } + } + + private void handlePageChange(InventoryClickEvent event, int change) { + String title = event.getView().getTitle(); + int currentPage = extractPageNumber(title); + if (currentPage != -1) { + Poll.getInstance().getServer().getScheduler().runTask(Poll.getInstance(), () -> { + event.getWhoClicked().closeInventory(); + ((Player) event.getWhoClicked()).performCommand("polls " + (currentPage + change)); + }); + } + } + + private int extractPageNumber(String title) { + try { + int startIndex = title.indexOf("Page ") + 5; + int endIndex = title.indexOf("/", startIndex); + if (startIndex > 0 && endIndex > startIndex) { + return Integer.parseInt(title.substring(startIndex, endIndex).trim()); + } + } catch (NumberFormatException e) { + e.printStackTrace(); // Optional: Zum Debuggen, wenn etwas schiefgeht. + } + return -1; // Rückgabe -1, wenn die Seitenzahl nicht extrahiert werden kann. + } +} diff --git a/src/main/java/com/shweit/poll/utils/ConnectionManager.java b/src/main/java/com/shweit/poll/utils/ConnectionManager.java new file mode 100644 index 0000000..3a42297 --- /dev/null +++ b/src/main/java/com/shweit/poll/utils/ConnectionManager.java @@ -0,0 +1,15 @@ +package com.shweit.poll.utils; + +import com.shweit.poll.Poll; + +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +public class ConnectionManager { + public Connection getConnection() throws SQLException { + File dbFile = new File(Poll.getInstance().getDataFolder(), "polls.sqlite"); + return DriverManager.getConnection("jdbc:sqlite:" + dbFile.getPath()); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 11ea8d9..56628ac 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -5,6 +5,11 @@ api-version: '1.21' author: Shweit commands: + polls: + description: Show all available polls in a GUI. + usage: /polls + permission: poll.polls + createpoll: description: Creates a new poll with a question and up to 10 answers. usage: /createpoll "" "" "" ... [--multi] diff --git a/src/main/resources/schema.sql b/src/main/resources/sql/polls_table.sql similarity index 84% rename from src/main/resources/schema.sql rename to src/main/resources/sql/polls_table.sql index 3ad9c9d..93714fe 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/sql/polls_table.sql @@ -4,5 +4,8 @@ CREATE TABLE IF NOT EXISTS polls ( answers TEXT NOT NULL, allowMultiple BOOLEAN NOT NULL, uuid TEXT NOT NULL, + isOpen BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); + + diff --git a/src/main/resources/sql/votes_table.sql b/src/main/resources/sql/votes_table.sql new file mode 100644 index 0000000..40f5bf3 --- /dev/null +++ b/src/main/resources/sql/votes_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS votes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + poll_id INTEGER NOT NULL, + answers TEXT NOT NULL, + uuid TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (poll_id) REFERENCES polls (id) +); \ No newline at end of file