60 Commits

Author SHA1 Message Date
e5c5993fb2 Make random statuses update automatically
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 22:08:05 +01:00
d331c48ced Fix invite command being categorized as fun
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 18:04:37 +01:00
84ff5a752e Improve help command title
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 18:03:02 +01:00
4f408fb5f9 Make permissions bold instead of code-wrapped
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 17:58:14 +01:00
1384259187 Bump version to 0.5.10
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 17:55:43 +01:00
82698ec5fe Fix nothing being rolles if no arg was specified
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 17:55:30 +01:00
0762068465 Make help command use descriptions and usages
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 17:51:28 +01:00
cc9aee3441 Fix spacing 2022-12-20 17:23:52 +01:00
86c7c30d8f Improve default responses 2022-12-20 17:23:20 +01:00
19e3cde7e6 Bump version to 0.5.9
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 17:18:39 +01:00
72f9bb4eb5 Make command category not null
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 17:18:24 +01:00
407ca279f5 Implement alias command 2022-12-20 17:18:09 +01:00
9eefa4b958 Implement alias command 2022-12-20 17:17:13 +01:00
9278b485d9 Add help command and command categories
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 17:06:18 +01:00
5a2205e567 Remove deprecated and unused private method
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 15:15:03 +01:00
3212ceb03c Throw exception in case of serialization issue
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 15:13:21 +01:00
a80b2cc5a9 Make serialization util class
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 15:11:44 +01:00
d087de1d01 Use enum instead of boolean for page switching
All checks were successful
continuous-integration/drone/push Build is passing
This is useless but looks better
2022-12-20 15:09:35 +01:00
99fc980e00 Remove duplication
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 15:04:39 +01:00
02627ab732 Add javadoc comment
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 14:53:58 +01:00
dead16f338 Merge two classes
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 14:51:29 +01:00
2f51f9d40c Optimize imports
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 14:50:09 +01:00
5f5fc8d3a8 Remove duplicated method
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 14:49:44 +01:00
4c98182da7 Fix small emoji translation issue
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 03:44:00 +01:00
9e57a3a426 Optimize imports
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 03:17:32 +01:00
2bf08c27b7 Make urban command support slash too
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 02:17:27 +01:00
b085efeccb Allow sender to delete their own urban command results
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 00:59:26 +01:00
d8604c7ae5 Fix urban dictionary term not getting parsed correctly for url
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 00:52:31 +01:00
e83d7de7f5 Bump version to 0.5.8
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 00:35:08 +01:00
dfa25e54f3 Make urban command support multiple entries
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-20 00:34:49 +01:00
d1dc71dde9 Improve urban dictionary parsing
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 22:53:25 +01:00
6fcd3b4cdf Improve urban dictionary parsing
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 22:45:02 +01:00
deb7d83e64 Keep newlines in urban dictionary parser
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 21:58:57 +01:00
010a25fd66 Bump version to 0.5.7
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 21:43:07 +01:00
7c2530c88b Implement urban dictionary lookup command
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 21:42:55 +01:00
20665f4862 Change a magic ball response
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 21:03:20 +01:00
ecfa3cded8 Make it send a message instead of responding to 8ball
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 21:00:25 +01:00
90e0c4ddf9 Bump version to 0.5.6
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 20:39:16 +01:00
fd9fe4ead6 Add basic love calculator message command
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 20:38:32 +01:00
6cdd44da29 Bump version to 0.5.5
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 18:37:38 +01:00
3dd30a3a89 Make magicball support slash commands too
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 18:37:17 +01:00
5c8bad2b02 Improve magic ball answers
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 18:28:21 +01:00
b23bc30fc0 Implement magic ball message command
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 18:24:29 +01:00
018e24034f Bump version to 0.5.4
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 17:32:18 +01:00
Lorenzo Dellacà
1a19a9ea06 Improve diceroll looks, implement limits to avoid abuse
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 17:31:28 +01:00
Lorenzo Dellacà
495f164552 Ignore bots interacting with hideko
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 16:54:15 +01:00
Lorenzo Dellacà
fd100649a7 Remove unneeded todo 2022-12-19 16:48:20 +01:00
Lorenzo Dellacà
b3990ff04f Make clear command also delete the sender's message
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 16:47:49 +01:00
Lorenzo Dellacà
f5238ced89 Bump version to 0.5.3
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 01:41:04 +01:00
Lorenzo Dellacà
f0ee565185 Implement basic functional diceroll command
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 01:36:43 +01:00
Lorenzo Dellacà
a21d179308 Fix command label being passed as arg in case of no args
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 00:22:51 +01:00
Lorenzo Dellacà
36ad728bbc Fallback to 0 instead of 1
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 00:14:30 +01:00
Lorenzo Dellacà
1a6fe6465c Fix console error when int parsing fails in clear message
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 00:13:14 +01:00
Lorenzo Dellacà
f0004dc555 Re-register accidentally removed invite command
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 00:10:15 +01:00
Lorenzo Dellacà
8ddf0ab80d Bump JDA version to more stable beta
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 00:07:02 +01:00
Lorenzo Dellacà
660e18d1f4 Bump version to 0.5.2
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 00:05:49 +01:00
Lorenzo Dellacà
db943f7e05 Fix messages with newlines not being handled for commands
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-19 00:05:36 +01:00
Lorenzo Dellacà
cb49bda84a Make say support both slash and message commands 2022-12-19 00:05:13 +01:00
Lorenzo Dellacà
b318b9f22b Bump version to 0.5.1
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-18 23:49:00 +01:00
Lorenzo Dellacà
1447f8c177 Make avatar support both slash and message commands
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-18 23:47:54 +01:00
36 changed files with 2152 additions and 113 deletions

19
pom.xml
View File

@@ -6,7 +6,7 @@
<groupId>wtf.beatrice.hidekobot</groupId> <groupId>wtf.beatrice.hidekobot</groupId>
<artifactId>HidekoBot</artifactId> <artifactId>HidekoBot</artifactId>
<version>0.5.0</version> <version>0.5.10</version>
<properties> <properties>
<maven.compiler.source>16</maven.compiler.source> <maven.compiler.source>16</maven.compiler.source>
@@ -18,7 +18,7 @@
<dependency> <dependency>
<groupId>net.dv8tion</groupId> <groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId> <artifactId>JDA</artifactId>
<version>5.0.0-alpha.22</version> <version>5.0.0-beta.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
@@ -40,6 +40,21 @@
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>1.33</version> <version>1.33</version>
</dependency> </dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.15.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>emoji-java</artifactId>
<version>5.1.1</version>
</dependency>
</dependencies> </dependencies>

View File

@@ -18,8 +18,9 @@ import wtf.beatrice.hidekobot.listeners.SlashCommandCompletionListener;
import wtf.beatrice.hidekobot.listeners.SlashCommandListener; import wtf.beatrice.hidekobot.listeners.SlashCommandListener;
import wtf.beatrice.hidekobot.runnables.ExpiredMessageTask; import wtf.beatrice.hidekobot.runnables.ExpiredMessageTask;
import wtf.beatrice.hidekobot.runnables.HeartBeatTask; import wtf.beatrice.hidekobot.runnables.HeartBeatTask;
import wtf.beatrice.hidekobot.runnables.StatusUpdateTask;
import wtf.beatrice.hidekobot.util.CommandUtil;
import wtf.beatrice.hidekobot.util.Logger; import wtf.beatrice.hidekobot.util.Logger;
import wtf.beatrice.hidekobot.util.SlashCommandUtil;
import java.io.File; import java.io.File;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -121,18 +122,28 @@ public class HidekoBot
slashCommandListener.registerCommand(new DieCommand()); slashCommandListener.registerCommand(new DieCommand());
slashCommandListener.registerCommand(new HelpCommand()); slashCommandListener.registerCommand(new HelpCommand());
slashCommandListener.registerCommand(new InviteCommand()); slashCommandListener.registerCommand(new InviteCommand());
slashCommandListener.registerCommand(new MagicBallCommand());
slashCommandListener.registerCommand(new PingCommand()); slashCommandListener.registerCommand(new PingCommand());
slashCommandListener.registerCommand(new SayCommand()); slashCommandListener.registerCommand(new SayCommand());
slashCommandListener.registerCommand(new UrbanDictionaryCommand());
Cache.setSlashCommandListener(slashCommandListener); Cache.setSlashCommandListener(slashCommandListener);
Cache.setSlashCommandCompletionListener(slashCommandCompletionListener); Cache.setSlashCommandCompletionListener(slashCommandCompletionListener);
// register message commands // register message commands
MessageCommandListener messageCommandListener = new MessageCommandListener(); MessageCommandListener messageCommandListener = new MessageCommandListener();
messageCommandListener.registerCommand(new HelloCommand()); messageCommandListener.registerCommand(new HelloCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.InviteCommand()); messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.AliasCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.AvatarCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.BotInfoCommand()); messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.BotInfoCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.CoinFlipCommand()); messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.CoinFlipCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.ClearCommand()); messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.ClearCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.DiceRollCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.HelpCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.InviteCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.LoveCalculatorCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.MagicBallCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.SayCommand());
messageCommandListener.registerCommand(new wtf.beatrice.hidekobot.commands.message.UrbanDictionaryCommand());
Cache.setMessageCommandListener(messageCommandListener); Cache.setMessageCommandListener(messageCommandListener);
// register listeners // register listeners
@@ -144,11 +155,10 @@ public class HidekoBot
// update slash commands (delayed) // update slash commands (delayed)
final boolean finalForceUpdateCommands = forceUpdateCommands; final boolean finalForceUpdateCommands = forceUpdateCommands;
Executors.newSingleThreadScheduledExecutor().schedule(() -> // todo: try-with-resources Executors.newSingleThreadScheduledExecutor().schedule(() -> // todo: try-with-resources
SlashCommandUtil.updateSlashCommands(finalForceUpdateCommands), 1, TimeUnit.SECONDS); CommandUtil.updateSlashCommands(finalForceUpdateCommands), 1, TimeUnit.SECONDS);
// set the bot's status // set the bot's status
jda.getPresence().setStatus(OnlineStatus.ONLINE); jda.getPresence().setStatus(OnlineStatus.ONLINE);
jda.getPresence().setActivity(Activity.playing("Hatsune Miku: Project DIVA"));
// connect to database // connect to database
logger.log("Connecting to database..."); logger.log("Connecting to database...");
@@ -172,6 +182,8 @@ public class HidekoBot
scheduler.scheduleAtFixedRate(expiredMessageTask, 5, 5, TimeUnit.SECONDS); //every 5 seconds scheduler.scheduleAtFixedRate(expiredMessageTask, 5, 5, TimeUnit.SECONDS); //every 5 seconds
HeartBeatTask heartBeatTask = new HeartBeatTask(); HeartBeatTask heartBeatTask = new HeartBeatTask();
scheduler.scheduleAtFixedRate(heartBeatTask, 10, 30, TimeUnit.SECONDS); //every 30 seconds scheduler.scheduleAtFixedRate(heartBeatTask, 10, 30, TimeUnit.SECONDS); //every 30 seconds
StatusUpdateTask statusUpdateTask = new StatusUpdateTask();
scheduler.scheduleAtFixedRate(statusUpdateTask, 0, 60 * 5, TimeUnit.SECONDS); // every 5 minutes
// register shutdown interrupt signal listener for proper shutdown. // register shutdown interrupt signal listener for proper shutdown.
Signal.handle(new Signal("INT"), signal -> shutdown()); Signal.handle(new Signal("INT"), signal -> shutdown());

