diff --git a/SheepstarModules/PollV1/.gitignore b/SheepstarModules/PollV1/.gitignore
new file mode 100644
index 0000000..7b98e63
--- /dev/null
+++ b/SheepstarModules/PollV1/.gitignore
@@ -0,0 +1,3 @@
+# Project exclude paths
+/target/
+.idea
\ No newline at end of file
diff --git a/SheepstarModules/PollV1/README.md b/SheepstarModules/PollV1/README.md
new file mode 100644
index 0000000..71dea27
--- /dev/null
+++ b/SheepstarModules/PollV1/README.md
@@ -0,0 +1,2 @@
+# SheepstarModule-Poll
+The official sheepstar poll module
diff --git a/SheepstarModules/PollV1/pom.xml b/SheepstarModules/PollV1/pom.xml
new file mode 100644
index 0000000..6664fec
--- /dev/null
+++ b/SheepstarModules/PollV1/pom.xml
@@ -0,0 +1,24 @@
+
+
+ 4.0.0
+
+ xyz.sheepstar
+ SheepstarModule-Poll
+ pre1.0.0
+
+
+ 8
+ 8
+
+
+
+
+ xyz.sheepstar
+ SheepstarCore
+ beta1.0.2
+
+
+
+
\ No newline at end of file
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/action/AutomaticCloseAction.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/action/AutomaticCloseAction.java
new file mode 100644
index 0000000..996d6f2
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/action/AutomaticCloseAction.java
@@ -0,0 +1,27 @@
+package xyz.sheepstar.poll.action;
+
+import xyz.sheepstar.poll.api.controller.PollController;
+import xyz.sheepstar.poll.api.models.PollManager;
+import xyz.sheepstar.poll.core.PollCore;
+import xyz.sheepstar.util.action.RepeatedAction;
+
+import java.time.LocalDateTime;
+
+public class AutomaticCloseAction extends RepeatedAction {
+
+ private final PollManager pollManager = (PollManager) api.getDatabase().getTableFactory().getTable(PollManager.class);
+ private final PollController pollController = PollCore.getPollController();
+
+ @Override
+ public long time() {
+ return 5;
+ }
+
+ @Override
+ public void execute() {
+ pollManager.getExpirablePolls().forEach((pollID, dateTime) -> {
+ if (dateTime.compareTo(LocalDateTime.now()) <= 0)
+ pollController.endPoll(pollID, pollManager.getPollById(pollID).getGuild());
+ });
+ }
+}
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/controller/PollController.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/controller/PollController.java
new file mode 100644
index 0000000..e96c93f
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/controller/PollController.java
@@ -0,0 +1,163 @@
+package xyz.sheepstar.poll.api.controller;
+
+import net.dv8tion.jda.api.entities.Emoji;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.TextChannel;
+import net.dv8tion.jda.api.interactions.components.ButtonStyle;
+import org.apache.commons.lang3.RandomStringUtils;
+import xyz.sheepstar.poll.api.entities.Poll;
+import xyz.sheepstar.poll.api.models.PollAnswerManager;
+import xyz.sheepstar.poll.api.models.PollManager;
+import xyz.sheepstar.poll.commands.ClosePollCommand;
+import xyz.sheepstar.util.bot.builder.message.DefaultEmbedBuilder;
+import xyz.sheepstar.util.bot.builder.message.DefaultResponseBuilder;
+import xyz.sheepstar.util.bot.builder.message.MessageType;
+import xyz.sheepstar.util.bot.listener.ListenerBasics;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+
+public class PollController extends ListenerBasics {
+
+ private PollManager pollManager = (PollManager) table(PollManager.class);
+ private PollAnswerManager pollAnswerManager = (PollAnswerManager) table(PollAnswerManager.class);
+
+ /**
+ * Gets the number emoji by number
+ *
+ * @param number The number the emoji should display
+ * @return the number as emoji
+ */
+ private Emoji getEmojiByNumber(int number) {
+ return Emoji.fromUnicode(number + "️⃣");
+ }
+
+ /**
+ * Gets the 'started'-message of the poll
+ *
+ * @param pollID The id of the poll (used for the end button)
+ * @param date The date when the poll should expire
+ * @param title The title of the poll
+ * @param description The description of the poll
+ * @param answers All answers available in this poll
+ * @param guild The guild which created the poll
+ * @param closeButton Should the close button be displayed?
+ * @param multipleChoices Are multiple choices allowed?
+ * @return the final message
+ */
+ public Message getStartedPollMessage(String pollID, Date date, String title, String description, ArrayList answers, Guild guild,
+ boolean closeButton, boolean multipleChoices) {
+ DefaultResponseBuilder response = DefaultResponseBuilder.createSimple(guild, ClosePollCommand.class).withColor(MessageType.PRIMARY)
+ .withTitle(translate("poll.poll_started", guild));
+
+ response.addField(":label: " + translate("poll.object.title", guild), title);
+
+ if (description != null)
+ response.addField(":clipboard: " + translate("poll.object.description", guild), description);
+
+ ArrayList subFields = new ArrayList<>();
+
+ for (int i = 0; i < answers.size(); i++)
+ subFields.add(getEmojiByNumber(i + 1).getAsMention() + " - " + answers.get(i));
+
+ response.addField(":ballot_box: " + translate("poll.object." + (multipleChoices ? "multiple_" : "") + "choices", guild) + "", subFields);
+
+ if (date != null)
+ response.addField(String.format("\n:alarm_clock: %s ", translate("poll.ends", guild), date.getTime() / 1000));
+
+ if (closeButton)
+ response.addButton(ButtonStyle.DANGER, "end#" + pollID, date == null ? translate("poll.end_poll", guild) : translate("poll.end_now", guild));
+
+ return response.build();
+ }
+
+ /**
+ * Gets the 'ended'-message of the poll
+ *
+ * @param poll The poll you want to get the message from
+ * @return The final 'ended'-message
+ */
+ public Message getEndedPollMessage(Poll poll) {
+ DefaultEmbedBuilder embedBuilder = new DefaultEmbedBuilder(MessageType.PRIMARY).setTitle(translate("poll.poll_ended", poll.getGuild()));
+
+ StringBuilder builder = new StringBuilder();
+
+ boolean isTimeMessage = false;
+ for (String line : poll.getMessage().getEmbeds().get(0).getDescription().split("\n")) {
+ if (line.startsWith(":alarm_clock:")) isTimeMessage = true;
+ if (!line.startsWith(":alarm_clock:")) builder.append(line).append("\n");
+ }
+
+ embedBuilder.setDescription((!isTimeMessage ? builder + "\n" : builder.substring(0, builder.length() - 3)));
+
+ HashMap reaction_counts = new HashMap<>();
+
+ poll.getMessage().getReactions().forEach(reaction -> {
+ if (reaction.getReactionEmote().isEmoji())
+ reaction_counts.put(reaction.getReactionEmote().getEmoji(), reaction.getCount() - 1);
+ });
+
+ ArrayList subFields = new ArrayList<>();
+
+ for (int i = 0; i < poll.getAnswers().size(); i++)
+ subFields.add(getEmojiByNumber(i + 1).getAsMention() + " - " + reaction_counts.getOrDefault(getEmojiByNumber(i + 1).getAsMention(), 0));
+
+ embedBuilder.addBField(":abacus: " + translate("poll.object.results", poll.getGuild()), subFields.toArray(new String[0]));
+
+ return embedBuilder.toMessage();
+ }
+
+ /**
+ * Creates a new poll
+ *
+ * @param channel The channel the poll should be created in
+ * @param date The date when the poll should expire
+ * @param title The title of the poll
+ * @param description The description of the poll
+ * @param answers All answers available in this poll
+ * @param closeButton Should the close button be displayed?
+ * @param multipleChoices Are multiple choices allowed?
+ * @return the id of the created poll
+ */
+ public String createPoll(TextChannel channel, Date date, String title, String description, ArrayList answers, boolean closeButton, boolean multipleChoices) {
+ String pollID = RandomStringUtils.randomAlphanumeric(10);
+
+ pollAnswerManager.addAnswers(pollID, answers);
+
+ channel.sendMessage(getStartedPollMessage(pollID, date, title, description, answers, channel.getGuild(), closeButton, multipleChoices)).queue(message -> {
+ pollManager.createPoll(pollID, message, date);
+ for (int i = 0; i < answers.size(); i++)
+ message.addReaction(getEmojiByNumber(i + 1).getAsMention()).queue();
+ });
+ return pollID;
+ }
+
+ /**
+ * Ends the provided poll
+ *
+ * @param pollID The poll you want to end
+ * @param guild The guild which created the poll
+ * @return true
if the poll has been ended successfully, otherwise false
+ */
+ public boolean endPoll(String pollID, Guild guild) {
+ if (!pollManager.isCreated(pollID, guild)) return false;
+
+ try {
+ Poll poll = pollManager.getPollById(pollID);
+
+ poll.getMessage().editMessage(getEndedPollMessage(poll)).queue();
+
+ poll.getMessage().clearReactions().queue();
+ } catch (Exception e) {
+ return false;
+ }
+
+ pollManager.deletePoll(pollID, guild);
+ pollAnswerManager.deleteAnswers(pollID);
+
+ return true;
+ }
+
+}
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/entities/Poll.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/entities/Poll.java
new file mode 100644
index 0000000..0ed7957
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/entities/Poll.java
@@ -0,0 +1,93 @@
+package xyz.sheepstar.poll.api.entities;
+
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.entities.TextChannel;
+import xyz.sheepstar.core.SheepstarCore;
+import xyz.sheepstar.poll.api.models.PollAnswerManager;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+
+public class Poll {
+
+ private PollAnswerManager pollAnswerManager = (PollAnswerManager) SheepstarCore
+ .getSheepstar().getDatabase().getTableFactory().getTable(PollAnswerManager.class);
+
+ private String pollID;
+ private Guild guild;
+ private TextChannel channel;
+ private Message message;
+ private LocalDateTime expiryDate;
+ private ArrayList answers;
+
+ /**
+ * Basic constructor of the {@link Poll} entity
+ *
+ * @param pollID The id of the poll
+ * @param message The poll message created by the bot
+ * @param expiryDate The date when the poll should expire
+ */
+ public Poll(String pollID, Message message, LocalDateTime expiryDate) {
+ this.pollID = pollID;
+ this.message = message;
+ this.guild = message.getGuild();
+ this.channel = message.getTextChannel();
+ this.expiryDate = expiryDate;
+ this.answers = pollAnswerManager.getAnswers(pollID);
+ }
+
+ /**
+ * Gets the poll id
+ *
+ * @return the poll id
+ */
+ public String getPollID() {
+ return pollID;
+ }
+
+ /**
+ * Gets the guild
+ *
+ * @return the guild
+ */
+ public Guild getGuild() {
+ return guild;
+ }
+
+ /**
+ * Gets the channel
+ *
+ * @return the channel
+ */
+ public TextChannel getChannel() {
+ return channel;
+ }
+
+ /**
+ * Gets the message
+ *
+ * @return the message
+ */
+ public Message getMessage() {
+ return message;
+ }
+
+ /**
+ * Gets the date when the poll should expire
+ *
+ * @return the expiry date
+ */
+ public LocalDateTime getExpiryDate() {
+ return expiryDate;
+ }
+
+ /**
+ * Gets all answers
+ *
+ * @return all answers
+ */
+ public ArrayList getAnswers() {
+ return answers;
+ }
+}
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/models/PollAnswerManager.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/models/PollAnswerManager.java
new file mode 100644
index 0000000..7f44531
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/models/PollAnswerManager.java
@@ -0,0 +1,59 @@
+package xyz.sheepstar.poll.api.models;
+
+import xyz.sheepstar.util.sql.SheepManager;
+
+import java.util.ArrayList;
+
+public class PollAnswerManager extends SheepManager {
+
+ @Override
+ protected String tableName() {
+ return "poll_poll_answers";
+ }
+
+ @Override
+ protected void tableFields() {
+ custom("poll_id").add();
+ custom("answer").add();
+ }
+
+ /**
+ * Adds a single answer to the poll answers
+ *
+ * @param pollID The id of the poll you want to add the answer to
+ * @param answer The answer you want to add
+ */
+ public void addAnswer(String pollID, String answer) {
+ insert().value("poll_id", pollID).value("answer", answer).execute();
+ }
+
+ /**
+ * Adds multiple answers to the poll answers
+ *
+ * @param pollID The id of the poll you want to add the answer to
+ * @param answers The answers you want to add
+ */
+ public void addAnswers(String pollID, ArrayList answers) {
+ answers.forEach(answer -> addAnswer(pollID, answer));
+ }
+
+ /**
+ * Deletes all answers from a poll
+ *
+ * @param pollID The id of the poll
+ */
+ public void deleteAnswers(String pollID) {
+ delete().where("poll_id", pollID).execute();
+ }
+
+ /**
+ * Gets all answers from the poll
+ *
+ * @param pollID The id of the poll you want to get the answers from
+ * @return all answers from the poll
+ */
+ public ArrayList getAnswers(String pollID) {
+ return select().where("poll_id", pollID).getResult().getList("answer");
+ }
+
+}
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/models/PollManager.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/models/PollManager.java
new file mode 100644
index 0000000..db4cb46
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/api/models/PollManager.java
@@ -0,0 +1,115 @@
+package xyz.sheepstar.poll.api.models;
+
+import de.gnmyt.sqltoolkit.types.SQLType;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Message;
+import xyz.sheepstar.poll.api.entities.Poll;
+import xyz.sheepstar.util.sql.SheepManager;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+import java.util.HashMap;
+
+public class PollManager extends SheepManager {
+
+ @Override
+ protected String tableName() {
+ return "poll_polls";
+ }
+
+ @Override
+ protected void tableFields() {
+ custom("poll_id").add();
+ custom("guild_id").add();
+ custom("channel_id").add();
+ custom("message_id").add();
+ custom("expiry").type(SQLType.DATETIME).allowNull(true).add();
+ }
+
+ /**
+ * Checks if the provided poll has been already created
+ *
+ * @param pollID The id of the poll you want to check
+ * @param guild The guild in which the poll has been created
+ * @return true
if the provided poll has been already created, otherwise false
+ */
+ public boolean isCreated(String pollID, Guild guild) {
+ return select().where("guild_id", guild.getId()).where("poll_id", pollID).getResult().exists();
+ }
+
+ /**
+ * Checks if the provided message is a poll
+ *
+ * @param message The message you want to check
+ * @return true
if the message is a poll, otherwise false
+ */
+ public boolean isPollMessage(Message message) {
+ return select().where("message_id", message.getId()).getResult().exists();
+ }
+
+ /**
+ * Gets the id from the poll by a message
+ *
+ * @param message The message you want to get the poll id from
+ * @return the id of the poll
+ */
+ public String getIDByMessage(Message message) {
+ return select().where("message_id", message.getId()).getResult().getString("poll_id");
+ }
+
+ /**
+ * Creates a new poll for you
+ *
+ * @param pollID The id the poll should have
+ * @param message The already sent message of the poll (to edit if finished)
+ * @param expiry The date when the poll should expire, NULL
if it should not close automatically
+ */
+ public void createPoll(String pollID, Message message, Date expiry) {
+ if (isCreated(pollID, message.getGuild())) return;
+ insert()
+ .value("poll_id", pollID)
+ .value("guild_id", message.getGuild().getId())
+ .value("channel_id", message.getChannel().getId())
+ .value("message_id", message.getId())
+ .value("expiry", expiry)
+ .execute();
+ }
+
+ /**
+ * Deletes a poll from the table
+ *
+ * @param pollID The id of the poll you want to delete
+ * @param guild The id of the guild the poll was created on
+ */
+ public void deletePoll(String pollID, Guild guild) {
+ if (!isCreated(pollID, guild)) return;
+ delete().where("poll_id", pollID).where("guild_id", guild.getId()).execute();
+ }
+
+ /**
+ * Gets all polls that can expire
+ *
+ * @return all polls that can expire
+ */
+ public HashMap getExpirablePolls() {
+ HashMap expirable_polls = new HashMap<>();
+ select().getResult().getList().forEach(entry -> {
+ if (entry.get("expiry") != null)
+ expirable_polls.put((String) entry.get("poll_id"), (LocalDateTime) entry.get("expiry"));
+ });
+ return expirable_polls;
+ }
+
+ /**
+ * Gets a specific poll by id
+ *
+ * @param pollID The id of the poll you want to get
+ * @return the poll
+ */
+ public Poll getPollById(String pollID) {
+ return new Poll(pollID, api.getJDA().getTextChannelById(select().where("poll_id", pollID)
+ .getResult().getString("channel_id")).retrieveMessageById(select().where("poll_id", pollID).getResult().getString("message_id")).complete(),
+ (LocalDateTime) select().where("poll_id", pollID).getResult().getObject("expiry"));
+ }
+
+}
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/commands/ClosePollCommand.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/commands/ClosePollCommand.java
new file mode 100644
index 0000000..1d6be8d
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/commands/ClosePollCommand.java
@@ -0,0 +1,35 @@
+package xyz.sheepstar.poll.commands;
+
+import xyz.sheepstar.poll.api.controller.PollController;
+import xyz.sheepstar.poll.core.PollCore;
+import xyz.sheepstar.util.bot.command.Arguments;
+import xyz.sheepstar.util.bot.command.GuildCommand;
+import xyz.sheepstar.util.bot.command.GuildEventController;
+import xyz.sheepstar.util.bot.command.PublicCommandException;
+import xyz.sheepstar.util.bot.command.annotations.CommandMeta;
+import xyz.sheepstar.util.bot.permission.PermissionNode;
+
+@CommandMeta(aliases = "poll", subAliases = "close", description = "Closes a specific poll", permission = PermissionNode.ADMINISTRATOR)
+public class ClosePollCommand extends GuildCommand {
+
+ @Override
+ public void usage() {
+ usage("poll_id", "The id of the poll you want to close").required(true).add();
+ }
+
+ private PollController pollController = PollCore.getPollController();
+
+ @Override
+ public void execute(GuildEventController event, Arguments args) throws Exception {
+ if (pollController.endPoll(args.getString("poll_id"), event.getGuild())) {
+ event.success("poll.ended_successfully");
+ } else throw new PublicCommandException("poll.not_found");
+ }
+
+ @Override
+ public void buttonClick(GuildEventController event, String id) throws Exception {
+ if (!id.startsWith("end#")) return;
+
+ pollController.endPoll(id.split("#")[1], event.getGuild());
+ }
+}
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/commands/CreatePollCommand.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/commands/CreatePollCommand.java
new file mode 100644
index 0000000..92badf8
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/commands/CreatePollCommand.java
@@ -0,0 +1,78 @@
+package xyz.sheepstar.poll.commands;
+
+import net.dv8tion.jda.api.entities.ChannelType;
+import net.dv8tion.jda.api.entities.GuildChannel;
+import net.dv8tion.jda.api.entities.TextChannel;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import xyz.sheepstar.poll.api.controller.PollController;
+import xyz.sheepstar.poll.core.PollCore;
+import xyz.sheepstar.util.bot.command.Arguments;
+import xyz.sheepstar.util.bot.command.GuildCommand;
+import xyz.sheepstar.util.bot.command.GuildEventController;
+import xyz.sheepstar.util.bot.command.PublicCommandException;
+import xyz.sheepstar.util.bot.command.annotations.CommandMeta;
+import xyz.sheepstar.util.bot.permission.PermissionNode;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+@CommandMeta(aliases = "poll", subAliases = "create", permission = PermissionNode.ADMINISTRATOR, description = "Creates a poll")
+public class CreatePollCommand extends GuildCommand {
+
+ private PollController pollController = PollCore.getPollController();
+
+ @Override
+ public void usage() {
+ usage("title", "The title of the poll").required(true).add();
+
+ usage("choice_1", "The first choice").required(true).add();
+ usage("choice_2", "The second choice").required(true).add();
+
+
+ usage("description", "The description of the poll").add();
+
+ usage("time", "When should the poll automatically be dissolved").add();
+
+ usage(OptionType.BOOLEAN, "close_button", "Should the poll show a close button? Otherwise you get the id to close the poll").add();
+
+ usage(OptionType.BOOLEAN, "multiple_choices", "Allows the user to click on multiple options").add();
+
+ usage(OptionType.CHANNEL, "channel", "The channel in which the message should be sent (for example if you have an poll channel)").add();
+
+ // All optional choices
+
+ usage("choice_3", "The third choice").add();
+ usage("choice_4", "The fourth choice").add();
+ usage("choice_5", "The fifth choice").add();
+ usage("choice_6", "The 6th choice").add();
+ usage("choice_7", "The 7th choice").add();
+ usage("choice_8", "The 8th choice").add();
+ usage("choice_9", "The 9th choice").add();
+ }
+
+ @Override
+ public void execute(GuildEventController event, Arguments args) throws Exception {
+ GuildChannel channel = args.exists("channel") ? args.getChannel("channel") : event.getChannel();
+
+ Date date = args.exists("time") ? getTimeFromString(args.getString("time")) : null;
+
+ boolean closeButton = !args.exists("close_button") || args.getBoolean("close_button");
+
+ boolean multipleChoices = args.exists("multiple_choices") && args.getBoolean("multiple_choices");
+
+ if (channel.getType() != ChannelType.TEXT)
+ throw new PublicCommandException("poll.error.channel.must_be_text");
+
+ ArrayList answers = new ArrayList<>();
+
+ for (int i = 1; i < 10; i++)
+ if (args.exists("choice_" + i)) answers.add(args.getString("choice_" + i));
+
+
+ String pollID = pollController.createPoll((TextChannel) channel, date, args.getString("title"), args.getString("description"), answers, closeButton, multipleChoices);
+
+ event.success("poll.poll_created", channel.getAsMention(), pollID);
+ }
+
+
+}
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/core/PollCore.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/core/PollCore.java
new file mode 100644
index 0000000..5497884
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/core/PollCore.java
@@ -0,0 +1,70 @@
+package xyz.sheepstar.poll.core;
+
+import xyz.sheepstar.poll.action.AutomaticCloseAction;
+import xyz.sheepstar.poll.api.controller.PollController;
+import xyz.sheepstar.poll.api.models.PollAnswerManager;
+import xyz.sheepstar.poll.api.models.PollManager;
+import xyz.sheepstar.poll.commands.ClosePollCommand;
+import xyz.sheepstar.poll.commands.CreatePollCommand;
+import xyz.sheepstar.poll.listener.MultipleReactionListener;
+import xyz.sheepstar.util.bot.manager.ImportManager;
+import xyz.sheepstar.util.module.SheepstarModule;
+
+public class PollCore extends SheepstarModule {
+
+ private static PollController pollController;
+
+ private ImportManager importManager;
+
+ @Override
+ public void onEnable() {
+ importManager = new ImportManager(getAPI(), "poll");
+
+ registerManagers();
+
+ pollController = new PollController();
+
+ registerCommands();
+ registerActions();
+ registerListeners();
+ }
+
+ /**
+ * Registers all managers of the module
+ */
+ public void registerManagers() {
+ registerTable(new PollAnswerManager());
+ registerTable(new PollManager());
+ }
+
+ /**
+ * Registers all commands of the module
+ */
+ public void registerCommands() {
+ importManager.registerCommand(new CreatePollCommand());
+ importManager.registerCommand(new ClosePollCommand());
+ }
+
+ /**
+ * Registers all listeners of the module
+ */
+ public void registerListeners() {
+ importManager.registerListener(new MultipleReactionListener());
+ }
+
+ /**
+ * Registers all actions of the module
+ */
+ public void registerActions() {
+ new AutomaticCloseAction().start();
+ }
+
+ /**
+ * Gets the poll controller
+ *
+ * @return the poll controller
+ */
+ public static PollController getPollController() {
+ return pollController;
+ }
+}
diff --git a/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/listener/MultipleReactionListener.java b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/listener/MultipleReactionListener.java
new file mode 100644
index 0000000..f4b104d
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/java/xyz/sheepstar/poll/listener/MultipleReactionListener.java
@@ -0,0 +1,65 @@
+package xyz.sheepstar.poll.listener;
+
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.events.message.guild.react.GuildMessageReactionAddEvent;
+import xyz.sheepstar.poll.api.models.PollAnswerManager;
+import xyz.sheepstar.poll.api.models.PollManager;
+import xyz.sheepstar.util.bot.listener.GuildListener;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MultipleReactionListener extends GuildListener {
+
+ private PollManager pollManager = (PollManager) table(PollManager.class);
+ private PollAnswerManager pollAnswerManager = (PollAnswerManager) table(PollAnswerManager.class);
+
+ /**
+ * Checks if the provided emoji is valid
+ *
+ * @param emoji The emoji you want to check
+ * @param message The message to get the size of the answers
+ * @return true
if the emoji is valid, otherwise false
+ */
+ public boolean isValidEmote(String emoji, Message message) {
+ for (int i = 0; i < pollAnswerManager.getAnswers(pollManager.getIDByMessage(message)).size(); i++) {
+ if (emoji.contains(String.valueOf(i + 1))) return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onGuildMessageReactionAdd(GuildMessageReactionAddEvent event) {
+ event.retrieveMessage().queue(message -> {
+ if (event.getUser().getId().equals(jda.getSelfUser().getId())) return;
+ if (!message.getAuthor().getId().equals(jda.getSelfUser().getId())) return;
+ if (!pollManager.isPollMessage(message)) return;
+
+ // TODO: Migrate to id instead of message
+
+ if (!event.getReactionEmote().isEmoji()) {
+ event.getReaction().removeReaction(event.getUser()).queue();
+ return;
+ }
+
+ if (!isValidEmote(event.getReactionEmote().getEmoji(), message)) {
+ event.getReaction().removeReaction(event.getUser()).queue();
+ return;
+ }
+
+ String[] embedLines = message.getEmbeds().get(0).getDescription().split("\n");
+
+ for (String line : embedLines)
+ if (line.startsWith("**:ballot_box:") && line.contains("(")) return;
+
+ AtomicInteger reactAmount = new AtomicInteger();
+ message.getReactions().forEach(reaction -> reaction.retrieveUsers().queue(users -> users.forEach(user -> {
+ if (user.getId().equals(event.getUser().getId())) reactAmount.getAndIncrement();
+
+ if (reactAmount.get() > 1) {
+ event.getReaction().removeReaction(event.getUser()).queue();
+ return;
+ }
+ })));
+ });
+ }
+}
diff --git a/SheepstarModules/PollV1/src/main/resources/module.yml b/SheepstarModules/PollV1/src/main/resources/module.yml
new file mode 100644
index 0000000..4dce574
--- /dev/null
+++ b/SheepstarModules/PollV1/src/main/resources/module.yml
@@ -0,0 +1,3 @@
+main: xyz.sheepstar.poll.core.PollCore
+name: poll
+author: Mathias Wagner
\ No newline at end of file