Added the giveaway module V1

This commit is contained in:
Mathias Wagner 2022-09-06 16:32:30 +02:00
parent 526dba12b8
commit 0817607697
11 changed files with 595 additions and 0 deletions

View File

@ -0,0 +1,3 @@
# Project exclude paths
/target/
.idea

View File

@ -0,0 +1,2 @@
# SheepstarModule-Giveaway
The official sheepstar giveaway module

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.sheepstar</groupId>
<artifactId>SheepstarModule-Giveaway</artifactId>
<version>pre1.0.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>xyz.sheepstar</groupId>
<artifactId>SheepstarCore</artifactId>
<version>beta1.0.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,27 @@
package xyz.sheepstar.giveaway.action;
import xyz.sheepstar.giveaway.api.controller.GiveawayController;
import xyz.sheepstar.giveaway.api.models.GiveawayManager;
import xyz.sheepstar.giveaway.core.GiveawayCore;
import xyz.sheepstar.util.action.RepeatedAction;
import java.time.LocalDateTime;
public class AutomaticCloseAction extends RepeatedAction {
private final GiveawayManager giveawayManager = (GiveawayManager) api.getDatabase().getTableFactory().getTable(GiveawayManager.class);
private final GiveawayController giveawayController = GiveawayCore.getGiveawayController();
@Override
public long time() {
return 5;
}
@Override
public void execute() {
giveawayManager.getExpirableGiveaways().forEach((giveawayId, dateTime) -> {
if (dateTime.compareTo(LocalDateTime.now()) <= 0)
giveawayController.endGiveaway(giveawayId, giveawayManager.getGiveawayById(giveawayId).getGuild());
});
}
}

View File