View File

@@ -0,0 +1,23 @@
package wtf.beatrice.hidekobot.commands.base;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.LinkedList;
public class Alias
{
public static String generateNiceAliases(MessageCommand command)
{
LinkedList<String> aliases = command.getCommandLabels();
StringBuilder aliasesStringBuilder = new StringBuilder();
for(int i = 0; i < aliases.size(); i++)
{
aliasesStringBuilder.append("`").append(aliases.get(i)).append("`");
if(i + 1 != aliases.size())
aliasesStringBuilder.append(", "); // separate with comma except on last iteration
}
return aliasesStringBuilder.toString();
}
}

View File

@@ -0,0 +1,62 @@
package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import wtf.beatrice.hidekobot.Cache;
public class Avatar
{
public static int parseResolution(int resolution)
{
int[] acceptedSizes = Cache.getSupportedAvatarResolutions();
// method to find closest value to accepted values
int distance = Math.abs(acceptedSizes[0] - resolution);
int idx = 0;
for(int c = 1; c < acceptedSizes.length; c++){
int cdistance = Math.abs(acceptedSizes[c] - resolution);
if(cdistance < distance){
idx = c;
distance = cdistance;
}
}
return acceptedSizes[idx];
}
public static MessageEmbed buildEmbed(int resolution, User user)
{
int[] acceptedSizes = Cache.getSupportedAvatarResolutions();
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle("Profile picture");
embedBuilder.addField("User", "<@" + user.getId() + ">", false);
embedBuilder.addField("Current resolution", resolution + " × " + resolution, false);
// string builder to create a string that links to all available resolutions
StringBuilder links = new StringBuilder();
for(int pos = 0; pos < acceptedSizes.length; pos++)
{
int currSize = acceptedSizes[pos];
String currLink = user.getEffectiveAvatar().getUrl(currSize);
links.append("[").append(currSize).append("px](").append(currLink).append(")");
if(pos + 1 != acceptedSizes.length) // don't add a separator on the last iteration
{
links.append(" | ");
}
}
embedBuilder.addField("Available resolutions", links.toString(), false);
embedBuilder.setImage(user.getEffectiveAvatar().getUrl(resolution));
return embedBuilder.build();
}
}

View File

@@ -7,10 +7,8 @@ import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.InteractionHook; import net.dv8tion.jda.api.interactions.InteractionHook;
import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.components.buttons.Button;
import wtf.beatrice.hidekobot.Cache;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -46,12 +44,15 @@ public class ClearChat
return null; return null;
} }
public static int delete(int toDeleteAmount, long startingMessageId, MessageChannel channel) public static int delete(int toDeleteAmount,
long startingMessageId,
MessageChannel channel)
{ {
// int to keep track of how many messages we actually deleted. // int to keep track of how many messages we actually deleted.
int deleted = 0; int deleted = 0;
int limit = 95; //discord limits this method to range 2-100. we set it to 95 to be safe. int limit = 95; //discord limits this method to only 2<x<100 deletions per run.
// we set this slightly lower to be safe, and iterate as needed.
// increase the count by 1, because we technically aren't clearing the first ID ever // increase the count by 1, because we technically aren't clearing the first ID ever
// which is actually the slash command's ID and not a message. // which is actually the slash command's ID and not a message.
@@ -149,12 +150,13 @@ public class ClearChat
public static Button getDismissButton() public static Button getDismissButton()
{ {
return Button.primary("clear_dismiss", "Dismiss") return Button.primary("generic_dismiss", "Dismiss")
.withEmoji(Emoji.fromUnicode("")); .withEmoji(Emoji.fromUnicode(""));
} }
public static String parseAmount(int deleted) public static String parseAmount(int deleted)
{ {
if(deleted < 1) if(deleted < 1)
{ {
return "\uD83D\uDE22 Couldn't clear any message!"; return "\uD83D\uDE22 Couldn't clear any message!";
@@ -166,27 +168,7 @@ public class ClearChat
} }
} }
private void respond(Object responseFlowObj, String content) // cap the amount to avoid abuse.
{ public static int getMaxAmount() { return 1000; }
if(responseFlowObj instanceof InteractionHook) {
((InteractionHook) responseFlowObj).editOriginal(content).queue();
} else if (responseFlowObj instanceof Message) {
((Message) responseFlowObj).reply(content).queue();
}
}
public static void dismissMessage(ButtonInteractionEvent event)
{
if(!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId())))
{
event.reply("❌ You did not run this command!").setEphemeral(true).queue();
} else
{
event.getInteraction().getMessage().delete().queue();
}
}
} }

View File

@@ -17,7 +17,7 @@ public class CoinFlip
public static Button getReflipButton() { public static Button getReflipButton() {
return Button.primary("coinflip_reflip", "Flip again") return Button.primary("coinflip_reflip", "Flip again")
.withEmoji(Emoji.fromFormatted("\uD83E\uDE99")); .withEmoji(Emoji.fromUnicode("\uD83E\uDE99"));
} }
public static String genRandom() public static String genRandom()
@@ -61,8 +61,6 @@ public class CoinFlip
public static void trackAndRestrict(Message replyMessage, User user) public static void trackAndRestrict(Message replyMessage, User user)
{ {
String replyMessageId = replyMessage.getId();
Cache.getDatabaseSource().queueDisabling(replyMessage); Cache.getDatabaseSource().queueDisabling(replyMessage);
Cache.getDatabaseSource().trackRanCommandReply(replyMessage, user); Cache.getDatabaseSource().trackRanCommandReply(replyMessage, user);
} }

View File

@@ -0,0 +1,67 @@
package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.util.RandomUtil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class MagicBall
{
public static LinkedList<String> getLabels()
{
return new LinkedList<>(Arrays.asList("8ball", "8b", "eightball", "magicball"));
}
private final static List<String> answers = new ArrayList<>(
Arrays.asList("It is certain.",
"It is decidedly so.",
"Without a doubt.",
"Yes, definitely.",
"That would be a yes.",
"As I see it, yes.",
"Most likely.",
"Looks like it.",
"Yes.",
"Signs point to yes.",
"Reply hazy, try again.",
"Ask again later.",
"Better not tell you now.",
"Seems uncertain.",
"Concentrate and ask again.",
"Don't count on it.",
"My answer is no.",
"My sources say no.",
"Outlook not so good.",
"Very doubtful."));
public static String getRandomAnswer()
{
int answerPos = RandomUtil.getRandomNumber(0, answers.size() - 1);
return answers.get(answerPos);
}
public static MessageEmbed generateEmbed(String question, User author)
{
// add a question mark at the end, if missing.
// this might not always apply but it's fun
if(!question.endsWith("?")) question += "?";
String answer = getRandomAnswer();
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl());
embedBuilder.setTitle("Magic Ball");
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.addField("❓ Question", question, false);
embedBuilder.addField("\uD83C\uDFB1 Answer", answer, false);
return embedBuilder.build();
}
}

