summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/net/tylermurphy/ken/Ken.java8
-rw-r--r--src/main/java/net/tylermurphy/ken/api/Request.java13
-rw-r--r--src/main/java/net/tylermurphy/ken/command/Responder.java27
-rw-r--r--src/main/java/net/tylermurphy/ken/command/fun/CrabRave.java74
-rw-r--r--src/main/java/net/tylermurphy/ken/command/fun/Eject.java86
-rw-r--r--src/main/java/net/tylermurphy/ken/command/fun/GayMeme.java82
-rw-r--r--src/main/java/net/tylermurphy/ken/command/fun/Headpat.java84
-rw-r--r--src/main/java/net/tylermurphy/ken/command/fun/Wank.java84
-rw-r--r--src/main/java/net/tylermurphy/ken/command/game/Claim.java (renamed from src/main/java/net/tylermurphy/ken/command/fun/Claim.java)4
-rw-r--r--src/main/java/net/tylermurphy/ken/command/game/Money.java (renamed from src/main/java/net/tylermurphy/ken/command/fun/Money.java)4
-rw-r--r--src/main/java/net/tylermurphy/ken/command/game/Roulette.java105
-rw-r--r--src/main/java/net/tylermurphy/ken/command/game/Slots.java (renamed from src/main/java/net/tylermurphy/ken/command/fun/Slots.java)8
-rw-r--r--src/main/java/net/tylermurphy/ken/command/main/Help.java14
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/Ban.java86
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/CloseServer.java4
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/History.java4
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/Kick.java4
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/Mute.java4
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/OpenServer.java4
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/TempBan.java109
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/UnBan.java88
-rw-r--r--src/main/java/net/tylermurphy/ken/command/moderation/UnMute.java4
-rw-r--r--src/main/java/net/tylermurphy/ken/command/selfrole/AddRole.java (renamed from src/main/java/net/tylermurphy/ken/command/main/AddRole.java)2
-rw-r--r--src/main/java/net/tylermurphy/ken/command/selfrole/AddRolesPage.java (renamed from src/main/java/net/tylermurphy/ken/command/main/AddRolesPage.java)2
-rw-r--r--src/main/java/net/tylermurphy/ken/command/selfrole/DeleteRole.java (renamed from src/main/java/net/tylermurphy/ken/command/main/DeleteRole.java)2
-rw-r--r--src/main/java/net/tylermurphy/ken/command/selfrole/DeleteRolesPage.java (renamed from src/main/java/net/tylermurphy/ken/command/main/DeleteRolesPage.java)2
-rw-r--r--src/main/java/net/tylermurphy/ken/command/selfrole/Roles.java (renamed from src/main/java/net/tylermurphy/ken/command/main/Roles.java)2
-rw-r--r--src/main/java/net/tylermurphy/ken/database/Database.java4
-rw-r--r--src/main/java/net/tylermurphy/ken/database/EconomyTable.java2
-rw-r--r--src/main/java/net/tylermurphy/ken/database/ModerationTable.java84
-rw-r--r--src/main/java/net/tylermurphy/ken/image/GifFactory.java131
-rw-r--r--src/main/java/net/tylermurphy/ken/image/ImageFactory.java75
-rw-r--r--src/main/java/net/tylermurphy/ken/util/Checks.java33
-rw-r--r--src/main/java/net/tylermurphy/ken/util/GifSequenceWriter.java75
-rw-r--r--src/main/java/net/tylermurphy/ken/util/ImageFetcher.java30
35 files changed, 1114 insertions, 230 deletions
diff --git a/src/main/java/net/tylermurphy/ken/Ken.java b/src/main/java/net/tylermurphy/ken/Ken.java
index b03ea3d..b9b6cba 100644
--- a/src/main/java/net/tylermurphy/ken/Ken.java
+++ b/src/main/java/net/tylermurphy/ken/Ken.java
@@ -8,17 +8,19 @@ import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Role;
+import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.api.requests.GatewayIntent;
import net.dv8tion.jda.api.utils.MemberCachePolicy;
import net.tylermurphy.ken.command.Responder;
import net.tylermurphy.ken.database.Database;
import net.tylermurphy.ken.music.PlayerManager;
+import net.tylermurphy.ken.util.ImageFetcher;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.awt.Color;
+import java.awt.*;
import java.io.File;
import java.io.InputStream;
@@ -32,12 +34,14 @@ public class Ken {
private final YouTube youTube;
private final Responder responder;
private final Database database;
+ private final ImageFetcher imageFetcher;
private Ken(){
Ken.instance = this;
this.config = Config.create("config.yml");
this.log = LoggerFactory.getLogger(Ken.class);
this.database = new Database();
+ this.imageFetcher = new ImageFetcher();
try {
api = JDABuilder.createDefault(config.getString("botToken"))
.setActivity(Activity.playing("@Ken | /help"))
@@ -91,6 +95,8 @@ public class Ken {
public Database getDatabase() { return database; }
+ public ImageFetcher getImageFetcher() { return imageFetcher; }
+
public Role getRoleById(long id) { return api.getRoleById(id); }
public EmbedBuilder getDefaultEmbed() {
diff --git a/src/main/java/net/tylermurphy/ken/api/Request.java b/src/main/java/net/tylermurphy/ken/api/Request.java
index 8daa899..4c7ba7d 100644
--- a/src/main/java/net/tylermurphy/ken/api/Request.java
+++ b/src/main/java/net/tylermurphy/ken/api/Request.java
@@ -1,5 +1,6 @@
package net.tylermurphy.ken.api;
+import javax.imageio.ImageIO;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
@@ -77,4 +78,16 @@ public abstract class Request {
return null;
}
+ public static HttpURLConnection getConnection(String link){
+ try {
+ final URL url = new URL(link);
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0");
+ return connection;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
}
diff --git a/src/main/java/net/tylermurphy/ken/command/Responder.java b/src/main/java/net/tylermurphy/ken/command/Responder.java
index 3dfc644..bd4e0e1 100644
--- a/src/main/java/net/tylermurphy/ken/command/Responder.java
+++ b/src/main/java/net/tylermurphy/ken/command/Responder.java
@@ -20,15 +20,18 @@ import net.dv8tion.jda.api.interactions.components.selections.SelectMenu;
import net.dv8tion.jda.api.requests.restaction.interactions.ReplyCallbackAction;
import net.dv8tion.jda.api.utils.FileUpload;
import net.tylermurphy.ken.Ken;
-import net.tylermurphy.ken.command.fun.Claim;
-import net.tylermurphy.ken.command.fun.Eject;
-import net.tylermurphy.ken.command.fun.Money;
-import net.tylermurphy.ken.command.fun.Slots;
+import net.tylermurphy.ken.command.game.Claim;
+import net.tylermurphy.ken.command.game.Money;
+import net.tylermurphy.ken.command.game.Roulette;
+import net.tylermurphy.ken.command.game.Slots;
+import net.tylermurphy.ken.command.fun.*;
import net.tylermurphy.ken.command.game.*;
import net.tylermurphy.ken.command.main.*;
+import net.tylermurphy.ken.command.moderation.*;
import net.tylermurphy.ken.command.music.*;
import net.tylermurphy.ken.command.music.Queue;
import net.tylermurphy.ken.command.nsfw.*;
+import net.tylermurphy.ken.command.selfrole.*;
import java.awt.*;
import java.lang.reflect.Method;
@@ -79,6 +82,20 @@ public class Responder extends ListenerAdapter {
new Money(),
new Slots(),
new Claim(),
+ new Headpat(),
+ new Roulette(),
+ new GayMeme(),
+ new CrabRave(),
+ new Wank(),
+ new Ban(),
+ new TempBan(),
+ new Kick(),
+ new UnBan(),
+ new Mute(),
+ new UnMute(),
+ new OpenServer(),
+ new CloseServer(),
+ new History(),
};
Arrays.stream(objects).forEach(register::register);
}
@@ -136,6 +153,7 @@ public class Responder extends ListenerAdapter {
.setTitle(":x: **Error**")
.setDescription(e.getCause() != null ? e.getCause().getMessage() : e.getMessage());
event.replyEmbeds(builder.build()).setEphemeral(true).queue();
+ e.printStackTrace();
}
}
@@ -283,6 +301,7 @@ public class Responder extends ListenerAdapter {
switch (option.getType()) {
case STRING -> args.add(option.getAsString());
case INTEGER -> args.add(option.getAsInt());
+ case NUMBER -> args.add(option.getAsDouble());
case BOOLEAN -> args.add(option.getAsBoolean());
case USER -> args.add(option.getAsMember());
case CHANNEL -> args.add(option.getAsChannel());
diff --git a/src/main/java/net/tylermurphy/ken/command/fun/CrabRave.java b/src/main/java/net/tylermurphy/ken/command/fun/CrabRave.java
new file mode 100644
index 0000000..b0d3b0f
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/fun/CrabRave.java
@@ -0,0 +1,74 @@
+package net.tylermurphy.ken.command.fun;
+
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.api.Request;
+import net.tylermurphy.ken.command.Response;
+import net.tylermurphy.ken.command.annotation.Command;
+import net.tylermurphy.ken.command.annotation.Option;
+import net.tylermurphy.ken.util.GifSequenceWriter;
+
+import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+public class CrabRave {
+
+ @Command(name="crabrave", description="Crav rave a server member")
+ @Option(name="member", description="Server member to rave", type= OptionType.USER, required=true)
+ public Response execute(Member sender, List<Object> args){
+ Member ejected = (Member) args.get(0);
+ try {
+ byte[] data = generateGif(ejected.getUser());
+ return Response.success(ejected.getEffectiveName() +" was raved by "+sender.getEffectiveName()).setFile(data, "eject.gif");
+ } catch (IOException e) {
+ return Response.error("Failed to generate gif");
+ }
+ }
+
+ private byte[] generateGif(User u) throws IOException, NullPointerException {
+ BufferedImage crab = Ken.getInstance().getImageFetcher().getImage("https://cdn.tylermurphy.net/ken/crab.png", true);
+ BufferedImage avatar = Ken.getInstance().getImageFetcher().getImage(u.getAvatarUrl(), false);
+ BufferedImage[] frames = new BufferedImage[26];
+ for(int i = 0; i < frames.length; i++) {
+ frames[i] = generateFrame(i, avatar, crab);
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
+ GifSequenceWriter writer = new GifSequenceWriter(ios, frames[0].getType(), 10, true);
+
+ for (BufferedImage img : frames) {
+ writer.writeToSequence(img);
+ }
+
+ writer.close();
+ ios.close();
+ return baos.toByteArray();
+
+ }
+
+ private BufferedImage generateFrame(int frame, BufferedImage avatar, BufferedImage crab) {
+ int w = 500;
+ int h = 275;
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g2d = img.createGraphics();
+
+ g2d.drawImage(crab, 500*-frame,0, 13000, 275, null);
+ g2d.drawImage(avatar,60,175, 75, 75, null);
+ g2d.setFont(new Font("Dialog", Font.BOLD, 50));
+ g2d.setColor(Color.black);
+ g2d.drawString("IS GONE", 180, 225);
+ g2d.dispose();
+
+ return img;
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/fun/Eject.java b/src/main/java/net/tylermurphy/ken/command/fun/Eject.java
index fda1e18..29900be 100644
--- a/src/main/java/net/tylermurphy/ken/command/fun/Eject.java
+++ b/src/main/java/net/tylermurphy/ken/command/fun/Eject.java
@@ -1,12 +1,21 @@
package net.tylermurphy.ken.command.fun;
import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.api.Request;
import net.tylermurphy.ken.command.annotation.Command;
import net.tylermurphy.ken.command.annotation.Option;
import net.tylermurphy.ken.command.Response;
-import net.tylermurphy.ken.image.GifFactory;
+import net.tylermurphy.ken.util.GifSequenceWriter;
+import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.util.List;
public class Eject {
@@ -15,8 +24,79 @@ public class Eject {
@Option(name="member", description="Server member to eject", type=OptionType.USER, required=true)
public Response execute(Member sender, List<Object> args){
Member ejected = (Member) args.get(0);
- byte[] data = GifFactory.generateEjectGif(ejected.getUser());
- return Response.success(ejected.getEffectiveName() +" was ejected by "+sender.getEffectiveName()).setFile(data, "eject.gif");
+ try {
+ byte[] data = generateGif(ejected.getUser());
+ return Response.success(ejected.getEffectiveName() +" was ejected by "+sender.getEffectiveName()).setFile(data, "eject.gif");
+ } catch (IOException e) {
+ return Response.error("Failed to generate gif");
+ }
+ }
+
+ private byte[] generateGif(User u) throws IOException {
+ String message;
+ if (Math.random() > .5)
+ message = u.getName() + " was An Impostor";
+ else
+ message = u.getName() + " was not An Impostor";
+
+ BufferedImage space = Ken.getInstance().getImageFetcher().getImage("https://cdn.tylermurphy.net/ken/space.jpg", true);
+ BufferedImage avatar = Ken.getInstance().getImageFetcher().getImage(u.getAvatarUrl(), false);
+
+ BufferedImage[] frames = new BufferedImage[30];
+ for (int i = 0; i < frames.length; i++) {
+ frames[i] = generateFrame(message, Math.min(i, 23), 24, space, avatar);
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
+ GifSequenceWriter writer = new GifSequenceWriter(ios, frames[0].getType(), 8, true);
+
+ for (BufferedImage img : frames) {
+ writer.writeToSequence(img);
+ }
+
+ writer.close();
+ ios.close();
+ return baos.toByteArray();
+ }
+
+ public BufferedImage generateFrame(String message, int frame, float totalFrames, BufferedImage space1, BufferedImage avatar) {
+ int w = 600;
+ int h = 400;
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g2d = img.createGraphics();
+ g2d.drawImage(space1, (int) (-100+100/totalFrames*frame), 0, (int)(w*1.5), (int)(h*1.5), null);
+
+ BufferedImage ravater = rotate(avatar, 180/24*frame);
+ g2d.drawImage(ravater, (w+2*avatar.getWidth()+100)/24*frame-avatar.getWidth()-50, h/2-avatar.getHeight()/2, 150, 150, null);
+
+ if(frame > 12) {
+ int length = (message.length() * (frame-11)/12);
+ message = message.substring(0, length);
+
+ g2d.setFont(new Font("Dialog", Font.BOLD, 25));
+ FontMetrics fm = g2d.getFontMetrics();
+ int x = w/2 - fm.stringWidth(message)/2;
+ int y = h/2;
+ g2d.drawString(message, x, y);
+ }
+
+ g2d.dispose();
+
+ return img;
+ }
+
+ private BufferedImage rotate(BufferedImage img, double angle) {
+ int w = img.getWidth()+50;
+ int h = img.getHeight()+50;
+
+ BufferedImage rotated = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphic = rotated.createGraphics();
+ graphic.rotate(Math.toRadians(angle), w/2, h/2);
+ graphic.drawImage(img, null, w/2-img.getWidth()/2, h/2-img.getHeight()/2);
+ graphic.dispose();
+ return rotated;
}
}
diff --git a/src/main/java/net/tylermurphy/ken/command/fun/GayMeme.java b/src/main/java/net/tylermurphy/ken/command/fun/GayMeme.java
new file mode 100644
index 0000000..b538606
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/fun/GayMeme.java
@@ -0,0 +1,82 @@
+package net.tylermurphy.ken.command.fun;
+
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.api.Request;
+import net.tylermurphy.ken.command.Response;
+import net.tylermurphy.ken.command.annotation.Command;
+import net.tylermurphy.ken.command.annotation.Option;
+import net.tylermurphy.ken.util.GifSequenceWriter;
+
+import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageOutputStream;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+public class GayMeme {
+
+ @Command(name="gaymeme", description="Generate a gay meme about a fellow server member")
+ @Option(name="member", description="Server member to make the meme about", type= OptionType.USER, required=true)
+ public Response execute(Member sender, List<Object> args){
+ Member ejected = (Member) args.get(0);
+ try {
+ byte[] data = generateGif(ejected.getUser());
+ return Response.success(sender.getEffectiveName() +" thinks "+ejected.getEffectiveName()+ " is gay!").setFile(data, "eject.gif");
+ } catch (IOException e) {
+ return Response.error("Failed to generate gif");
+ }
+ }
+
+ private byte[] generateGif(User u) throws IOException, NullPointerException {
+ BufferedImage avatar = Ken.getInstance().getImageFetcher().getImage(u.getAvatarUrl(), false);
+ BufferedImage background = Ken.getInstance().getImageFetcher().getImage("https://cdn.tylermurphy.net/ken/theMoreYouKnow.png", true);
+ BufferedImage gay = Ken.getInstance().getImageFetcher().getImage("https://cdn.tylermurphy.net/ken/gay.png", true);
+
+ BufferedImage[] frames = new BufferedImage[40];
+ for(int i = 0; i < frames.length; i++) {
+ frames[i] = generateFrame(i, avatar, background, gay);
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
+ GifSequenceWriter writer = new GifSequenceWriter(ios, frames[0].getType(), 16, true);
+
+ for (BufferedImage img : frames) {
+ writer.writeToSequence(img);
+ }
+
+ writer.close();
+ ios.close();
+ return baos.toByteArray();
+
+ }
+
+ private BufferedImage generateFrame(int frame, BufferedImage avatar, BufferedImage background, BufferedImage gay) {
+ int w = 400;
+ int h = 200;
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g2d = img.createGraphics();
+
+ if(frame < 25) {
+ g2d.drawImage(gay, 0, 0, 400, 200, null);
+ if (frame % 2 == 0) {
+ g2d.setColor(Color.black);
+ g2d.fillRect(0, 50, 400, 100);
+ }
+ g2d.drawImage(avatar, 150, 50, 100, 100, null);
+ } else {
+ g2d.drawImage(background, (400*3)*-Math.min(7, frame-24), 0, 9600, 200, null);
+ }
+
+ g2d.dispose();
+
+ return img;
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/fun/Headpat.java b/src/main/java/net/tylermurphy/ken/command/fun/Headpat.java
new file mode 100644
index 0000000..5a2239e
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/fun/Headpat.java
@@ -0,0 +1,84 @@
+package net.tylermurphy.ken.command.fun;
+
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.api.Request;
+import net.tylermurphy.ken.command.Response;
+import net.tylermurphy.ken.command.annotation.Command;
+import net.tylermurphy.ken.command.annotation.Option;
+import net.tylermurphy.ken.util.GifSequenceWriter;
+
+import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+public class Headpat {
+
+ @Command(name="headpat", description="Head pat a fellow server member")
+ @Option(name="member", description="Server member to head pat", type= OptionType.USER, required=true)
+ public Response execute(Member sender, List<Object> args){
+ Member ejected = (Member) args.get(0);
+ try {
+ byte[] data = generateGif(ejected.getUser());
+ return Response.success(sender.getEffectiveName() +" head patted "+ejected.getEffectiveName()).setFile(data, "eject.gif");
+ } catch (IOException e) {
+ return Response.error("Failed to generate gif");
+ }
+ }
+
+ private byte[] generateGif(User u) throws IOException, NullPointerException {
+ BufferedImage hand = Ken.getInstance().getImageFetcher().getImage("https://cdn.tylermurphy.net/ken/headpat.png", true);
+ BufferedImage avatar = Ken.getInstance().getImageFetcher().getImage(u.getAvatarUrl(), false);
+
+ BufferedImage[] frames = new BufferedImage[5];
+ for(int i = 0; i < frames.length; i++) {
+ frames[i] = generateFrame(i, hand, avatar);
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
+ GifSequenceWriter writer = new GifSequenceWriter(ios, frames[0].getType(), 4, true);
+
+ for (BufferedImage img : frames) {
+ writer.writeToSequence(img);
+ }
+
+ writer.close();
+ ios.close();
+ return baos.toByteArray();
+
+ }
+
+ private BufferedImage generateFrame(int frame, BufferedImage hand, BufferedImage avatar){
+ int w = 400;
+ int h = 400;
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g2d = img.createGraphics();
+
+
+ if(frame == 0)
+ g2d.drawImage(avatar, 140, 140, 260, 260, null);
+ else if(frame == 1)
+ g2d.drawImage(avatar, 120, 160, 280, 240, null);
+ else if(frame == 2)
+ g2d.drawImage(avatar, 100, 180, 300, 220, null);
+ else if(frame == 3)
+ g2d.drawImage(avatar, 110, 160, 280, 240, null);
+ else if(frame == 4)
+ g2d.drawImage(avatar, 130, 140, 260, 260, null);
+
+ g2d.drawImage(hand, frame*-400,0, 2000, 400, null);
+
+ g2d.dispose();
+
+ return img;
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/fun/Wank.java b/src/main/java/net/tylermurphy/ken/command/fun/Wank.java
new file mode 100644
index 0000000..c2cb6f1
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/fun/Wank.java
@@ -0,0 +1,84 @@
+package net.tylermurphy.ken.command.fun;
+
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.User;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.command.Response;
+import net.tylermurphy.ken.command.annotation.Command;
+import net.tylermurphy.ken.command.annotation.Option;
+import net.tylermurphy.ken.util.GifSequenceWriter;
+
+import javax.imageio.ImageIO;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+public class Wank {
+
+ @Command(name="wank", description="Wank to a fellow crew member")
+ @Option(name="member", description="Server member to wank", type= OptionType.USER, required=true)
+ public Response execute(Member sender, List<Object> args){
+ Member ejected = (Member) args.get(0);
+ try {
+ byte[] data = generateGif(ejected.getUser());
+ return Response.success(sender.getEffectiveName() +" wanked to "+ejected.getEffectiveName()).setFile(data, "eject.gif");
+ } catch (IOException e) {
+ return Response.error("Failed to generate gif");
+ }
+ }
+
+ private byte[] generateGif(User u) throws IOException {
+ BufferedImage wank = Ken.getInstance().getImageFetcher().getImage("https://cdn.tylermurphy.net/ken/laptop.png", true);
+ BufferedImage avatar = Ken.getInstance().getImageFetcher().getImage(u.getAvatarUrl(), false);
+
+ BufferedImage[] frames = new BufferedImage[4];
+ for (int i = 0; i < frames.length; i++) {
+ frames[i] = generateFrame(i, rotate(avatar, -35), wank);
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
+ GifSequenceWriter writer = new GifSequenceWriter(ios, frames[0].getType(), 8, true);
+
+ for (BufferedImage img : frames) {
+ writer.writeToSequence(img);
+ }
+
+ writer.close();
+ ios.close();
+ return baos.toByteArray();
+ }
+
+ public BufferedImage generateFrame(int frame, BufferedImage avatar, BufferedImage wank) {
+ int w = 640;
+ int h = 360;
+ BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+
+ Graphics2D g2d = img.createGraphics();
+
+ g2d.drawImage(avatar, 100, 95, 300, 140, null);
+ g2d.drawImage(wank, 640*-frame,0, 2560, 360, null);
+
+ g2d.dispose();
+
+ return img;
+ }
+
+ private BufferedImage rotate(BufferedImage img, double angle) {
+ int w = img.getWidth()+50;
+ int h = img.getHeight()+50;
+
+ BufferedImage rotated = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D graphic = rotated.createGraphics();
+ graphic.rotate(Math.toRadians(angle), w/2, h/2);
+ graphic.drawImage(img, null, w/2-img.getWidth()/2, h/2-img.getHeight()/2);
+ graphic.dispose();
+ return rotated;
+ }
+
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/fun/Claim.java b/src/main/java/net/tylermurphy/ken/command/game/Claim.java
index bc53b83..1016935 100644
--- a/src/main/java/net/tylermurphy/ken/command/fun/Claim.java
+++ b/src/main/java/net/tylermurphy/ken/command/game/Claim.java
@@ -1,4 +1,4 @@
-package net.tylermurphy.ken.command.fun;
+package net.tylermurphy.ken.command.game;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
@@ -17,7 +17,7 @@ public class Claim {
@Command(name="claim",description="Claim a random amount of moneys per 24h")
public Response execute(Member sender, Guild guild){
String data = Ken.getInstance().getDatabase().getEconomyData().getData(guild.getIdLong(), sender.getUser().getIdLong());
- JSONObject json = Ken.getInstance().getDatabase().getEconomyData().updateData(guild.getIdLong(), sender.getUser().getIdLong(), data);
+ JSONObject json = Ken.getInstance().getDatabase().getEconomyData().updateData(data);
int money = json.getInt("money");
long claim = json.getLong("claim");
long currentTime = new Date().getTime();
diff --git a/src/main/java/net/tylermurphy/ken/command/fun/Money.java b/src/main/java/net/tylermurphy/ken/command/game/Money.java
index 7d399e2..b1fb1b5 100644
--- a/src/main/java/net/tylermurphy/ken/command/fun/Money.java
+++ b/src/main/java/net/tylermurphy/ken/command/game/Money.java
@@ -1,4 +1,4 @@
-package net.tylermurphy.ken.command.fun;
+package net.tylermurphy.ken.command.game;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
@@ -15,7 +15,7 @@ public class Money {
@Command(name="money",description="Get the amount of money you have")
public Response execute(Member sender, Guild guild){
String data = Ken.getInstance().getDatabase().getEconomyData().getData(guild.getIdLong(), sender.getUser().getIdLong());
- JSONObject json = Ken.getInstance().getDatabase().getEconomyData().updateData(guild.getIdLong(), sender.getUser().getIdLong(), data);
+ JSONObject json = Ken.getInstance().getDatabase().getEconomyData().updateData(data);
int money = json.getInt("money");
MessageEmbed embed = Ken.getInstance().getDefaultEmbed()
.setColor(Color.green)
diff --git a/src/main/java/net/tylermurphy/ken/command/game/Roulette.java b/src/main/java/net/tylermurphy/ken/command/game/Roulette.java
new file mode 100644
index 0000000..05a9d1b
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/game/Roulette.java
@@ -0,0 +1,105 @@
+package net.tylermurphy.ken.command.game;
+
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.command.Response;
+import net.tylermurphy.ken.command.annotation.Command;
+import net.tylermurphy.ken.command.annotation.Option;
+import net.tylermurphy.ken.command.annotation.Selection;
+import org.json.JSONObject;
+
+import java.util.List;
+
+public class Roulette {
+
+ @Command(name="roulette", description="Bet on a game of roulette")
+ @Option(name="bet", description="How much you want to bet", type=OptionType.INTEGER, required=true)
+ @Selection(name="type", description="What type of bet do you want to place", type=OptionType.STRING, choices={"red","black","green","odd","even","high","low","1st dozen","2nd dozen","3rd dozen","first four"}, required=true)
+ public Response execute(Member sender ,Guild guild, List<Object> args){
+ String data = Ken.getInstance().getDatabase().getEconomyData().getData(guild.getIdLong(), sender.getUser().getIdLong());
+ JSONObject json = Ken.getInstance().getDatabase().getEconomyData().updateData(data);
+ int money = json.getInt("money");
+ int bet = (int)args.get(0);
+ if(bet < 5){
+ return Response.error("You cannot bet anything less than 5 moneys");
+ } else if(bet > money){
+ return Response.error("You don't have enough moneys to bet this much");
+ }
+ String guess = (String)args.get(1);
+ int rand = (int)(Math.random()*38);
+ boolean win = switch (guess) {
+ case "green" -> getColor(rand).equals("green");
+ case "black" -> getColor(rand).equals("black");
+ case "red" -> getColor(rand).equals("red");
+ case "even" -> rand>1 && (rand)%2==1;
+ case "odd" -> rand>1 && rand%2==0;
+ case "low" -> rand>=2 && rand<=19;
+ case "high" -> rand>19;
+ case "1st dozen" -> rand>=2 && rand<=13;
+ case "2nd dozen" -> rand>=14 && rand<=25;
+ case "3rd dozen" -> rand>25;
+ case "first four" -> rand<3;
+ default -> false;
+ };
+ int rate = getWinRate(guess);
+ int balance = win ? money -bet + bet * rate : money - bet;
+ EmbedBuilder builder = Ken.getInstance().getDefaultEmbed()
+ .setAuthor("Roulette")
+ .appendDescription("`Balance:` "+money+"\n")
+ .appendDescription("`Bet:` "+bet+"\n")
+ .appendDescription(getEmoji(rand) + getNumber(rand)+"\n")
+ .appendDescription("`New Balance:` "+balance+"\n")
+ .setFooter(win ? "You win! ("+rate+"x)" : "You loose, bet lost");
+ json.put("money",balance);
+ Ken.getInstance().getDatabase().getEconomyData().setData(guild.getIdLong(), sender.getUser().getIdLong(), json.toString());
+ return Response.success(builder.build());
+ }
+
+ private String getColor(int rand){
+ boolean even = (rand&2)==1;
+ if(rand<3) return "green";
+ if(rand<=10)
+ if(even) return "black";
+ else return "red";
+ else if(rand<=18)
+ if(even) return "red";
+ else return "black";
+ else if(rand<=28)
+ if(even) return "black";
+ else return "red";
+ else
+ if(even) return "red";
+ else return "black";
+ }
+
+ private int getWinRate(String guess){
+ return switch (guess) {
+ case "green" -> 17;
+ case "black", "red", "even", "odd", "low", "high" -> 2;
+ case "1st dozen", "2nd dozen", "3rd dozen" -> 3;
+ case "first four" -> 6;
+ default -> 1;
+ };
+ }
+
+ private String getEmoji(int rand){
+ String color = getColor(rand);
+ if(color.equals("green"))
+ return ":green_square:";
+ else if(color.equals("red"))
+ return ":red_square:";
+ else
+ return ":black_large_square:";
+ }
+
+ private String getNumber(int rand){
+ if(rand<2)
+ return rand == 0 ? "00" : "0";
+ else
+ return String.valueOf(rand-1);
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/fun/Slots.java b/src/main/java/net/tylermurphy/ken/command/game/Slots.java
index f03d50c..2ccdef1 100644
--- a/src/main/java/net/tylermurphy/ken/command/fun/Slots.java
+++ b/src/main/java/net/tylermurphy/ken/command/game/Slots.java
@@ -1,4 +1,4 @@
-package net.tylermurphy.ken.command.fun;
+package net.tylermurphy.ken.command.game;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Guild;
@@ -31,7 +31,7 @@ public class Slots {
@Option(name="amount", description="Amount to bet", type=OptionType.INTEGER, required=true)
public Response execute(Member sender, Guild guild, List<Object> args){
String data = Ken.getInstance().getDatabase().getEconomyData().getData(guild.getIdLong(), sender.getUser().getIdLong());
- JSONObject json = Ken.getInstance().getDatabase().getEconomyData().updateData(guild.getIdLong(), sender.getUser().getIdLong(), data);
+ JSONObject json = Ken.getInstance().getDatabase().getEconomyData().updateData(data);
int money = json.getInt("money");
int bet = (int)args.get(0);
if(bet < 5){
@@ -46,9 +46,7 @@ public class Slots {
.setTitle("Slot Machine")
.appendDescription("`Balance:` "+money+"\n")
.appendDescription("`Bet:` "+bet+"\n")
- .appendDescription(":black_large_square:".repeat(5)+"\n")
- .appendDescription(":black_large_square:" + slot1.emoji + slot2.emoji + slot3.emoji +":black_large_square:\n")
- .appendDescription(":black_large_square:".repeat(5)+"\n");
+ .appendDescription(slot1.emoji + slot2.emoji + slot3.emoji + "\n");
if(slot1.emoji.equals(slot2.emoji) && slot1.emoji.equals(slot3.emoji)){
// 3 of a kind
money += bet * slot2.full_reward;
diff --git a/src/main/java/net/tylermurphy/ken/command/main/Help.java b/src/main/java/net/tylermurphy/ken/command/main/Help.java
index 4b13bba..316ba24 100644
--- a/src/main/java/net/tylermurphy/ken/command/main/Help.java
+++ b/src/main/java/net/tylermurphy/ken/command/main/Help.java
@@ -65,12 +65,24 @@ public class Help {
.appendDescription("**/queue** View the current song queue\n"),
Ken.getInstance().getDefaultEmbed()
.setAuthor("Command List")
+ .setTitle(":camera: **Gif Commands**")
+ .appendDescription("**/crabrave <member>** Crab rave on someone\n")
+ .appendDescription("**/eject <member>** Eject someone because they are sus\n")
+ .appendDescription("**/gaymeme <member>** Warn someone that they are gay\n")
+ .appendDescription("**/headpat <member>** Pat someone on the head\n")
+ .appendDescription("**/wank <member>** Wank to someone\n"),
+ Ken.getInstance().getDefaultEmbed()
+ .setAuthor("Command List")
.setTitle(":game_die: **Game Commands**")
.appendDescription("**/d6** Roll a d6 die\n")
.appendDescription("**/d8** Roll a d8 die\n")
.appendDescription("**/d12** Roll a d12 die\n")
.appendDescription("**/d20** Roll a d20 die\n")
- .appendDescription("**/dice <sides>** Roll a dice with set sides\n"),
+ .appendDescription("**/dice <sides>** Roll a dice with set sides\n")
+ .appendDescription("**/claim** Claim moneys every 24h\n")
+ .appendDescription("**/money** Check how much moneys you have\n")
+ .appendDescription("**/roulette <bet>** Gamble on roulette\n")
+ .appendDescription("**/slots <bet>** Gamble on slots\n"),
Ken.getInstance().getDefaultEmbed()
.setAuthor("Command List")
.setTitle(":man_detective: **Self Roles**")
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/Ban.java b/src/main/java/net/tylermurphy/ken/command/moderation/Ban.java
new file mode 100644
index 0000000..95ee7e7
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/Ban.java
@@ -0,0 +1,86 @@
+package net.tylermurphy.ken.command.moderation;
+
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.PrivateChannel;
+import net.dv8tion.jda.api.entities.Role;
+import net.dv8tion.jda.api.exceptions.ErrorResponseException;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.command.Response;
+import net.tylermurphy.ken.command.annotation.Command;
+import net.tylermurphy.ken.command.annotation.Option;
+import net.tylermurphy.ken.command.annotation.Requirement;
+import net.tylermurphy.ken.database.ModerationTable;
+import net.tylermurphy.ken.util.Checks;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.awt.*;
+import java.util.Date;
+import java.util.List;
+
+public class Ban {
+
+ @Command(name="ban", description="Perm ban a user and delete all messages in past 24h")
+ @Option(name="member", description="Member to ban", type=OptionType.USER, required=true)
+ @Option(name="reason", description="Reason to ban", type=OptionType.STRING, required=true)
+ @Option(name="deletemessages", description="Delete messages from past 24h", type=OptionType.BOOLEAN, required=true)
+ @Requirement(Permission.BAN_MEMBERS)
+ public Response execute(Member sender, List<Object> args, Guild guild){
+ Member target = (Member) args.get(0);
+ String reason = (String) args.get(1);
+ boolean purge = (boolean) args.get(2);
+ if(target == sender){
+ return Response.error("You are not allowed to do this to yourself");
+ }
+ Role low = Checks.getHighestRole(target);
+ Role high = Checks.getHighestRole(sender);
+ if(!Checks.getRolePermission(high, low)){
+ return Response.error("You need a higher role than the target");
+ }
+ Role self = Checks.getHighestRole(guild.getSelfMember());
+ if(!Checks.getRolePermission(self, low)){
+ return Response.error("I need a higher role than the target");
+ }
+
+ try {
+ guild.retrieveBan(target).complete();
+ return Response.error("User is already banned from this server");
+ } catch (ErrorResponseException ignored) {}
+
+ ModerationTable table = Ken.getInstance().getDatabase().getModerationTable();
+ String data = table.getData(guild.getIdLong(), target.getUser().getIdLong());
+ JSONObject json = table.updateData(data);
+
+ JSONObject status = json.getJSONObject("status");
+ status.put("type", "ban");
+ status.put("reason", reason);
+ status.put("until", 0L);
+ json.put("status", status);
+
+ JSONArray history = json.getJSONArray("history");
+ history.put(new JSONObject().put("type", "ban").put("reason", reason).put("date",new Date().getTime()));
+ json.put("history", history);
+
+ table.setData(guild.getIdLong(), target.getUser().getIdLong(), json.toString());
+
+ PrivateChannel channel = target.getUser().openPrivateChannel().complete();
+ if(channel != null){
+ EmbedBuilder builder = Ken.getInstance().getDefaultEmbed()
+ .setColor(Color.red)
+ .setTitle("**Banned**")
+ .appendDescription("You have been permanently banned from "+guild.getName()+"\n")
+ .appendDescription("`By:` " + sender.getEffectiveName()+"\n")
+ .appendDescription("`Reason:` " + reason);
+ channel.sendMessageEmbeds(builder.build()).queue();
+ }
+ guild.ban(target,purge ? 1 : 0,reason).queue();
+ EmbedBuilder builder = Ken.getInstance().getDefaultEmbed()
+ .appendDescription(String.format("Permanently banned %s for %s", target.getEffectiveName(), reason));
+ return Response.success(builder.build());
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/CloseServer.java b/src/main/java/net/tylermurphy/ken/command/moderation/CloseServer.java
new file mode 100644
index 0000000..b6638d7
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/CloseServer.java
@@ -0,0 +1,4 @@
+package net.tylermurphy.ken.command.moderation;
+
+public class CloseServer {
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/History.java b/src/main/java/net/tylermurphy/ken/command/moderation/History.java
new file mode 100644
index 0000000..cc598f9
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/History.java
@@ -0,0 +1,4 @@
+package net.tylermurphy.ken.command.moderation;
+
+public class History {
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/Kick.java b/src/main/java/net/tylermurphy/ken/command/moderation/Kick.java
new file mode 100644
index 0000000..aad0de1
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/Kick.java
@@ -0,0 +1,4 @@
+package net.tylermurphy.ken.command.moderation;
+
+public class Kick {
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/Mute.java b/src/main/java/net/tylermurphy/ken/command/moderation/Mute.java
new file mode 100644
index 0000000..6e23d74
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/Mute.java
@@ -0,0 +1,4 @@
+package net.tylermurphy.ken.command.moderation;
+
+public class Mute {
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/OpenServer.java b/src/main/java/net/tylermurphy/ken/command/moderation/OpenServer.java
new file mode 100644
index 0000000..ce2b32a
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/OpenServer.java
@@ -0,0 +1,4 @@
+package net.tylermurphy.ken.command.moderation;
+
+public class OpenServer {
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/TempBan.java b/src/main/java/net/tylermurphy/ken/command/moderation/TempBan.java
new file mode 100644
index 0000000..62696ad
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/TempBan.java
@@ -0,0 +1,109 @@
+package net.tylermurphy.ken.command.moderation;
+
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.Guild;
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.PrivateChannel;
+import net.dv8tion.jda.api.entities.Role;
+import net.dv8tion.jda.api.exceptions.ErrorResponseException;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.command.Response;
+import net.tylermurphy.ken.command.annotation.Command;
+import net.tylermurphy.ken.command.annotation.Option;
+import net.tylermurphy.ken.command.annotation.Requirement;
+import net.tylermurphy.ken.database.ModerationTable;
+import net.tylermurphy.ken.util.Checks;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.awt.*;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+public class TempBan {
+
+ @Command(name="tempban", description="Temp ban a user and delete all messages in past 24h")
+ @Option(name="member", description="Member to temp-ban", type= OptionType.USER, required=true)
+ @Option(name="days", description="Days for temp ban", type= OptionType.INTEGER, required=true)
+ @Option(name="hours", description="Hours for temp ban", type= OptionType.INTEGER, required=true)
+ @Option(name="reason", description="Reason to temp-ban", type=OptionType.STRING, required=true)
+ @Option(name="deletemessages", description="Delete messages from past 24h", type=OptionType.BOOLEAN, required=true)
+ @Requirement(Permission.BAN_MEMBERS)
+ public Response execute(Member sender, List<Object> args, Guild guild){
+ Member target = (Member) args.get(0);
+ int days = (int) args.get(1);
+ int hours = (int) args.get(2);
+ String reason = (String) args.get(3);
+ boolean purge = (boolean) args.get(4);
+ if(target == sender){
+ return Response.error("You are not allowed to do this to yourself");
+ }
+ Role low = Checks.getHighestRole(target);
+ Role high = Checks.getHighestRole(sender);
+ if(!Checks.getRolePermission(high, low)){
+ return Response.error("You need a higher role than the target");
+ }
+ Role self = Checks.getHighestRole(guild.getSelfMember());
+ if(!Checks.getRolePermission(self, low)){
+ return Response.error("I need a higher role than the target");
+ }
+ if(hours < 0){
+ return Response.error("Hours must be 0 or greater");
+ }
+ if(days < 0){
+ return Response.error("Days must be 0 or greater");
+ }
+ if(days == 0 && hours == 0){
+ return Response.error("Time to ban cannot be 0");
+ }
+
+ try {
+ guild.retrieveBan(target).complete();
+ return Response.error("User is already banned from this server");
+ } catch (ErrorResponseException ignored) {}
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(new Date());
+ cal.add(Calendar.DATE, days);
+ cal.add(Calendar.HOUR, hours);
+ SimpleDateFormat format = new SimpleDateFormat("L d, y ha zz");
+ String date = format.format(cal.getTime());
+
+ ModerationTable table = Ken.getInstance().getDatabase().getModerationTable();
+ String data = table.getData(guild.getIdLong(), target.getUser().getIdLong());
+ JSONObject json = table.updateData(data);
+
+ JSONObject status = json.getJSONObject("status");
+ status.put("type", "temp-ban");
+ status.put("reason", reason);
+ status.put("until", cal.getTime().getTime());
+ json.put("status", status);
+
+ JSONArray history = json.getJSONArray("history");
+ history.put(new JSONObject().put("type", "temp-ban").put("reason", reason).put("date",new Date().getTime()));
+ json.put("history", history);
+
+ table.setData(guild.getIdLong(), target.getUser().getIdLong(), json.toString());
+
+ PrivateChannel channel = target.getUser().openPrivateChannel().complete();
+ if(channel != null){
+ EmbedBuilder builder = Ken.getInstance().getDefaultEmbed()
+ .setColor(Color.red)
+ .setTitle("**Temp Banned**")
+ .appendDescription("You have been temporarily banned from "+guild.getName()+"\n")
+ .appendDescription("`By:` " + sender.getEffectiveName()+"\n")
+ .appendDescription("`Reason:` " + reason)
+ .appendDescription("`Until:` " + date);
+ channel.sendMessageEmbeds(builder.build()).queue();
+ }
+ guild.ban(target,purge ? 1 : 0,reason).queue();
+ EmbedBuilder builder = Ken.getInstance().getDefaultEmbed()
+ .appendDescription(String.format("Temporarily banned %s for %s until %s", target.getEffectiveName(), reason, date));
+ return Response.success(builder.build());
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/UnBan.java b/src/main/java/net/tylermurphy/ken/command/moderation/UnBan.java
new file mode 100644
index 0000000..b259002
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/UnBan.java
@@ -0,0 +1,88 @@
+package net.tylermurphy.ken.command.moderation;
+
+import net.dv8tion.jda.api.EmbedBuilder;
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.*;
+import net.dv8tion.jda.api.exceptions.ErrorResponseException;
+import net.dv8tion.jda.api.interactions.commands.OptionType;
+import net.tylermurphy.ken.Ken;
+import net.tylermurphy.ken.command.Response;
+import net.tylermurphy.ken.command.annotation.Command;
+import net.tylermurphy.ken.command.annotation.Option;
+import net.tylermurphy.ken.command.annotation.Requirement;
+import net.tylermurphy.ken.database.ModerationTable;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.awt.*;
+import java.util.Date;
+import java.util.List;
+
+public class UnBan {
+
+ @Command(name="unban", description="Unban a user")
+ @Option(name="username", description="Username", type= OptionType.STRING, required=true)
+ @Option(name="discriminator", description="Discriminator", type=OptionType.INTEGER, required=true)
+ @Option(name="reason", description="Reason to unban", type=OptionType.STRING, required=true)
+ @Requirement(Permission.BAN_MEMBERS)
+ public Response execute(Member sender, Guild guild, List<Object> args){
+ String username = (String) args.get(0);
+ String discriminator = String.valueOf(args.get(1));
+ String reason = (String) args.get(2);
+
+ User target = null;
+ for(Guild.Ban ban : guild.retrieveBanList()){
+ User temp = ban.getUser();
+ if(temp.getName().equals(username) && temp.getDiscriminator().equals(discriminator)){
+ target = temp;
+ break;
+ }
+ }
+
+ if(target == null){
+ return Response.error("Unable to find this user");
+ }
+
+ if(target == sender.getUser()){
+ return Response.error("You are not allowed to do this to yourself");
+ }
+
+ try {
+ guild.retrieveBan(target).complete();
+ } catch (ErrorResponseException e) {
+ return Response.error("User is not banned from this server");
+ }
+
+ ModerationTable table = Ken.getInstance().getDatabase().getModerationTable();
+ String data = table.getData(guild.getIdLong(), target.getIdLong());
+ JSONObject json = table.updateData(data);
+
+ JSONObject status = json.getJSONObject("status");
+ status.put("type", "None");
+ status.put("reason", "");
+ status.put("until", 0L);
+ json.put("status", status);
+
+ JSONArray history = json.getJSONArray("history");
+ history.put(new JSONObject().put("type", "unban").put("reason", reason).put("date",new Date().getTime()));
+ json.put("history", history);
+
+ table.setData(guild.getIdLong(), target.getIdLong(), json.toString());
+
+ PrivateChannel channel = target.openPrivateChannel().complete();
+ if(channel != null){
+ EmbedBuilder builder = Ken.getInstance().getDefaultEmbed()
+ .setColor(Color.green)
+ .setTitle("**Unbanned**")
+ .appendDescription("You have been unbanned from "+guild.getName()+"\n")
+ .appendDescription("`By:` " + sender.getEffectiveName()+"\n")
+ .appendDescription("`Reason:` " + reason);
+ channel.sendMessageEmbeds(builder.build()).queue();
+ }
+ guild.unban(target).queue();
+ EmbedBuilder builder = Ken.getInstance().getDefaultEmbed()
+ .appendDescription(String.format("Unbanned %s for %s", target, reason));
+ return Response.success(builder.build());
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/moderation/UnMute.java b/src/main/java/net/tylermurphy/ken/command/moderation/UnMute.java
new file mode 100644
index 0000000..99ef6de
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/command/moderation/UnMute.java
@@ -0,0 +1,4 @@
+package net.tylermurphy.ken.command.moderation;
+
+public class UnMute {
+}
diff --git a/src/main/java/net/tylermurphy/ken/command/main/AddRole.java b/src/main/java/net/tylermurphy/ken/command/selfrole/AddRole.java
index 4aefa9a..cd8a7cd 100644
--- a/src/main/java/net/tylermurphy/ken/command/main/AddRole.java
+++ b/src/main/java/net/tylermurphy/ken/command/selfrole/AddRole.java
@@ -1,4 +1,4 @@
-package net.tylermurphy.ken.command.main;
+package net.tylermurphy.ken.command.selfrole;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
diff --git a/src/main/java/net/tylermurphy/ken/command/main/AddRolesPage.java b/src/main/java/net/tylermurphy/ken/command/selfrole/AddRolesPage.java
index b29daeb..b5719c6 100644
--- a/src/main/java/net/tylermurphy/ken/command/main/AddRolesPage.java
+++ b/src/main/java/net/tylermurphy/ken/command/selfrole/AddRolesPage.java
@@ -1,4 +1,4 @@
-package net.tylermurphy.ken.command.main;
+package net.tylermurphy.ken.command.selfrole;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
diff --git a/src/main/java/net/tylermurphy/ken/command/main/DeleteRole.java b/src/main/java/net/tylermurphy/ken/command/selfrole/DeleteRole.java
index 9d39a50..9d8b25f 100644
--- a/src/main/java/net/tylermurphy/ken/command/main/DeleteRole.java
+++ b/src/main/java/net/tylermurphy/ken/command/selfrole/DeleteRole.java
@@ -1,4 +1,4 @@
-package net.tylermurphy.ken.command.main;
+package net.tylermurphy.ken.command.selfrole;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
diff --git a/src/main/java/net/tylermurphy/ken/command/main/DeleteRolesPage.java b/src/main/java/net/tylermurphy/ken/command/selfrole/DeleteRolesPage.java
index 76128fd..cbea176 100644
--- a/src/main/java/net/tylermurphy/ken/command/main/DeleteRolesPage.java
+++ b/src/main/java/net/tylermurphy/ken/command/selfrole/DeleteRolesPage.java
@@ -1,4 +1,4 @@
-package net.tylermurphy.ken.command.main;
+package net.tylermurphy.ken.command.selfrole;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Guild;
diff --git a/src/main/java/net/tylermurphy/ken/command/main/Roles.java b/src/main/java/net/tylermurphy/ken/command/selfrole/Roles.java
index 0fc482b..238a8de 100644
--- a/src/main/java/net/tylermurphy/ken/command/main/Roles.java
+++ b/src/main/java/net/tylermurphy/ken/command/selfrole/Roles.java
@@ -1,4 +1,4 @@
-package net.tylermurphy.ken.command.main;
+package net.tylermurphy.ken.command.selfrole;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.*;
diff --git a/src/main/java/net/tylermurphy/ken/database/Database.java b/src/main/java/net/tylermurphy/ken/database/Database.java
index 8e209fb..2c46c68 100644
--- a/src/main/java/net/tylermurphy/ken/database/Database.java
+++ b/src/main/java/net/tylermurphy/ken/database/Database.java
@@ -13,6 +13,7 @@ public class Database {
private final DatabaseConnection connection;
private final SelfRoleTable selfRoleTable;
private final EconomyTable economyTable;
+ private final ModerationTable moderationTable;
public Database(){
if(Ken.getInstance().getConfig().getBoolean("database.sqlite")) {
@@ -22,6 +23,7 @@ public class Database {
}
selfRoleTable = new SelfRoleTable(this);
economyTable = new EconomyTable(this);
+ moderationTable = new ModerationTable(this);
}
public SelfRoleTable getSelfRoleData(){
@@ -32,6 +34,8 @@ public class Database {
return economyTable;
}
+ public ModerationTable getModerationTable() { return moderationTable; }
+
protected Connection connect() throws SQLException {
return connection.connect();
}
diff --git a/src/main/java/net/tylermurphy/ken/database/EconomyTable.java b/src/main/java/net/tylermurphy/ken/database/EconomyTable.java
index 589a204..9a716a5 100644
--- a/src/main/java/net/tylermurphy/ken/database/EconomyTable.java
+++ b/src/main/java/net/tylermurphy/ken/database/EconomyTable.java
@@ -55,7 +55,7 @@ public class EconomyTable {
}
}
- public JSONObject updateData(long guildId, long userId, String data) {
+ public JSONObject updateData(String data) {
JSONObject object = data == null ? new JSONObject() : new JSONObject(data);
if(!object.has("money")) object.put("money", 0);
if(!object.has("claim")) object.put("claim", 0L);
diff --git a/src/main/java/net/tylermurphy/ken/database/ModerationTable.java b/src/main/java/net/tylermurphy/ken/database/ModerationTable.java
new file mode 100644
index 0000000..9e0061d
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/database/ModerationTable.java
@@ -0,0 +1,84 @@
+package net.tylermurphy.ken.database;
+
+import net.tylermurphy.ken.Ken;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.sql.*;
+import java.util.function.Consumer;
+
+public class ModerationTable {
+
+ private final Database database;
+
+ public ModerationTable(Database database){
+
+ String sql = """
+ CREATE TABLE IF NOT EXISTS moderation_data (
+ guild_id BIGINT NOT NULL,
+ user_id BIGINT NOT NULL,
+ data TEXT NOT NULL,
+ until BIGINT NOT NULL,
+ PRIMARY KEY (guild_id,user_id)
+ );""";
+
+ try(Connection connection = database.connect(); Statement statement = connection.createStatement()) {
+ statement.executeUpdate(sql);
+ } catch (SQLException e) {
+ Ken.getInstance().getLogger().error("SQL Error: " + e.getMessage());
+ }
+
+ this.database = database;
+ }
+
+ public String getData(long guildId, long userId){
+ String sql = "SELECT * FROM moderation_data WHERE guild_id=? AND user_id=?";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setLong(1, guildId);
+ statement.setLong(2, userId);
+ ResultSet rs = statement.executeQuery();
+ if(rs.next()) return rs.getString("data");
+ else return null;
+ } catch (SQLException e) {
+ Ken.getInstance().getLogger().error("SQL Error: " + e.getMessage());
+ return null;
+ }
+ }
+
+ public boolean setData(long guildId, long userId, String data) {
+ String sql = "INSERT OR REPLACE INTO moderation_data (guild_id, user_id, data, until) VALUES(?,?,?,?)";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setLong(1, guildId);
+ statement.setLong(2, userId);
+ statement.setString(3, data);
+ statement.setLong(4, new JSONObject(data).getJSONObject("status").getLong("until"));
+ return statement.executeUpdate() != 0;
+ } catch (SQLException e) {
+ Ken.getInstance().getLogger().error("SQL Error: " + e.getMessage());
+ return false;
+ }
+ }
+
+ public void callOverdue(long date, Consumer<String[]> callback){
+ String sql = "SELECT * FROM moderation_data WHERE until < ? AND until > 0";
+ try(Connection connection = database.connect(); PreparedStatement statement = connection.prepareStatement(sql)) {
+ statement.setLong(1, date);
+ ResultSet rs = statement.executeQuery();
+ while (rs.next()){
+ String[] data = {String.valueOf(rs.getLong("guild_id")), String.valueOf(rs.getLong("user_id")), rs.getString("data")};
+ callback.accept(data);
+ }
+ } catch (SQLException e) {
+ Ken.getInstance().getLogger().error("SQL Error: " + e.getMessage());
+ }
+ }
+
+ public JSONObject updateData(String data) {
+ JSONObject object = data == null ? new JSONObject() : new JSONObject(data);
+ if(!object.has("history")) object.put("history", new JSONArray());
+ if(!object.has("roles")) object.put("roles", new JSONArray());
+ if(!object.has("status")) object.put("status", new JSONObject().put("type","None").put("until", 0L).put("reason",""));
+ return object;
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/image/GifFactory.java b/src/main/java/net/tylermurphy/ken/image/GifFactory.java
deleted file mode 100644
index 193dc41..0000000
--- a/src/main/java/net/tylermurphy/ken/image/GifFactory.java
+++ /dev/null
@@ -1,131 +0,0 @@
-package net.tylermurphy.ken.image;
-
-import java.awt.image.BufferedImage;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.Iterator;
-
-import javax.imageio.IIOException;
-import javax.imageio.IIOImage;
-import javax.imageio.ImageIO;
-import javax.imageio.ImageTypeSpecifier;
-import javax.imageio.ImageWriter;
-import javax.imageio.metadata.IIOInvalidTreeException;
-import javax.imageio.metadata.IIOMetadata;
-import javax.imageio.metadata.IIOMetadataNode;
-import javax.imageio.stream.ImageOutputStream;
-
-import net.dv8tion.jda.api.entities.User;
-
-public class GifFactory {
-
- public static byte[] generateEjectGif(User u) {
- String message;
- if(Math.random() > .5)
- message = u.getName() + " was An Impostor";
- else
- message = u.getName() + " was not An Impostor";
-
- BufferedImage space1 = ImageFactory.getImageFromURL("https://cdn.tylermurphy.net/ken/space.jpg");
- BufferedImage avatar = ImageFactory.getImageFromURL(u.getAvatarUrl());
-
- BufferedImage[] frames = new BufferedImage[30];
- for(int i = 0; i < 30; i++) {
- frames[i] = ImageFactory.GenerateEjectFrame(message, Math.min(i, 23), 24, space1, avatar);
- }
-
- try {
- return generateFromBI(frames);
- } catch(Exception e) {
- e.printStackTrace();
- return null;
- }
-
- }
-
- private static byte[] generateFromBI(BufferedImage[] images)
- throws IIOException, IOException
- {
- System.out.println('e');
- ImageWriter gifWriter = getWriter();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ImageOutputStream ios = ImageIO.createImageOutputStream(baos);
- IIOMetadata metadata = getMetadata(gifWriter);
-
- gifWriter.setOutput(ios);
- gifWriter.prepareWriteSequence(null);
- for (BufferedImage img: images)
- {
- IIOImage temp = new IIOImage(img, null, metadata);
- gifWriter.writeToSequence(temp, null);
- }
-
- gifWriter.endWriteSequence();
- ios.close();
- System.out.println('e');
- return baos.toByteArray();
-
- }
-
- private static ImageWriter getWriter() throws IIOException
- {
- Iterator<ImageWriter> itr = ImageIO.getImageWritersByFormatName("gif");
- if(itr.hasNext())
- return itr.next();
-
- throw new IIOException("GIF writer doesn't exist on this JVM!");
- }
-
- private static IIOMetadata getMetadata(ImageWriter writer)
- throws IIOInvalidTreeException
- {
- ImageTypeSpecifier img_type = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
- IIOMetadata metadata = writer.getDefaultImageMetadata(img_type, null);
- String native_format = metadata.getNativeMetadataFormatName();
- IIOMetadataNode node_tree = (IIOMetadataNode)metadata.getAsTree(native_format);
-
- IIOMetadataNode graphics_node = getNode("GraphicControlExtension", node_tree);
- graphics_node.setAttribute("delayTime", String.valueOf(8));
- graphics_node.setAttribute("disposalMethod", "none");
- graphics_node.setAttribute("userInputFlag", "FALSE");
-
- makeLoopy(node_tree);
-
- metadata.setFromTree(native_format, node_tree);
-
- return metadata;
- }
-
- private static void makeLoopy(IIOMetadataNode root)
- {
- IIOMetadataNode app_extensions = getNode("ApplicationExtensions", root);
- IIOMetadataNode app_node = getNode("ApplicationExtension", app_extensions);
-
- app_node.setAttribute("applicationID", "NETSCAPE");
- app_node.setAttribute("authenticationCode", "2.0");
- app_node.setUserObject(new byte[]{ 0x1, (byte) (0), (byte) ((0) & 0xFF)});
-
- app_extensions.appendChild(app_node);
- root.appendChild(app_extensions);
- }
-
- private static IIOMetadataNode getNode(String node_name, IIOMetadataNode root)
- {
- IIOMetadataNode node;
-
- for (int i = 0; i < root.getLength(); i++)
- {
- if(root.item(i).getNodeName().compareToIgnoreCase(node_name) == 0)
- {
- node = (IIOMetadataNode) root.item(i);
- return node;
- }
- }
-
- node = new IIOMetadataNode(node_name);
- root.appendChild(node);
-
- return node;
- }
-
-} \ No newline at end of file
diff --git a/src/main/java/net/tylermurphy/ken/image/ImageFactory.java b/src/main/java/net/tylermurphy/ken/image/ImageFactory.java
deleted file mode 100644
index e70156c..0000000
--- a/src/main/java/net/tylermurphy/ken/image/ImageFactory.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package net.tylermurphy.ken.image;
-
-import java.awt.AlphaComposite;
-
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.geom.RoundRectangle2D;
-import java.awt.image.BufferedImage;
-import java.net.HttpURLConnection;
-import java.net.URL;
-
-import javax.imageio.ImageIO;
-
-public class ImageFactory {
-
- public static BufferedImage GenerateEjectFrame(String message, int frame, float totalFrames, BufferedImage space1, BufferedImage avatar) {
- try {
- int w = 600;
- int h = 400;
- BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
-
- Graphics2D g2d = img.createGraphics();
- g2d.drawImage(space1, (int) (-100+100/totalFrames*frame), 0, (int)(w*1.5), (int)(h*1.5), null);
-
- BufferedImage ravater = rotate(avatar, 180/24*frame);
- g2d.drawImage(ravater, (w+2*avatar.getWidth()+100)/24*frame-avatar.getWidth()-50, h/2-avatar.getHeight()/2, 150, 150, null);
-
- if(frame > 12) {
- int length = (message.length() * (frame-11)/12);
- message = message.substring(0, length);
-
- g2d.setFont(new Font("Dialog", Font.BOLD, 25));
- FontMetrics fm = g2d.getFontMetrics();
- int x = w/2 - fm.stringWidth(message)/2;
- int y = h/2;
- g2d.drawString(message, x, y);
- }
-
- return img;
-
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
- public static BufferedImage rotate(BufferedImage bimg, double angle) {
-
- int w = bimg.getWidth()+50;
- int h = bimg.getHeight()+50;
-
- BufferedImage rotated = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
- Graphics2D graphic = rotated.createGraphics();
- graphic.rotate(Math.toRadians(angle), w/2, h/2);
- graphic.drawImage(bimg, null, w/2-bimg.getWidth()/2, h/2-bimg.getHeight()/2);
- graphic.dispose();
- return rotated;
- }
-
- public static BufferedImage getImageFromURL(String link) {
- try {
- final URL url = new URL(link);
- final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0");
- return ImageIO.read(connection.getInputStream());
- } catch (Exception e) {
- e.printStackTrace();
- return null;
- }
- }
-
-} \ No newline at end of file
diff --git a/src/main/java/net/tylermurphy/ken/util/Checks.java b/src/main/java/net/tylermurphy/ken/util/Checks.java
new file mode 100644
index 0000000..7c3daa1
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/util/Checks.java
@@ -0,0 +1,33 @@
+package net.tylermurphy.ken.util;
+
+import net.dv8tion.jda.api.entities.Member;
+import net.dv8tion.jda.api.entities.Role;
+
+import java.util.List;
+
+public class Checks {
+
+ public static Role getHighestRole(Member member) {
+ if(member == null) {
+ return null;
+ }
+ List<Role> roles = member.getRoles();
+ if (roles.isEmpty()) {
+ return null;
+ }
+ return roles.stream().min((first, second) -> {
+ if (first.getPosition() == second.getPosition()) {
+ return 0;
+ }
+ return first.getPosition() > second.getPosition() ? -1 : 1;
+ }).orElse(null);
+ }
+
+ public static boolean getRolePermission(Role first, Role second){
+ if(first == null) return false;
+ if(second == null) return true;
+ if(!first.getGuild().equals(second.getGuild())) return false;
+ return first.getPosition() > second.getPosition();
+ }
+
+}
diff --git a/src/main/java/net/tylermurphy/ken/util/GifSequenceWriter.java b/src/main/java/net/tylermurphy/ken/util/GifSequenceWriter.java
new file mode 100644
index 0000000..f5860dc
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/util/GifSequenceWriter.java
@@ -0,0 +1,75 @@
+package net.tylermurphy.ken.util;
+
+import javax.imageio.*;
+import javax.imageio.metadata.IIOInvalidTreeException;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import javax.imageio.stream.ImageOutputStream;
+import java.awt.image.RenderedImage;
+import java.io.IOException;
+
+public class GifSequenceWriter {
+
+ protected ImageWriter writer;
+ protected ImageWriteParam params;
+ protected IIOMetadata metadata;
+
+ public GifSequenceWriter(ImageOutputStream out, int imageType, int delay, boolean loop) throws IOException {
+ writer = ImageIO.getImageWritersBySuffix("gif").next();
+ params = writer.getDefaultWriteParam();
+
+ ImageTypeSpecifier imageTypeSpecifier = ImageTypeSpecifier.createFromBufferedImageType(imageType);
+ metadata = writer.getDefaultImageMetadata(imageTypeSpecifier, params);
+
+ configureRootMetadata(delay, loop);
+
+ writer.setOutput(out);
+ writer.prepareWriteSequence(null);
+ }
+
+ private void configureRootMetadata(int delay, boolean loop) throws IIOInvalidTreeException {
+ String metaFormatName = metadata.getNativeMetadataFormatName();
+ IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(metaFormatName);
+
+ IIOMetadataNode graphicsControlExtensionNode = getNode(root, "GraphicControlExtension");
+ graphicsControlExtensionNode.setAttribute("disposalMethod", "restoreToBackgroundColor");
+ graphicsControlExtensionNode.setAttribute("userInputFlag", "FALSE");
+ graphicsControlExtensionNode.setAttribute("transparentColorFlag", "TRUE");
+ graphicsControlExtensionNode.setAttribute("delayTime", Integer.toString(delay / 10));
+ graphicsControlExtensionNode.setAttribute("transparentColorIndex", "3");
+
+ IIOMetadataNode commentsNode = getNode(root, "CommentExtensions");
+ commentsNode.setAttribute("CommentExtension", "Created by: https://memorynotfound.com");
+
+ IIOMetadataNode appExtensionsNode = getNode(root, "ApplicationExtensions");
+ IIOMetadataNode child = new IIOMetadataNode("ApplicationExtension");
+ child.setAttribute("applicationID", "NETSCAPE");
+ child.setAttribute("authenticationCode", "2.0");
+
+ int loopContinuously = loop ? 0 : 1;
+ child.setUserObject(new byte[]{ 0x1, (byte) (loopContinuously & 0xFF), (byte) ((loopContinuously >> 8) & 0xFF)});
+ appExtensionsNode.appendChild(child);
+ metadata.setFromTree(metaFormatName, root);
+ }
+
+ private static IIOMetadataNode getNode(IIOMetadataNode rootNode, String nodeName){
+ int nNodes = rootNode.getLength();
+ for (int i = 0; i < nNodes; i++){
+ if (rootNode.item(i).getNodeName().equalsIgnoreCase(nodeName)){
+ return (IIOMetadataNode) rootNode.item(i);
+ }
+ }
+ IIOMetadataNode node = new IIOMetadataNode(nodeName);
+ rootNode.appendChild(node);
+ return(node);
+ }
+
+ public void writeToSequence(RenderedImage img) throws IOException {
+ writer.writeToSequence(new IIOImage(img, null, metadata), params);
+ }
+
+ public void close() throws IOException {
+ writer.endWriteSequence();
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/net/tylermurphy/ken/util/ImageFetcher.java b/src/main/java/net/tylermurphy/ken/util/ImageFetcher.java
new file mode 100644
index 0000000..875475b
--- /dev/null
+++ b/src/main/java/net/tylermurphy/ken/util/ImageFetcher.java
@@ -0,0 +1,30 @@
+package net.tylermurphy.ken.util;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ImageFetcher {
+
+ private final Map<String,BufferedImage> CACHE = new HashMap<>();
+
+ public BufferedImage getImage(String url, boolean cache) throws IOException {
+ if(CACHE.containsKey(url)) return CACHE.get(url);
+ HttpURLConnection connection = getConnection(url);
+ BufferedImage image = ImageIO.read(connection.getInputStream());
+ if(cache && image != null) CACHE.put(url, image);
+ return image;
+ }
+
+ private HttpURLConnection getConnection(String link) throws IOException {
+ final URL url = new URL(link);
+ final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:104.0) Gecko/20100101 Firefox/104.0");
+ return connection;
+ }
+
+}