@ -0,0 +1,184 @@
package xyz.sheepstar.giveaway.api.controller;
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.entities.User;
import net.dv8tion.jda.api.interactions.components.ButtonStyle;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
import xyz.sheepstar.giveaway.api.entities.Giveaway;
import xyz.sheepstar.giveaway.api.models.GiveawayManager;
import xyz.sheepstar.giveaway.commands.CloseGiveawayCommand;
import xyz.sheepstar.util.bot.builder.message.DefaultResponseBuilder;
import xyz.sheepstar.util.bot.listener.ListenerBasics;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
public class GiveawayController extends ListenerBasics {
private final GiveawayManager giveawayManager = (GiveawayManager) table(GiveawayManager.class);
/**
* Gets the "start"-message of the giveaway
*
* @param giveawayID The id of the giveaway
* @param title The title of the giveaway
* @param description The description of the giveaway
* @param guild The guild that created the giveaway
* @param expiry The expiry date of the giveaway
* @param closeButton Should a close button be displayed?
* @param maxWinners The max amount of winners
* @return the created message
*/
public Message getStartMessage(String giveawayID, String title, String description, Guild guild, Date expiry, boolean closeButton, int maxWinners) {
DefaultResponseBuilder embedBuilder = DefaultResponseBuilder.createSimple(guild, CloseGiveawayCommand.class)
.withTitle(translate("giveaway.giveaway_started", guild));
embedBuilder.addField(":gift: " + translate("giveaway.object.title", guild), title);
if (description != null)
embedBuilder.addField(":clipboard: " + translate("giveaway.object.description", guild), description);
if (expiry != null)
embedBuilder.addField(String.format(":alarm_clock: %s <t:%s:R>", translate("poll.ends", guild), expiry.getTime() / 1000));
embedBuilder.addField(translate("giveaway.object.react", guild, "\uD83C\uDF89"));
if (closeButton)
embedBuilder.addButton(ButtonStyle.DANGER, "end#" + giveawayID, expiry == null
? translate("giveaway.end_giveaway", guild) : translate("giveaway.end_now", guild));
return embedBuilder.build();
}
/**
* Gets the "end"-message of the giveaway
*
* @param giveaway The giveaway object
* @param winner The list of winners that should be displayed
* @return the created "end"-message
*/
public Message getEndMessage(Giveaway giveaway, List<User> winner) {
DefaultResponseBuilder embedBuilder = DefaultResponseBuilder.createSimple(giveaway.getGuild(), CloseGiveawayCommand.class)
.withTitle(translate("giveaway.giveaway_ended", giveaway.getGuild()));
embedBuilder.addField(":gift: " + translate("giveaway.object.title", giveaway.getGuild()), giveaway.getTitle());
if (giveaway.getDescription() != null)
embedBuilder.addField(":clipboard: " + translate("giveaway.object.description", giveaway.getGuild()), giveaway.getDescription());
ArrayList<String> userMentions = new ArrayList<>();
winner.forEach(user -> userMentions.add(user.getAsMention()));
if (userMentions.isEmpty())
userMentions.add(":no_entry_sign: " + translate("giveaway.object.found_no_one", giveaway.getGuild()));
embedBuilder.addField(":trophy: " + translate("giveaway.object.winner", giveaway.getGuild(), winner.size()), userMentions);
return embedBuilder.build();
}
/**
* Creates a new giveaway
*
* @param title The title the giveaway should have
* @param description The description of the giveaway
* Set it to <b>NULL</b>, if you don't have one
* @param channel The channel in which the message should be sent
* @param expiry The expiry date of the giveaway
* @param closeButton Should a close button be displayed?
* @param maxEntries The max amount of entries in the giveaway
* @return the id of the created giveaway
*/
public String createGiveaway(String title, String description, TextChannel channel, Date expiry, boolean closeButton, int maxEntries) {
String pollID = RandomStringUtils.randomAlphanumeric(10).toUpperCase();
channel.sendMessage(getStartMessage(pollID, title, description, channel.getGuild(), expiry, closeButton, maxEntries)).queue(message -> {
giveawayManager.createGiveaway(pollID, title, description, message, expiry, maxEntries);
message.addReaction("\uD83C\uDF89").queue();
});
return pollID;
}
/**
* Ends a giveaway
*
* @param id The id of the giveaway
* @param guild The guild that created the giveaway
* @return the list of users that won. <code>NULL</code> if the giveaway could not be found
*/
public List<User> endGiveaway(String id, Guild guild) {
if (!giveawayManager.isCreated(id, guild)) return null;
try {
Giveaway giveaway = giveawayManager.getGiveawayById(id);
int maxWinners = giveaway.getMaxEntries();
List<User> emote_reactions = new ArrayList<>();
giveaway.getMessage().getReactions().forEach(reaction -> emote_reactions.addAll(reaction.retrieveUsers().complete()));
List<User> winners = pickWinners(emote_reactions.stream().filter(user -> !user.isBot()).collect(Collectors.toList()),
maxWinners);
giveawayManager.deleteGiveaway(id, guild);
giveaway.getMessage().editMessage(getEndMessage(giveaway, winners)).queue();
giveaway.getMessage().clearReactions().queue();
if (api.getBotManager().getSettingsManager().getSettingString(guild, "giveaway", "mention_winners").equals("yes"))
winners.forEach(user -> giveaway.getMessage().getChannel().sendMessage(user.getAsMention())
.queue(message -> message.delete().queue()));
return winners;
} catch (Exception e) {
return null;
}
}
/**
* Picks winners from a list
*
* @param possibleWinners All possible winners
* @param maxWinners The max amount of winners
* @return the list of users that actually won
*/
public List<User> pickWinners(List<User> possibleWinners, int maxWinners) {
List<User> winners = new ArrayList<>();
if (possibleWinners.size() == 0) return winners;
for (int i = 0; i < maxWinners; i++) {
if (i > possibleWinners.size()) break;
User currentWinner = pickWinner(possibleWinners);
while (winners.contains(currentWinner)) {
currentWinner = pickWinner(possibleWinners);
}
winners.add(currentWinner);
possibleWinners.remove(currentWinner);
}
return winners;
}
/**
* Picks a winner from the list
*
* @param winners The list of possible winners
* @return the lucky person that won
*/
public User pickWinner(List<User> winners) {
return winners.get(RandomUtils.nextInt(0, winners.size()));
}
}

View File