View File

@@ -0,0 +1,11 @@
package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.Permission;
public class Say
{
public static Permission getPermission() {
return Permission.MESSAGE_MANAGE;
}
}

View File

@@ -0,0 +1,320 @@
package wtf.beatrice.hidekobot.commands.base;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.ItemComponent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.text.WordUtils;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.util.SerializationUtil;
import java.util.*;
public class UrbanDictionary
{
public static LinkedList<String> getCommandLabels()
{ return new LinkedList<>(Arrays.asList("urban", "urbandictionary", "ud")); }
public static String getBaseUrl() {
return "https://www.urbandictionary.com/define.php?term=";
}
public static Button getPreviousPageButton()
{
return Button.primary("urban_previouspage", "Back")
.withEmoji(Emoji.fromFormatted("⬅️"));
}
public static Button getNextPageButton()
{
return Button.primary("urban_nextpage", "Next")
.withEmoji(Emoji.fromFormatted("➡️"));
}
public static Button getDeleteButton()
{
return Button.danger("generic_dismiss", "Delete")
.withEmoji(Emoji.fromFormatted("\uD83D\uDDD1"));
}
public static String getNoArgsError() {
return "\uD83D\uDE22 I need to know what to search for!";
}
public static String sanitizeArgs(String term, boolean forUrl)
{
term = term.replaceAll("[^\\d\\w\\s]", ""); // only keep letters, numbers and spaces
term = WordUtils.capitalizeFully(term); // Make Every Word Start With A Capital Letter
if(forUrl) term = term.replaceAll("\\s+", "+"); // replace all whitespaces with + for the url
if (term.length() > 64) term = term.substring(0, 64); // cut it to length to avoid abuse
return term;
}
public static String generateUrl(String term)
{
return getBaseUrl() + sanitizeArgs(term, true);
}
public static MessageEmbed buildEmbed(String term,
String url,
User author,
UrbanSearch search,
int page)
{
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle(term + ", on Urban Dictionary", url);
embedBuilder.setAuthor(author.getAsTag(), null, author.getAvatarUrl());
embedBuilder.addField("Definition", search.getPlaintextMeanings().get(page), false);
embedBuilder.addField("Example", search.getPlaintextExamples().get(page), false);
embedBuilder.addField("Submission",
"*Entry " + (page+1) + " | Sent by " + search.getContributorsNames().get(page) +
" on " + search.getSubmissionDates().get(page) + "*",
false);
return embedBuilder.build();
}
public static String getTermNotFoundError()
{
return "\uD83D\uDE22 I couldn't find that term!";
}
public static void track(Message message, User user, UrbanSearch search, String sanitizedTerm)
{
Cache.getDatabaseSource().queueDisabling(message);
Cache.getDatabaseSource().trackRanCommandReply(message, user);
Cache.getDatabaseSource().trackUrban(search.getSerializedMeanings(),
search.getSerializedExamples(),
search.getSerializedContributors(),
search.getSerializedDates(),
message,
sanitizedTerm);
}
public static void changePage(ButtonInteractionEvent event, ChangeType changeType)
{
String messageId = event.getMessageId();
DatabaseSource database = Cache.getDatabaseSource();
// check if the user interacting is the same one who ran the command
if (!(database.isUserTrackedFor(event.getUser().getId(), messageId))) {
event.reply("❌ You did not run this command!").setEphemeral(true).queue();
return;
}
// get current page and calculate how many pages there are
int page = Cache.getDatabaseSource().getUrbanPage(messageId);
String term = database.getUrbanTerm(messageId);
String url = generateUrl(term);
// get serialized parameters
String serializedMeanings = database.getUrbanMeanings(messageId);
String serializedExamples = database.getUrbanExamples(messageId);
String serializedContributors = database.getUrbanContributors(messageId);
String serializedDates = database.getUrbanDates(messageId);
// construct object by passing serialized parameters
UrbanSearch search = new UrbanSearch(serializedMeanings,
serializedExamples, serializedContributors, serializedDates);
// move to new page
if(changeType == ChangeType.NEXT)
page++;
else if(changeType == ChangeType.PREVIOUS)
page--;
term = UrbanDictionary.sanitizeArgs(term, false);
// generate embed with new results
MessageEmbed updatedEmbed = UrbanDictionary.buildEmbed(term, url, event.getUser(), search, page);
// get all attached components and check which ones need to be enabled or disabled
List<ItemComponent> components = new ArrayList<>();
if(page > 0)
{
components.add(UrbanDictionary.getPreviousPageButton().asEnabled());
} else {
components.add(UrbanDictionary.getPreviousPageButton().asDisabled());
}
if(page + 1 == search.getPages())
{
components.add(UrbanDictionary.getNextPageButton().asDisabled());
} else {
components.add(UrbanDictionary.getNextPageButton().asEnabled());
}
// update the components on the object
components.add(UrbanDictionary.getDeleteButton());
ActionRow currentRow = ActionRow.of(components);
// update the message
event.editComponents(currentRow).setEmbeds(updatedEmbed).queue();
database.setUrbanPage(messageId, page);
database.resetExpiryTimestamp(messageId);
}
public static class UrbanSearch
{
final LinkedList<String> plaintextMeanings;
final LinkedList<String> plaintextExamples;
final LinkedList<String> contributorsNames;
final LinkedList<String> submissionDates;
final String serializedMeanings;
final String serializedExamples;
final String serializedContributors;
final String serializedDates;
final int pages;
public UrbanSearch(String serializedMeanings,
String serializedExamples,
String serializedContributors,
String serializedDates)
{
this.serializedMeanings = serializedMeanings;
this.serializedExamples = serializedExamples;
this.serializedContributors = serializedContributors;
this.serializedDates = serializedDates;
this.plaintextMeanings = SerializationUtil.deserializeBase64(serializedMeanings);
this.plaintextExamples = SerializationUtil.deserializeBase64(serializedExamples);
this.contributorsNames = SerializationUtil.deserializeBase64(serializedContributors);
this.submissionDates = SerializationUtil.deserializeBase64(serializedDates);
this.pages = submissionDates.size();
}
public UrbanSearch(Elements definitions)
{
plaintextMeanings = new LinkedList<>();
plaintextExamples = new LinkedList<>();
contributorsNames = new LinkedList<>();
submissionDates = new LinkedList<>();
for(Element definition : definitions)
{
Elements meaningSingleton = definition.getElementsByClass("meaning");
if(meaningSingleton.isEmpty())
{
plaintextMeanings.add(" ");
} else
{
Element meaning = meaningSingleton.get(0);
String text = meaning.html()
.replaceAll("<br\\s*?>", "\n") // keep newlines
.replaceAll("<.*?>", ""); // remove all other html tags
// this is used to fix eg. &amp; being shown literally instead of being parsed
text = StringEscapeUtils.unescapeHtml4(text);
// discord only allows 1024 characters for embed fields
if(text.length() > 1024) text = text.substring(0, 1020) + "...";
plaintextMeanings.add(text);
}
Elements exampleSingleton = definition.getElementsByClass("example");
if(exampleSingleton.isEmpty())
{
plaintextExamples.add(" ");
} else
{
Element example = exampleSingleton.get(0);
String text = example.html()
.replaceAll("<br\\s*?>", "\n") // keep newlines
.replaceAll("<.*?>", ""); // remove all other html tags
// this is used to fix eg. &amp; being shown literally instead of being parsed
text = StringEscapeUtils.unescapeHtml4(text);
// discord only allows 1024 characters for embed fields
if(text.length() > 1024) text = text.substring(0, 1020) + "...";
plaintextExamples.add(text);
}
Elements contributorSingleton = definition.getElementsByClass("contributor");
if(contributorSingleton.isEmpty())
{
contributorsNames.add("Unknown");
} else
{
Element contributor = contributorSingleton.get(0);
String htmlContributor = contributor.html();
String htmlContributorName = contributor.select("a").html();
String htmlSubmitDate = htmlContributor.substring(
htmlContributor.indexOf("</a>") + 4);
contributorsNames.add(htmlContributorName
.replaceAll("<.*?>", "")); // remove all html tags;
submissionDates.add(htmlSubmitDate
.replaceAll("<.*?>", "")); // remove all html tags;
}
}
serializedMeanings = SerializationUtil.serializeBase64(plaintextMeanings);
serializedExamples = SerializationUtil.serializeBase64(plaintextExamples);
serializedContributors = SerializationUtil.serializeBase64(contributorsNames);
serializedDates = SerializationUtil.serializeBase64(submissionDates);
pages = submissionDates.size();
}
public List<String> getPlaintextMeanings() {
return this.plaintextMeanings;
}
public List<String> getPlaintextExamples() {
return this.plaintextExamples;
}
public List<String> getContributorsNames() {
return this.contributorsNames;
}
public List<String> getSubmissionDates() {
return this.submissionDates;
}
public String getSerializedMeanings() {
return serializedMeanings;
}
public String getSerializedExamples() {
return serializedExamples;
}
public String getSerializedContributors() {
return serializedContributors;
}
public String getSerializedDates() {
return serializedDates;
}
public int getPages() {
return pages;
}
}
public enum ChangeType
{
NEXT,
PREVIOUS;
}
}

View File

@@ -0,0 +1,78 @@
package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.Alias;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class AliasCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
return new LinkedList<>(Arrays.asList("alias", "aliases"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
return false;
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.TOOLS;
}
@NotNull
@Override
public String getDescription() {
return "See other command aliases.";
}
@Nullable
@Override
public String getUsage() {
return "<command>";
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
if(args.length == 0)
{
event.getMessage().reply("\uD83D\uDE20 Hey, you have to specify a command!").queue();
return;
}
String commandLabel = args[0].toLowerCase();
MessageCommand command = Cache.getMessageCommandListener().getRegisteredCommand(commandLabel);
if(command == null)
{
event.getMessage().reply("Unrecognized command: `" + commandLabel + "`!").queue(); // todo prettier
return;
}
String aliases = Alias.generateNiceAliases(command);
aliases = "Aliases for **" + command.getCommandLabels().get(0) + "**: " + aliases;
event.getMessage()
.reply(aliases)
.queue();
}
}

View File

@@ -0,0 +1,100 @@
package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Mentions;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.commands.base.Avatar;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class AvatarCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
return new LinkedList<>(Collections.singletonList("avatar"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
return false;
}
@NotNull
@Override
public String getDescription() {
return "Get someone's avatar, or your own. You can additionally specify a resolution.";
}
@Nullable
@Override
public String getUsage() {
return "[mentioned user] [resolution]";
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.TOOLS;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
int[] acceptedSizes = Cache.getSupportedAvatarResolutions();
User user;
int resolution = -1;
// we have no specific order for user and resolution, so let's try parsing any arg as resolution
// (mentions are handled differently by a specific method)
boolean resFound = false;
for (String arg : args) {
try {
int givenRes = Integer.parseInt(arg);
resolution = Avatar.parseResolution(givenRes);
resFound = true;
break;
} catch (NumberFormatException ignored) {
}
}
// fallback in case we didn't find any specified resolution
if(!resFound) resolution = Avatar.parseResolution(512);
// check if someone is mentioned
Mentions mentions = event.getMessage().getMentions();
if(mentions.getMentions().isEmpty())
{
user = event.getAuthor();
} else
{
String mentionedId = mentions.getMentions().get(0).getId();
user = HidekoBot.getAPI().retrieveUserById(mentionedId).complete();
}
// in case of issues, fallback to the sender
if(user == null) user = event.getAuthor();
// send a response
MessageEmbed embed = Avatar.buildEmbed(resolution, user);
event.getMessage().replyEmbeds(embed).queue();
}
}

View File

@@ -3,9 +3,11 @@ package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.MessageEmbed; import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.BotInfo; import wtf.beatrice.hidekobot.commands.base.BotInfo;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand; import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Collections; import java.util.Collections;
@@ -31,6 +33,24 @@ public class BotInfoCommand implements MessageCommand
return false; return false;
} }
@NotNull
@Override
public String getDescription() {
return "Get general info about the bot.";
}
@Nullable
@Override
public String getUsage() {
return null;
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.TOOLS;
}
@Override @Override
public void runCommand(MessageReceivedEvent event, String label, String[] args) { public void runCommand(MessageReceivedEvent event, String label, String[] args) {

View File

@@ -4,8 +4,11 @@ import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.ClearChat; import wtf.beatrice.hidekobot.commands.base.ClearChat;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand; import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Collections; import java.util.Collections;
@@ -28,6 +31,24 @@ public class ClearCommand implements MessageCommand
return false; return false;
} }
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.MODERATION;
}
@NotNull
@Override
public String getDescription() {
return "Clear the current channel's chat history.";
}
@Nullable
@Override
public String getUsage() {
return "[amount]";
}
@Override @Override
public void runCommand(MessageReceivedEvent event, String label, String[] args) public void runCommand(MessageReceivedEvent event, String label, String[] args)
{ {
@@ -43,7 +64,18 @@ public class ClearCommand implements MessageCommand
// get the amount from the command args. // get the amount from the command args.
Integer toDeleteAmount; Integer toDeleteAmount;
if (args.length == 0) toDeleteAmount = 1; if (args.length == 0) toDeleteAmount = 1;
else toDeleteAmount = Integer.parseInt(args[0]); else
{
try {
toDeleteAmount = Integer.parseInt(args[0]);
} catch (NumberFormatException e)
{
toDeleteAmount = 0;
}
}
// cap the amount to avoid abuse.
if(toDeleteAmount > ClearChat.getMaxAmount()) toDeleteAmount = 0;
error = ClearChat.checkDeleteAmount(toDeleteAmount); error = ClearChat.checkDeleteAmount(toDeleteAmount);
if (error != null) { if (error != null) {
@@ -64,14 +96,16 @@ public class ClearCommand implements MessageCommand
// edit the message text and attach a button. // edit the message text and attach a button.
Button dismiss = ClearChat.getDismissButton(); Button dismiss = ClearChat.getDismissButton();
// ^ todo: maybe the dismiss button should also delete the original message sent by the user? Message finalMessage = event.getChannel().sendMessage(content).setActionRow(dismiss).complete();
// todo: but then, we need to differentiate between command type in the database, and store
// todo: that message's id too.
botMessage = botMessage.editMessage(content).setActionRow(dismiss).complete();
// add the message to database. // add the message to database.
Cache.getDatabaseSource().queueDisabling(botMessage); Cache.getDatabaseSource().queueDisabling(finalMessage);
Cache.getDatabaseSource().trackRanCommandReply(botMessage, event.getAuthor()); Cache.getDatabaseSource().trackRanCommandReply(finalMessage, event.getAuthor());
// delete the sender's message.
event.getMessage().delete().queue();
// delete the "clearing" info message.
botMessage.delete().queue();
} }

View File

@@ -2,8 +2,10 @@ package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.commands.base.CoinFlip; import wtf.beatrice.hidekobot.commands.base.CoinFlip;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand; import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Arrays; import java.util.Arrays;
@@ -29,6 +31,24 @@ public class CoinFlipCommand implements MessageCommand
return false; return false;
} }
@NotNull
@Override
public String getDescription() {
return "Flip a coin.";
}
@Nullable
@Override
public String getUsage() {
return null;
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.FUN;
}
@Override @Override
public void runCommand(MessageReceivedEvent event, String label, String[] args) { public void runCommand(MessageReceivedEvent event, String label, String[] args) {

View File

@@ -0,0 +1,205 @@
package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.Dice;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.*;
public class DiceRollCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
return new LinkedList<>(Arrays.asList("diceroll", "droll", "roll"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
return false;
}
@NotNull
@Override
public String getDescription() {
return "Roll dice. You can roll multiple dice at the same time." +
"\nExamples:" +
"\n - `d8 10` to roll an 8-sided die 10 times." +
"\n - `d12 3 d5 10` to roll a 12-sided die 3 times, and then a 5-sided die 10 times." +
"\n - `30` to roll a standard 6-sided die 30 times." +
"\n - `d10` to roll a 10-sided die once.";
}
@Nullable
@Override
public String getUsage() {
return "[dice size] [rolls]";
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.FUN;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
LinkedHashMap<Dice, Integer> dicesToRoll = new LinkedHashMap<>();
String diceRegex = "d[0-9]+";
String amountRegex = "[0-9]+";
Dice currentDice = null;
int currentAmount;
UUID lastPushedDice = null;
int totalRolls = 0;
for(String arg : args)
{
if(totalRolls > 200)
{
event.getMessage().reply("Too many total rolls!").queue();
return;
}
if(arg.matches(amountRegex))
{
currentAmount = Integer.parseInt(arg);
if(currentDice == null)
{
currentDice = new Dice(6);
} else {
currentDice = new Dice(currentDice);
}
if(currentAmount > 100)
{
event.getMessage().reply("Too many rolls (`" + currentAmount + "`)!").queue();
return;
}
lastPushedDice = currentDice.getUUID();
dicesToRoll.put(currentDice, currentAmount);
totalRolls += currentAmount;
}
else if(arg.matches(diceRegex))
{
int sides = Integer.parseInt(arg.substring(1));
if(sides > 10000)
{
event.getMessage().reply("Too many sides (`" + sides + "`)!").queue();
return;
}
if(args.length == 1)
{
dicesToRoll.put(new Dice(sides), 1);
totalRolls++;
} else
{
if(currentDice != null)
{
if(lastPushedDice == null || !lastPushedDice.equals(currentDice.getUUID()))
{
dicesToRoll.put(currentDice, 1);
lastPushedDice = currentDice.getUUID();
totalRolls++;
}
}
currentDice = new Dice(sides);
}
}
}
if(lastPushedDice == null)
{
if(currentDice != null)
{
dicesToRoll.put(currentDice, 1);
totalRolls++;
}
} else
{
if(!lastPushedDice.equals(currentDice.getUUID()))
{
dicesToRoll.put(new Dice(currentDice), 1);
totalRolls++;
}
}
LinkedList<Dice> rolledDices = new LinkedList<>();
// in case no dice was specified (or invalid), roll a standard 6-sided dice.
if(dicesToRoll.isEmpty())
{
Dice standardDice = new Dice(6);
dicesToRoll.put(standardDice, 1);
totalRolls = 1;
}
for(Dice dice : dicesToRoll.keySet())
{
for(int roll = 0; roll < dicesToRoll.get(dice); roll++)
{
dice.roll();
rolledDices.add(new Dice(dice));
}
}
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setAuthor(event.getAuthor().getAsTag(), null, event.getAuthor().getAvatarUrl());
embedBuilder.setTitle("Dice Roll");
StringBuilder message = new StringBuilder();
int total = 0;
int previousDiceSides = 0;
for (Dice dice : rolledDices) {
int diceSize = dice.getSides();
if (previousDiceSides != diceSize) {
message.append("\nd").append(diceSize).append(": ");
previousDiceSides = diceSize;
} else if (previousDiceSides != 0) {
message.append(", ");
}
message.append("`").append(dice.getValue()).append("`");
total += dice.getValue();
}
// discord doesn't allow embed fields to be longer than 1024 and errors out
if(message.length() > 1024)
{
event.getMessage().reply("Too many rolls!").queue();
return;
}
embedBuilder.addField("\uD83C\uDFB2 Rolls", message.toString(), false);
String rolls = totalRolls == 1 ? "roll" : "rolls";
embedBuilder.addField("✨ Total", totalRolls + " " + rolls + ": " + total, false);
event.getMessage().replyEmbeds(embedBuilder.build()).queue();
}
}

View File

@@ -2,6 +2,9 @@ package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand; import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Arrays; import java.util.Arrays;
@@ -24,6 +27,24 @@ public class HelloCommand implements MessageCommand
return false; return false;
} }
@NotNull
@Override
public String getDescription() {
return "Get pinged by the bot.";
}
@Nullable
@Override
public String getUsage() {
return null;
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.FUN;
}
@Override @Override
public void runCommand(MessageReceivedEvent event, String label, String[] args) public void runCommand(MessageReceivedEvent event, String label, String[] args)
{ {

View File

@@ -0,0 +1,158 @@
package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.apache.commons.text.WordUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.commands.base.Alias;
import wtf.beatrice.hidekobot.commands.base.Say;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
public class HelpCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
return new LinkedList<>(Collections.singletonList("help"));
}
@Nullable
@Override
public List<Permission> getPermissions() { return null; }
@Override
public boolean passRawArgs() {
return false;
}
@NotNull
@Override
public String getDescription() {
return "Get general help on the bot. Specify a command if you want specific help about that command.";
}
@Nullable
@Override
public String getUsage() {
return "[command]";
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.TOOLS;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
LinkedHashMap<CommandCategory, LinkedList<MessageCommand>> commandCategories = new LinkedHashMap<>();
if(args.length == 0)
{
for(CommandCategory category : CommandCategory.values())
{
LinkedList<MessageCommand> commandsOfThisCategory = new LinkedList<>();
for (MessageCommand command : Cache.getMessageCommandListener().getRegisteredCommands())
{
if(command.getCategory().equals(category))
{
commandsOfThisCategory.add(command);
}
}
commandCategories.put(category, commandsOfThisCategory);
}
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle("Bot Help");
embedBuilder.addField("General Help",
"Type `" + Cache.getBotPrefix() + " help [command]` to get help on a specific command." +
"\nYou will find a list of commands organized in categories below.",
false);
for(CommandCategory category : commandCategories.keySet())
{
StringBuilder commandsList = new StringBuilder();
LinkedList<MessageCommand> commandsOfThisCategory = commandCategories.get(category);
for(int pos = 0; pos < commandsOfThisCategory.size(); pos++)
{
MessageCommand command = commandsOfThisCategory.get(pos);
commandsList.append("`").append(command.getCommandLabels().get(0)).append("`");
if(pos + 1 != commandsOfThisCategory.size())
commandsList.append(", "); // separate with comma except on last run
}
String niceCategoryName = category.name().replace("_", " ");
niceCategoryName = WordUtils.capitalizeFully(niceCategoryName);
niceCategoryName = category.getEmoji() + " " + niceCategoryName;
embedBuilder.addField(niceCategoryName, commandsList.toString(), false);
}
event.getMessage().replyEmbeds(embedBuilder.build()).queue();
} else {
String commandLabel = args[0].toLowerCase();
MessageCommand command = Cache.getMessageCommandListener().getRegisteredCommand(commandLabel);
if(command == null)
{
event.getMessage().reply("Unrecognized command: `" + commandLabel + "`!").queue(); // todo prettier
return;
}
commandLabel = command.getCommandLabels().get(0);
String usage = "`" + Cache.getBotPrefix() + " " + commandLabel;
String internalUsage = command.getUsage();
if(internalUsage != null) usage += " " + internalUsage;
usage += "`";
String aliases = Alias.generateNiceAliases(command);
List<Permission> permissions = command.getPermissions();
StringBuilder permissionsStringBuilder = new StringBuilder();
if(permissions == null)
{
permissionsStringBuilder = new StringBuilder("Available to everyone");
} else {
for(int i = 0; i < permissions.size(); i++)
{
Permission permission = permissions.get(i);
permissionsStringBuilder.append("**").append(permission.getName()).append("**");
if(i + 1 != permissions.size())
permissionsStringBuilder.append(", "); // separate with comma expect on last iteration
}
}
String title = command.getCategory().getEmoji() +
" \"" + WordUtils.capitalizeFully(commandLabel + "\" help");
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle(title);
embedBuilder.addField("Description", command.getDescription(), false);
embedBuilder.addField("Usage", usage, false);
embedBuilder.addField("Aliases", aliases, false);
embedBuilder.addField("Permissions", permissionsStringBuilder.toString(), false);
event.getMessage().replyEmbeds(embedBuilder.build()).queue();
}
}
}