@ -0,0 +1,113 @@
package xyz.sheepstar.giveaway.api.entities;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.TextChannel;
import java.time.LocalDateTime;
public class Giveaway {
private final String id;
private final String title;
private final String description;
private final LocalDateTime expiryDate;
private final int maxEntries;
private final Guild guild;
private final TextChannel channel;
private final Message message;
/**
* Constructor of the {@link Giveaway}
*
* @param id The id of the giveaway
* @param title The title of the giveaway
* @param description The description of the giveaway
* @param expiryDate The expiry date of the giveaway
* @param message The message of the giveaway
* @param maxEntries The max number of winners
*/
public Giveaway(String id, String title, String description, LocalDateTime expiryDate, Message message, int maxEntries) {
this.id = id;
this.title = title;
this.description = description;
this.expiryDate = expiryDate;
this.guild = message.getGuild();
this.channel = message.getTextChannel();
this.message = message;
this.maxEntries = maxEntries;
}
/**
* Gets the id of the giveaway
*
* @return the id of the giveaway
*/
public String getId() {
return id;
}
/**
* Gets the title of the giveaway
*
* @return the title of the giveaway
*/
public String getTitle() {
return title;
}
/**
* Gets the description of the giveaway
*
* @return the description of the giveaway
*/
public String getDescription() {
return description;
}
/**
* Gets the expiry date of the giveaway
*
* @return the expiry date od the giveaway
*/
public LocalDateTime getExpiryDate() {
return expiryDate;
}
/**
* Gets the guild that created the giveaway
*
* @return the guild that created the giveaway
*/
public Guild getGuild() {
return guild;
}
/**
* Gets the channel of the giveaway
*
* @return the channel of the giveaway
*/
public TextChannel getChannel() {
return channel;
}
/**
* Gets the giveaway message
*
* @return the giveaway message
*/
public Message getMessage() {
return message;
}
/**
* Gets the number of max winners
*
* @return the number of max winners
*/
public int getMaxEntries() {
return maxEntries;
}
}

View File

@ -0,0 +1,108 @@
package xyz.sheepstar.giveaway.api.models;
import de.gnmyt.sqltoolkit.manager.SelectionManager;
import de.gnmyt.sqltoolkit.types.SQLType;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import xyz.sheepstar.giveaway.api.entities.Giveaway;
import xyz.sheepstar.util.sql.SheepManager;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
public class GiveawayManager extends SheepManager {
@Override
protected String tableName() {
return "giveaway_giveaways";
}
@Override
protected void tableFields() {
custom("giveaway_id").add();
custom("title").add();
custom("description").allowNull(true).add();
custom("guild_id").add();
custom("channel_id").add();
custom("message_id").add();
custom("max_entries").type(SQLType.INTEGER).add();
custom("expiry").type(SQLType.DATETIME).allowNull(true).add();
}
/**
* Checks if the provided giveaway has been created
*
* @param id The id of the giveaway you want to check
* @param guild The guild that created the giveaway
* @return <code>true</code> if the giveaway has been created, otherwise <code>false</code>
*/
public boolean isCreated(String id, Guild guild) {
return select().where("giveaway_id", id).where("guild_id", guild.getId()).getResult().exists();
}
/**
* Creates a new giveaway
*
* @param id The id of the giveaway
* @param title The title of the giveaway
* @param description The description of the giveaway
* @param message The message of the giveaway
* @param expiry The expiry date of the giveaway
* @param maxEntries The max number of winners
*/
public void createGiveaway(String id, String title, String description, Message message, Date expiry, int maxEntries) {
if (isCreated(id, message.getGuild())) return;
insert()
.value("giveaway_id", id)
.value("title", title)
.value("description", description)
.value("guild_id", message.getGuild().getId())
.value("channel_id", message.getChannel().getId())
.value("message_id", message.getId())
.value("expiry", expiry)
.value("max_entries", maxEntries)
.execute();
}
/**
* Deletes a giveaway from the table
*
* @param id The id of the giveaway
* @param guild The guild that created the giveaway
*/
public void deleteGiveaway(String id, Guild guild) {
if (!isCreated(id, guild)) return;
delete().where("giveaway_id", id).where("guild_id", guild.getId()).execute();
}
/**
* Gets all giveaways that <b>can</b> expire
*
* @return all giveaways that can expire
*/
public HashMap<String, LocalDateTime> getExpirableGiveaways() {
HashMap<String, LocalDateTime> expirable_giveaways = new HashMap<>();
select().getResult().getList().forEach(entry -> {
if (entry.get("expiry") != null)
expirable_giveaways.put((String) entry.get("giveaway_id"), (LocalDateTime) entry.get("expiry"));
});
return expirable_giveaways;
}
/**
* Gets a giveaway object by id
*
* @param id The id of the giveaway
* @return the created {@link Giveaway} instance
*/
public Giveaway getGiveawayById(String id) {
SelectionManager giveaway = select().where("giveaway_id", id);
return new Giveaway(id, giveaway.getResult().getString("title"),
giveaway.getResult().getString("description"), (LocalDateTime) giveaway.getResult().getObject("expiry"),
api.getJDA().getTextChannelById(giveaway.getResult().getString("channel_id"))
.retrieveMessageById(giveaway.getResult().getString("message_id")).complete(), giveaway.getResult().getInteger("max_entries"));
}
}