View File

@@ -5,8 +5,10 @@ import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button; import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.commands.base.Invite; import wtf.beatrice.hidekobot.commands.base.Invite;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand; import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Collections; import java.util.Collections;
@@ -32,6 +34,24 @@ public class InviteCommand implements MessageCommand
return false; return false;
} }
@NotNull
@Override
public String getDescription() {
return "Get the bot's invite link.";
}
@Nullable
@Override
public String getUsage() {
return null;
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.MODERATION;
}
@Override @Override
public void runCommand(MessageReceivedEvent event, String label, String[] args) public void runCommand(MessageReceivedEvent event, String label, String[] args)
{ {

View File

@@ -0,0 +1,113 @@
package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.IMentionable;
import net.dv8tion.jda.api.entities.Mentions;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import wtf.beatrice.hidekobot.util.RandomUtil;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class LoveCalculatorCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels()
{
return new LinkedList<>(Arrays.asList("lovecalc", "lovecalculator", "lc"));
}
@Nullable
@Override
public List<Permission> getPermissions() {
return null; //anyone can use it
}
@Override
public boolean passRawArgs() {
return false;
}
@NotNull
@Override
public String getDescription() {
return "Calculate how much two people love each other. You can mention two people or just one.";
}
@Nullable
@Override
public String getUsage() {
return "<person 1> [person 2]";
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.FUN;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
Mentions mentionsObj = event.getMessage().getMentions();
List<IMentionable> mentions = mentionsObj.getMentions();
if(args.length == 0 || mentions.isEmpty())
{
event.getMessage()
.reply("\uD83D\uDE22 I need to know who to check! Please mention them.")
.queue();
return;
}
User user1, user2;
String mentionedUserId = mentions.get(0).getId();
user1 = HidekoBot.getAPI().retrieveUserById(mentionedUserId).complete();
if(mentions.size() == 1)
{
user2 = event.getAuthor();
} else {
mentionedUserId = mentions.get(1).getId();
user2 = HidekoBot.getAPI().retrieveUserById(mentionedUserId).complete();
}
int loveAmount = RandomUtil.getRandomNumber(0, 100);
String formattedAmount = loveAmount + "%";
if(loveAmount <= 30) formattedAmount += "... \uD83D\uDE22";
else if(loveAmount < 60) formattedAmount += "! \uD83E\uDDD0";
else if(loveAmount < 75) formattedAmount += "!!! \uD83E\uDD73";
else formattedAmount = "" + formattedAmount + "!!! \uD83D\uDE0D\uD83D\uDCA5";
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setAuthor(event.getAuthor().getAsTag(), null, event.getAuthor().getAvatarUrl());
embedBuilder.setTitle("Love Calculator");
embedBuilder.addField("\uD83D\uDC65 People",
user1.getAsMention() + " & " + user2.getAsMention(),
false);
embedBuilder.addField("❤️\u200D\uD83D\uDD25 Match",
formattedAmount,
false);
event.getChannel().sendMessageEmbeds(embedBuilder.build()).queue();
}
}

View File

@@ -0,0 +1,74 @@
package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.commands.base.MagicBall;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.LinkedList;
import java.util.List;
public class MagicBallCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
return MagicBall.getLabels();
}
@Nullable
@Override
public List<Permission> getPermissions() {
return null; // anyone can use it
}
@Override
public boolean passRawArgs() {
return false;
}
@NotNull
@Override
public String getDescription() {
return "Ask a question to the Magic Ball.";
}
@Nullable
@Override
public String getUsage() {
return "<question>";
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.FUN;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
if(args.length == 0)
{
event.getMessage().reply("You need to specify a question!").queue();
return;
}
StringBuilder questionBuilder = new StringBuilder();
for(int i = 0; i < args.length; i++)
{
String arg = args[i];
questionBuilder.append(arg);
if(i + 1 != args.length) // don't add a separator on the last iteration
questionBuilder.append(" ");
}
String question = questionBuilder.toString();
event.getChannel().sendMessageEmbeds(MagicBall.generateEmbed(question, event.getAuthor())).queue();
}
}

View File

@@ -0,0 +1,69 @@
package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import wtf.beatrice.hidekobot.commands.base.Say;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class SayCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
return new LinkedList<>(Collections.singletonList("say"));
}
@Nullable
@Override
public List<Permission> getPermissions() { return Collections.singletonList(Say.getPermission()); }
@Override
public boolean passRawArgs() {
return true;
}
@NotNull
@Override
public String getDescription() {
return "Make the bot say something for you.";
}
@Nullable
@Override
public String getUsage() {
return "<text>";
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.TOOLS;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
String messageContent;
if(args.length != 0 && !args[0].isEmpty())
{
messageContent = args[0];
} else {
event.getMessage().reply("\uD83D\uDE20 Hey, you have to tell me what to say!")
.queue();
return;
}
event.getChannel().sendMessage(messageContent).queue();
event.getMessage().delete().queue();
}
}

View File

@@ -0,0 +1,106 @@
package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import wtf.beatrice.hidekobot.commands.base.UrbanDictionary;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class UrbanDictionaryCommand implements MessageCommand
{
@Override
public LinkedList<String> getCommandLabels() {
return UrbanDictionary.getCommandLabels();
}
@Nullable
@Override
public List<Permission> getPermissions() {
return null; //anyone can use it
}
@Override
public boolean passRawArgs() {
return false;
}
@NotNull
@Override
public String getDescription() {
return "Look something up in the Urban Dictionary.";
}
@Nullable
@Override
public String getUsage() {
return "<query>";
}
@NotNull
@Override
public CommandCategory getCategory() {
return CommandCategory.FUN;
}
@Override
public void runCommand(MessageReceivedEvent event, String label, String[] args)
{
if(args.length == 0)
{
event.getMessage().reply(UrbanDictionary.getNoArgsError()).queue();
return;
}
// sanitize args by only keeping letters and numbers, and adding "+" instead of spaces for HTML parsing
StringBuilder termBuilder = new StringBuilder();
for (int i = 0; i < args.length; i++) {
String arg = args[i];
termBuilder.append(arg);
if(i + 1 != args.length) // add spaces between args, but not on the last run
termBuilder.append(" ");
}
String term = UrbanDictionary.sanitizeArgs(termBuilder.toString(), false);
String url = UrbanDictionary.generateUrl(term);
Document doc;
try {
doc = Jsoup.connect(url).get();
} catch (IOException e) {
event.getMessage().reply(UrbanDictionary.getTermNotFoundError()).queue();
return;
}
Elements definitions = doc.getElementsByClass("definition");
UrbanDictionary.UrbanSearch search = new UrbanDictionary.UrbanSearch(definitions);
MessageEmbed embed = UrbanDictionary.buildEmbed(term, url, event.getAuthor(), search, 0);
// disable next page if we only have one result
Button nextPageBtnLocal = UrbanDictionary.getNextPageButton();
if(search.getPages() == 1) nextPageBtnLocal = nextPageBtnLocal.asDisabled();
event.getChannel()
.sendMessageEmbeds(embed)
.addActionRow(UrbanDictionary.getPreviousPageButton().asDisabled(),
//disabled by default because we're on page 0
nextPageBtnLocal,
UrbanDictionary.getDeleteButton())
.queue(message -> UrbanDictionary.track(message, event.getAuthor(), search, term));
}
}

View File

@@ -1,6 +1,6 @@
package wtf.beatrice.hidekobot.commands.slash; package wtf.beatrice.hidekobot.commands.slash;
import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionMapping; import net.dv8tion.jda.api.interactions.commands.OptionMapping;
@@ -8,7 +8,7 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.commands.base.Avatar;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl; import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class AvatarCommand extends SlashCommandImpl public class AvatarCommand extends SlashCommandImpl
@@ -31,9 +31,6 @@ public class AvatarCommand extends SlashCommandImpl
User user; User user;
int resolution; int resolution;
int[] acceptedSizes = Cache.getSupportedAvatarResolutions();
OptionMapping userArg = event.getOption("user"); OptionMapping userArg = event.getOption("user");
if(userArg != null) if(userArg != null)
{ {
@@ -45,57 +42,12 @@ public class AvatarCommand extends SlashCommandImpl
OptionMapping sizeArg = event.getOption("size"); OptionMapping sizeArg = event.getOption("size");
if(sizeArg != null) if(sizeArg != null)
{ {
resolution = sizeArg.getAsInt(); resolution = Avatar.parseResolution(sizeArg.getAsInt());
// method to find closest value to accepted values
int distance = Math.abs(acceptedSizes[0] - resolution);
int idx = 0;
for(int c = 1; c < acceptedSizes.length; c++){
int cdistance = Math.abs(acceptedSizes[c] - resolution);
if(cdistance < distance){
idx = c;
distance = cdistance;
}
}
resolution = acceptedSizes[idx];
} else { } else {
resolution = 512; resolution = Avatar.parseResolution(512);
} }
EmbedBuilder embedBuilder = new EmbedBuilder(); MessageEmbed embed = Avatar.buildEmbed(resolution, user);
event.getHook().editOriginalEmbeds(embed).queue();
// embed processing
{
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle("Profile picture");
embedBuilder.addField("User", "<@" + user.getId() + ">", false);
embedBuilder.addField("Current resolution", resolution + " × " + resolution, false);
// string builder to create a string that links to all available resolutions
StringBuilder links = new StringBuilder();
for(int pos = 0; pos < acceptedSizes.length; pos++)
{
int currSize = acceptedSizes[pos];
String currLink = user.getEffectiveAvatar().getUrl(currSize);
links.append("[").append(currSize).append("px](").append(currLink).append(")");
if(pos + 1 != acceptedSizes.length) // don't add a separator on the last iteration
{
links.append(" | ");
}
}
embedBuilder.addField("Available resolutions", links.toString(), false);
embedBuilder.setImage(user.getEffectiveAvatar().getUrl(resolution));
}
event.getHook().editOriginalEmbeds(embedBuilder.build()).queue();
} }
} }

View File

@@ -44,6 +44,9 @@ public class ClearCommand extends SlashCommandImpl
OptionMapping amountOption = event.getOption("amount"); OptionMapping amountOption = event.getOption("amount");
int toDeleteAmount = amountOption == null ? 1 : amountOption.getAsInt(); int toDeleteAmount = amountOption == null ? 1 : amountOption.getAsInt();
// cap the amount to avoid abuse.
if(toDeleteAmount > ClearChat.getMaxAmount()) toDeleteAmount = 0;
error = ClearChat.checkDeleteAmount(toDeleteAmount); error = ClearChat.checkDeleteAmount(toDeleteAmount);
if(error != null) if(error != null)
{ {

View File

@@ -0,0 +1,49 @@
package wtf.beatrice.hidekobot.commands.slash;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull;
import wtf.beatrice.hidekobot.commands.base.MagicBall;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class MagicBallCommand extends SlashCommandImpl
{
@Override
public CommandData getSlashCommandData()
{
return Commands.slash(MagicBall.getLabels().get(0),
"Ask a question to the magic ball.")
.addOption(OptionType.STRING, "question",
"The question to ask.",
true,
false);
}
@Override
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event)
{
// get the asked question
OptionMapping textOption = event.getOption("question");
String question = "";
if(textOption != null)
{
question = textOption.getAsString();
}
if(textOption == null || question.isEmpty())
{
event.reply("\uD83D\uDE20 Hey, you have to ask me a question!")
.setEphemeral(true)
.queue();
return;
}
MessageEmbed response = MagicBall.generateEmbed(question, event.getUser());
event.replyEmbeds(response).queue();
}
}

View File

@@ -1,6 +1,5 @@
package wtf.beatrice.hidekobot.commands.slash; package wtf.beatrice.hidekobot.commands.slash;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions; import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
@@ -9,6 +8,7 @@ import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands; import net.dv8tion.jda.api.interactions.commands.build.Commands;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import wtf.beatrice.hidekobot.commands.base.Say;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl; import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
public class SayCommand extends SlashCommandImpl public class SayCommand extends SlashCommandImpl
@@ -22,7 +22,7 @@ public class SayCommand extends SlashCommandImpl
"The message to send.", "The message to send.",
true, true,
false) false)
.setDefaultPermissions(DefaultMemberPermissions.enabledFor(Permission.MESSAGE_MANAGE)); .setDefaultPermissions(DefaultMemberPermissions.enabledFor(Say.getPermission()));
} }
@Override @Override