View File

@ -0,0 +1,37 @@
package xyz.sheepstar.giveaway.commands;
import xyz.sheepstar.giveaway.api.controller.GiveawayController;
import xyz.sheepstar.giveaway.core.GiveawayCore;
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 = "giveaway", subAliases = "close", description = "Closes an existing giveaway", permission = PermissionNode.ADMINISTRATOR)
public class CloseGiveawayCommand extends GuildCommand {
@Override
public void usage() {
usage("id", "The id of the giveaway").required(true).add();
}
private final GiveawayController giveawayController = GiveawayCore.getGiveawayController();
@Override
public void execute(GuildEventController event, Arguments args) throws Exception {
String id = args.getString("id");
if (giveawayController.endGiveaway(id, event.getGuild()) != null) {
event.success("giveaway.giveaway_ended_successfully", id);
} else throw new PublicCommandException("giveaway.unknown_id", id);
}
@Override
public void buttonClick(GuildEventController event, String id) throws Exception {
if (!(id.startsWith("end") && id.contains("#"))) return;
String giveawayId = id.split("#")[1];
giveawayController.endGiveaway(giveawayId, event.getGuild());
}
}

View File

@ -0,0 +1,61 @@
package xyz.sheepstar.giveaway.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.giveaway.api.controller.GiveawayController;
import xyz.sheepstar.giveaway.core.GiveawayCore;
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.Date;
@CommandMeta(aliases = "giveaway", subAliases = "create", description = "Creates a new giveaway", permission = PermissionNode.ADMINISTRATOR)
public class CreateGiveawayCommand extends GuildCommand {
private final GiveawayController giveawayController = GiveawayCore.getGiveawayController();
@Override
public void usage() {
usage("price", "What can the user win?").required(true).add();
usage("description", "Want to add more information about the price / giveaway?").add();
usage("time", "When should the poll automatically be dissolved").add();
usage(OptionType.INTEGER, "winners", "How many winners can win this item? (Default: 1, allowed: 1-25)").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.CHANNEL, "channel", "The channel in which the message should be sent (for example if you have an poll channel)").add();
}
@Override
public void execute(GuildEventController event, Arguments args) throws Exception {
GuildChannel channel = args.exists("channel") ? args.getChannel("channel") : event.getChannel();
if (channel.getType() != ChannelType.TEXT)
throw new PublicCommandException("giveaway.error.channel.must_be_text");
String title = args.getString("price");
String description = args.getString("description");
Date date = args.exists("time") ? getTimeFromString(args.getString("time")) : null;
int winners = args.exists("winners") ? args.getInteger("winners") : 1;
boolean close_button = !args.exists("close_button") || args.getBoolean("close_button");
if (winners < 1 || winners > 25)
throw new PublicCommandException("giveaway.error.incorrect_winner_value");
String id = giveawayController.createGiveaway(title, description, (TextChannel) channel, date, close_button, winners);
event.primary("giveaway.giveaway_created", channel.getAsMention(), id);
}
}

View File

@ -0,0 +1,33 @@
package xyz.sheepstar.giveaway.core;
import xyz.sheepstar.giveaway.action.AutomaticCloseAction;
import xyz.sheepstar.giveaway.api.controller.GiveawayController;
import xyz.sheepstar.giveaway.api.models.GiveawayManager;
import xyz.sheepstar.giveaway.commands.CloseGiveawayCommand;
import xyz.sheepstar.giveaway.commands.CreateGiveawayCommand;
import xyz.sheepstar.util.bot.manager.ImportManager;
import xyz.sheepstar.util.module.SheepstarModule;
public class GiveawayCore extends SheepstarModule {
private static GiveawayController giveawayController;
private ImportManager importManager;
@Override
public void onEnable() {
importManager = new ImportManager(getAPI(), "giveaway");
registerTable(new GiveawayManager());
giveawayController = new GiveawayController();
importManager.registerCommand(new CreateGiveawayCommand());
importManager.registerCommand(new CloseGiveawayCommand());
new AutomaticCloseAction().start();
}
public static GiveawayController getGiveawayController() {
return giveawayController;
}
}

View File

@ -0,0 +1,3 @@
main: xyz.sheepstar.giveaway.core.GiveawayCore
name: giveaway
author: Mathias Wagner