View File

@@ -0,0 +1,81 @@
package wtf.beatrice.hidekobot.commands.slash;
import net.dv8tion.jda.api.entities.MessageEmbed;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
import net.dv8tion.jda.api.interactions.commands.OptionType;
import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.commands.build.Commands;
import net.dv8tion.jda.api.interactions.components.ActionRow;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.jetbrains.annotations.NotNull;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.select.Elements;
import wtf.beatrice.hidekobot.commands.base.UrbanDictionary;
import wtf.beatrice.hidekobot.objects.commands.SlashCommandImpl;
import java.io.IOException;
public class UrbanDictionaryCommand extends SlashCommandImpl
{
@Override
public CommandData getSlashCommandData()
{
return Commands.slash(UrbanDictionary.getCommandLabels().get(0),
"Look up a term on Urban Dictionary.")
.addOption(OptionType.STRING, "term", "The term to look up", true);
}
@Override
public void runSlashCommand(@NotNull SlashCommandInteractionEvent event)
{
event.deferReply().queue();
// get the term to look up
OptionMapping textOption = event.getOption("term");
String term = "";
if(textOption != null)
{
term = textOption.getAsString();
}
if(textOption == null || term.isEmpty())
{
event.reply(UrbanDictionary.getNoArgsError())
.setEphemeral(true)
.queue();
return;
}
final String sanitizedTerm = UrbanDictionary.sanitizeArgs(term, false);
String url = UrbanDictionary.generateUrl(sanitizedTerm);
Document doc;
try {
doc = Jsoup.connect(url).get();
} catch (IOException e) {
event.reply(UrbanDictionary.getTermNotFoundError())
.setEphemeral(true)
.queue();
return;
}
Elements definitions = doc.getElementsByClass("definition");
UrbanDictionary.UrbanSearch search = new UrbanDictionary.UrbanSearch(definitions);
MessageEmbed embed = UrbanDictionary.buildEmbed(sanitizedTerm, url, event.getUser(), search, 0);
// disable next page if we only have one result
Button nextPageBtnLocal = UrbanDictionary.getNextPageButton();
if(search.getPages() == 1) nextPageBtnLocal = nextPageBtnLocal.asDisabled();
ActionRow actionRow = ActionRow.of(UrbanDictionary.getPreviousPageButton().asDisabled(),
//disabled by default because we're on page 0
nextPageBtnLocal,
UrbanDictionary.getDeleteButton());
event.getHook().editOriginalEmbeds(embed).setComponents(actionRow).queue(message ->
UrbanDictionary.track(message, event.getUser(), search, sanitizedTerm));
}
}

View File

@@ -81,6 +81,12 @@ public class DatabaseSource
* | 39402849302 | 39402849302 | 39402849302 | 39402849302 | PRIVATE | * | 39402849302 | 39402849302 | 39402849302 | 39402849302 | PRIVATE |
* -------------------------------------------------------------------------------------------- * --------------------------------------------------------------------------------------------
* *
* TABLE 3: urban_dictionary
* -----------------------------------------------------------------------------------------------------
* | message_id | page | meanings | examples | contributors | dates | term |
* -----------------------------------------------------------------------------------------------------
* | 39402849302 | 0 | base64 | base64 | base64 | base64 | miku |
* -----------------------------------------------------------------------------------------------------
*/ */
//todo: javadocs //todo: javadocs
@@ -104,6 +110,16 @@ public class DatabaseSource
"channel_type TEXT NOT NULL" + // channel type (PRIVATE, FORUM, ...) "channel_type TEXT NOT NULL" + // channel type (PRIVATE, FORUM, ...)
");"); ");");
newTables.add("CREATE TABLE IF NOT EXISTS urban_dictionary (" +
"message_id TEXT NOT NULL, " + // message id of the bot's response
"page INTEGER NOT NULL," + // page that we are currently on
"meanings TEXT NOT NULL," + // list of all meanings, serialized and encoded
"examples TEXT NOT NULL, " + // list of all examples, serialized and encoded
"contributors TEXT NOT NULL, " + // list of all contributors, serialized and encoded
"dates TEXT NOT NULL, " + // list of all submission dates, serialized and encoded
"term TEXT NOT NULL" + // the term that was searched
");");
for(String sql : newTables) for(String sql : newTables)
{ {
try (Statement stmt = dbConnection.createStatement()) { try (Statement stmt = dbConnection.createStatement()) {
@@ -301,6 +317,17 @@ public class DatabaseSource
return false; return false;
} }
query = "DELETE FROM urban_dictionary WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, messageId);
preparedStatement.execute();
} catch (SQLException e)
{
e.printStackTrace();
return false;
}
return true; return true;
} }
@@ -373,5 +400,220 @@ public class DatabaseSource
return null; return null;
} }
public boolean trackUrban(String meanings, String examples,
String contributors, String dates,
Message message, String term)
{
String query = "INSERT INTO urban_dictionary " +
"(message_id, page, meanings, examples, contributors, dates, term) VALUES " +
" (?, ?, ?, ?, ?, ?, ?);";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, message.getId());
preparedStatement.setInt(2, 0);
preparedStatement.setString(3, meanings);
preparedStatement.setString(4, examples);
preparedStatement.setString(5, contributors);
preparedStatement.setString(6, dates);
preparedStatement.setString(7, term);
preparedStatement.executeUpdate();
return true;
} catch (SQLException e)
{
e.printStackTrace();
}
return false;
}
public int getUrbanPage(String messageId)
{
String query = "SELECT page " +
"FROM urban_dictionary " +
"WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.isClosed()) return 0;
while(resultSet.next())
{
return resultSet.getInt("page");
}
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
public String getUrbanMeanings(String messageId)
{
String query = "SELECT meanings " +
"FROM urban_dictionary " +
"WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.isClosed()) return null;
while(resultSet.next())
{
return resultSet.getString("meanings");
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public String getUrbanExamples(String messageId)
{
String query = "SELECT examples " +
"FROM urban_dictionary " +
"WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.isClosed()) return null;
while(resultSet.next())
{
return resultSet.getString("examples");
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public String getUrbanContributors(String messageId)
{
String query = "SELECT contributors " +
"FROM urban_dictionary " +
"WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.isClosed()) return null;
while(resultSet.next())
{
return resultSet.getString("contributors");
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public String getUrbanDates(String messageId)
{
String query = "SELECT dates " +
"FROM urban_dictionary " +
"WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.isClosed()) return null;
while(resultSet.next())
{
return resultSet.getString("dates");
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public String getUrbanTerm(String messageId)
{
String query = "SELECT term " +
"FROM urban_dictionary " +
"WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, messageId);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.isClosed()) return null;
while(resultSet.next())
{
return resultSet.getString("term");
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public boolean setUrbanPage(String messageId, int page)
{
String query = "UPDATE urban_dictionary " +
"SET page = ? " +
"WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setInt(1, page);
preparedStatement.setString(2, messageId);
preparedStatement.executeUpdate();
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public boolean resetExpiryTimestamp(String messageId)
{
LocalDateTime expiryTime = LocalDateTime.now().plusSeconds(Cache.getExpiryTimeSeconds());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(Cache.getExpiryTimestampFormat());
String expiryTimeFormatted = dateTimeFormatter.format(expiryTime);
String query = "UPDATE pending_disabled_messages " +
"SET expiry_timestamp = ? " +
"WHERE message_id = ?;";
try(PreparedStatement preparedStatement = dbConnection.prepareStatement(query))
{
preparedStatement.setString(1, expiryTimeFormatted);
preparedStatement.setString(2, messageId);
preparedStatement.executeUpdate();
return true;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
} }

View File

@@ -2,8 +2,9 @@ package wtf.beatrice.hidekobot.listeners;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.hooks.ListenerAdapter;
import wtf.beatrice.hidekobot.commands.base.ClearChat;
import wtf.beatrice.hidekobot.commands.base.CoinFlip; import wtf.beatrice.hidekobot.commands.base.CoinFlip;
import wtf.beatrice.hidekobot.commands.base.UrbanDictionary;
import wtf.beatrice.hidekobot.util.CommandUtil;
public class ButtonInteractionListener extends ListenerAdapter public class ButtonInteractionListener extends ListenerAdapter
{ {
@@ -17,8 +18,12 @@ public class ButtonInteractionListener extends ListenerAdapter
// coinflip // coinflip
case "coinflip_reflip" -> CoinFlip.buttonReFlip(event); case "coinflip_reflip" -> CoinFlip.buttonReFlip(event);
// clearchat command // generic dismiss button
case "clear_dismiss" -> ClearChat.dismissMessage(event); case "generic_dismiss" -> CommandUtil.delete(event);
// urban dictionary navigation
case "urban_nextpage" -> UrbanDictionary.changePage(event, UrbanDictionary.ChangeType.NEXT);
case "urban_previouspage" -> UrbanDictionary.changePage(event, UrbanDictionary.ChangeType.PREVIOUS);
} }

View File

@@ -7,14 +7,13 @@ import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter; import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand; import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import wtf.beatrice.hidekobot.objects.comparators.MessageCommandAliasesComparator; import wtf.beatrice.hidekobot.objects.comparators.MessageCommandAliasesComparator;
import wtf.beatrice.hidekobot.util.Logger; import wtf.beatrice.hidekobot.util.Logger;
import java.util.Arrays; import java.util.*;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
public class MessageCommandListener extends ListenerAdapter public class MessageCommandListener extends ListenerAdapter
{ {
@@ -23,6 +22,10 @@ public class MessageCommandListener extends ListenerAdapter
private final TreeMap<LinkedList<String>, MessageCommand> registeredCommands = private final TreeMap<LinkedList<String>, MessageCommand> registeredCommands =
new TreeMap<LinkedList<String>, MessageCommand>(new MessageCommandAliasesComparator()); new TreeMap<LinkedList<String>, MessageCommand>(new MessageCommandAliasesComparator());
// map commands and their categories.
// this is not strictly needed but it's better to have it so we avoid looping every time we need to check the cat.
LinkedHashMap<CommandCategory, LinkedList<MessageCommand>> commandCategories = new LinkedHashMap<>();
private final String commandRegex = "(?i)^(hideko|hde)\\b"; private final String commandRegex = "(?i)^(hideko|hde)\\b";
// (?i) -> case insensitive flag // (?i) -> case insensitive flag
// ^ -> start of string (not in middle of a sentence) // ^ -> start of string (not in middle of a sentence)
@@ -57,10 +60,14 @@ public class MessageCommandListener extends ListenerAdapter
@Override @Override
public void onMessageReceived(@NotNull MessageReceivedEvent event) public void onMessageReceived(@NotNull MessageReceivedEvent event)
{ {
String eventMessage = event.getMessage().getContentDisplay(); // check if a bot is sending this message, and ignore it
if(event.getAuthor().isBot()) return;
// warning: we are getting the RAW value of the message content, not the DISPLAY value!
String eventMessage = event.getMessage().getContentRaw();
// check if the sent message matches the bot activation regex (prefix, name, ...) // check if the sent message matches the bot activation regex (prefix, name, ...)
if(!eventMessage.toLowerCase().matches(commandRegex + ".*")) if(!eventMessage.toLowerCase().matches(commandRegex + "((.|\\n)*)"))
return; return;
// generate args from the string // generate args from the string
@@ -74,7 +81,9 @@ public class MessageCommandListener extends ListenerAdapter
// it will be the whole text as a single element. // it will be the whole text as a single element.
if(argsString.isEmpty()) if(argsString.isEmpty())
{ {
event.getMessage().reply("Hello there! ✨").queue(); event.getMessage()
.reply("Hello there! ✨ Type `" + Cache.getBotPrefix() + " help` to get started!")
.queue();
return; return;
} }
@@ -87,7 +96,11 @@ public class MessageCommandListener extends ListenerAdapter
if(commandObject == null) if(commandObject == null)
{ {
/* temporarily disabled because when people talk about the bot, it replies with this spammy message.
event.getMessage().reply("Unrecognized command: `" + commandLabel + "`!").queue(); // todo prettier event.getMessage().reply("Unrecognized command: `" + commandLabel + "`!").queue(); // todo prettier
*/
return; return;
} }
@@ -120,8 +133,9 @@ public class MessageCommandListener extends ListenerAdapter
String[] commandArgs; String[] commandArgs;
if(commandObject.passRawArgs()) if(commandObject.passRawArgs())
{ {
// remove first argument, which is the command label // remove first argument, which is the command label
argsString = argsString.replaceAll("^[\\S]+\\s+", ""); argsString = argsString.replaceAll("^[\\S]+\\s*", "");
// pass all other arguments as a single argument as the first array element // pass all other arguments as a single argument as the first array element
commandArgs = new String[]{argsString}; commandArgs = new String[]{argsString};
} }

View File

@@ -0,0 +1,45 @@
package wtf.beatrice.hidekobot.objects;
import wtf.beatrice.hidekobot.util.RandomUtil;
import java.util.UUID;
public class Dice
{
private final int sides;
private int value = 0;
private final UUID uuid;
public Dice(int sides)
{
this.sides = sides;
this.uuid = UUID.randomUUID();
}
public Dice(Dice old)
{
this.sides = old.sides;
this.value = old.value;
this.uuid = UUID.randomUUID();
}
public int getValue()
{
return value;
}
public int getSides()
{
return sides;
}
public void roll()
{
value = RandomUtil.getRandomNumber(1, sides);
}
public UUID getUUID()
{
return uuid;
}
}

View File

@@ -0,0 +1,18 @@
package wtf.beatrice.hidekobot.objects.commands;
public enum CommandCategory
{
MODERATION("\uD83D\uDC40"),
FUN("\uD83C\uDFB2"),
TOOLS("\uD83D\uDEE0"),
;
private String emoji;
CommandCategory(String emoji)
{
this.emoji = emoji;
}
public String getEmoji() { return emoji; }
}

View File

@@ -2,6 +2,7 @@ package wtf.beatrice.hidekobot.objects.commands;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.LinkedList; import java.util.LinkedList;
@@ -39,6 +40,31 @@ public interface MessageCommand
*/ */
boolean passRawArgs(); boolean passRawArgs();
/**
* Say what category this command belongs to.
*
* @return the command category.
*/
@NotNull
CommandCategory getCategory();
/**
* Say what this command does.
*
* @return a String explaining what this command does.
*/
@NotNull
String getDescription();
/**
* Say how people should use this command.
*
* @return a String explaining how to use the command, excluding the bot prefix and command name. Null if no parameter is needed
*/
@Nullable
String getUsage();
/** /**
* Run the command logic by parsing the event and replying accordingly. * Run the command logic by parsing the event and replying accordingly.
* *

View File

@@ -0,0 +1,30 @@
package wtf.beatrice.hidekobot.runnables;
import net.dv8tion.jda.api.entities.Activity;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.util.RandomUtil;
import java.util.Arrays;
import java.util.List;
public class StatusUpdateTask implements Runnable
{
List<String> statuses = Arrays.asList(
"Hatsune Miku: Project DIVA",
"Wii Sports",
"Excel",
"Mii Channel",
"Wii Speak",
"MineTest",
"Mario Kart Wii"
);
@Override
public void run() {
int randomPos = RandomUtil.getRandomNumber(0, statuses.size() - 1);
String status = statuses.get(randomPos) + " | " + Cache.getBotPrefix() + " help";
HidekoBot.getAPI().getPresence().setActivity(Activity.playing(status));
}
}

View File

@@ -1,21 +1,48 @@
package wtf.beatrice.hidekobot.util; package wtf.beatrice.hidekobot.util;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot; import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.listeners.MessageCommandListener;
import wtf.beatrice.hidekobot.objects.commands.SlashCommand; import wtf.beatrice.hidekobot.objects.commands.SlashCommand;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class SlashCommandUtil public class CommandUtil
{ {
private static final Logger logger = new Logger(MessageCommandListener.class); private static final Logger logger = new Logger(CommandUtil.class);
/**
* Function to delete a message when a user clicks the "delete" button attached to that message.
* This will check in the database if that user ran the command originally.
*
* @param event the button interaction event.
*/
public static void delete(ButtonInteractionEvent event)
{
// check if the user interacting is the same one who ran the command
if (!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) {
event.reply("❌ You did not run this command!").setEphemeral(true).queue();
return;
}
// delete the message
event.getInteraction().getMessage().delete().queue();
// no need to manually untrack it from database, it will be purged on the next planned check.
}
/**
* Method to update slash commands registered on Discord's side.
* It runs automatically every time the bot starts, but only updates the commands in case differences
* are found, unless forced.
*
* @param force a boolean specifying if the update should be forced even if no differences were found.
*/
public static void updateSlashCommands(boolean force) public static void updateSlashCommands(boolean force)
{ {

View File

@@ -0,0 +1,39 @@
package wtf.beatrice.hidekobot.util;
import org.apache.commons.lang3.SerializationException;
import java.io.*;
import java.util.Base64;
import java.util.LinkedList;
import java.util.List;
public class SerializationUtil
{
public static String serializeBase64(List dataList) {
try (ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream so = new ObjectOutputStream(bo)) {
so.writeObject(dataList);
so.flush();
return Base64.getEncoder().encodeToString(bo.toByteArray());
}
catch (IOException e) {
throw new SerializationException("Error during serialization", e);
}
}
public static LinkedList deserializeBase64(String dataStr) {
byte[] b = Base64.getDecoder().decode(dataStr);
ByteArrayInputStream bi = new ByteArrayInputStream(b);
ObjectInputStream si;
try {
si = new ObjectInputStream(bi);
return LinkedList.class.cast(si.readObject());
}
catch (IOException | ClassNotFoundException e) {
throw new SerializationException("Error during deserialization", e);
}
}
}