diff --git a/.gitignore b/.gitignore
index 8079693..29315ee 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
target/
+out/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
@@ -37,6 +38,15 @@ build/
### Mac OS ###
.DS_Store
+### Other Stuff ###
+config*.json
+!config.example.json
+!**/resources/config.schema.json
+
+### why would this not be here already ###
+/logs/
+
/tmp/
*.png
-*.pgn
\ No newline at end of file
+*.pgn
+*.gif
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8ccaa52..9235429 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,5 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/moe/wah/Main.java b/src/main/java/moe/wah/Main.java
deleted file mode 100644
index b5de26e..0000000
--- a/src/main/java/moe/wah/Main.java
+++ /dev/null
@@ -1,130 +0,0 @@
-package moe.wah;
-
-import com.github.alexandreroman.chessimage.ChessRenderer;
-import com.github.alexandreroman.chessimage.ChessThemeLibrary;
-import com.github.bhlangonijr.chesslib.Board;
-import com.github.bhlangonijr.chesslib.game.Game;
-import com.github.bhlangonijr.chesslib.move.Move;
-import com.github.bhlangonijr.chesslib.move.MoveList;
-import com.github.bhlangonijr.chesslib.pgn.PgnHolder;
-import com.sksamuel.scrimage.ImmutableImage;
-import com.sksamuel.scrimage.nio.StreamingGifWriter;
-import com.sksamuel.scrimage.nio.StreamingGifWriter.GifStream;
-
-import javax.imageio.ImageIO;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class Main {
- public static void main(String[] args) throws Exception {
- Files.createDirectories(Paths.get("./temp/"));
- // Load PGN from file
- PgnHolder pgn = new PgnHolder("game.pgn");
- pgnToGif(pgn);
- }
- private static void pgnToGif(PgnHolder pgn) throws Exception {
- ChessRenderer renderer = new ChessRenderer(ChessThemeLibrary.GREEN_THEME, 60);
- Game game = pgnToGame(pgn);
- String boardID = String.valueOf(Instant.now().toEpochMilli());
- game.loadMoveText();
- System.out.println(game.getTermination());
-
- MoveList moves = game.getHalfMoves();
- Board board = new Board();
- int index = 1;
- for (Move move : moves) {
- board.doMove(move);
- renderBoard(board, renderer, boardID, index);
-
- // Add 5 extra frames of final board
- if (index == moves.size()) {
- for (int x = 0; x < 5; x++) {
- index++;
- renderBoard(board, renderer, boardID, index);
- }
- }
- index++;
- }
-
- System.out.println("Rendered " + moves.size() + " moves");
-
- boardToGif(boardID);
- }
-
- private static void renderBoard(Board board, ChessRenderer renderer, String boardID, int index) {
- String index_padded = String.format("%03d", index);
- File targetFile = new File("./temp/", "board_" + boardID + "_" + index_padded + ".png");
- try (FileOutputStream out = new FileOutputStream(targetFile)) {
- renderer.render(board.getFen(), out);
- } catch (IOException e) {
- System.out.println("Error rendering move " + index_padded + ", " + e.getMessage());
- }
- }
-
- private static Game pgnToGame(PgnHolder pgn) throws Exception {
- pgn.loadPgn();
- return pgn.getGames().getFirst();
- }
-
- /**
- * Converts a chess board to a GIF
- * Output file is board_x.gif where x is the boardID
- * @param boardID ID of the board (UNIX epoch)
- */
- public static void boardToGif(String boardID) throws Exception {
- List boardFiles = getBoardFiles(new File("./temp/"), boardID);
- if (boardFiles.isEmpty()) {
- throw new RuntimeException("No PNG files found for board with ID: " + boardID);
- }
- List images = new ArrayList<>();
- for (File file : boardFiles) {
- BufferedImage image = ImageIO.read(file);
- images.add(image);
- }
- System.out.println("There are " + (long) images.size() + " frames for board " + boardID);
- String gif_name = "board_" + boardID + ".gif";
- StreamingGifWriter writer = new StreamingGifWriter(Duration.ofMillis(500), true, false);
- GifStream gif = writer.prepareStream(gif_name, BufferedImage.TYPE_INT_ARGB);
- for (BufferedImage image : images) {
- gif.writeFrame(ImmutableImage.fromAwt(image));
- }
- gif.close();
- System.out.println("Rendered " + gif_name);
- for (File file : boardFiles) {
- boolean delete_success = file.delete();
- if (!delete_success) {
- System.out.println("Failed to delete " + file.getName() + "!");
- }
- }
- System.out.println("Removed " + boardFiles.size() + " temp files");
- }
-
- /**
- * Gets the PNG files for the specified board
- * @param directory Directory to search
- * @param boardID Board ID (UNIX epoch)
- * @return A list of board Files
- */
- private static List getBoardFiles(File directory, String boardID) {
- List boardFiles = new ArrayList<>();
- File[] files = directory.listFiles();
- assert files != null;
- Arrays.sort(files);
- for (File file : files) {
- if (file.isFile() && file.getName().toLowerCase().endsWith(".png")
- && file.getName().startsWith("board_" + boardID)) {
- boardFiles.add(file);
- }
- }
- return boardFiles;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/moe/wah/pgnator/Config.java b/src/main/java/moe/wah/pgnator/Config.java
new file mode 100644
index 0000000..104b8dc
--- /dev/null
+++ b/src/main/java/moe/wah/pgnator/Config.java
@@ -0,0 +1,36 @@
+package moe.wah.pgnator;
+
+import com.freya02.botcommands.api.Logging;
+import com.google.gson.Gson;
+import org.slf4j.Logger;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+//You can add more fields in this class, if your input json matches the structure
+//You will need a valid config.json in the resources folder for this to work
+public class Config {
+ @SuppressWarnings("unused") private String token;
+
+ /**
+ * Returns the configuration object for this bot
+ *
+ * @return The config
+ * @throws IOException if the config JSON could not be read
+ */
+ public static Config readConfig() throws IOException {
+ Logger log = Logging.getLogger();
+ try (InputStream in = new FileInputStream("./config.json")) {
+ Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8);
+ log.info("Loaded config");
+ return new Gson().fromJson(reader, Config.class);
+ } catch (IOException | NullPointerException e) {
+ log.error("Failed to load config.json, does the file exist?");
+ throw new IOException(e);
+ }
+ }
+
+ public String getToken() {
+ return token;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/moe/wah/pgnator/PGNator.java b/src/main/java/moe/wah/pgnator/PGNator.java
new file mode 100644
index 0000000..881904f
--- /dev/null
+++ b/src/main/java/moe/wah/pgnator/PGNator.java
@@ -0,0 +1,59 @@
+package moe.wah.pgnator;
+
+import com.freya02.botcommands.api.CommandsBuilder;
+import com.freya02.botcommands.api.Logging;
+import net.dv8tion.jda.api.JDA;
+import net.dv8tion.jda.api.JDABuilder;
+import net.dv8tion.jda.api.entities.*;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+
+public class PGNator {
+ private static final Logger log = Logging.getLogger();
+ private static JDA jda;
+ private static Config config;
+
+ private PGNator(JDA jda, Config config) {
+ PGNator.jda = jda;
+ PGNator.config = config;
+ }
+
+ public static JDA getJDA() {
+ return jda;
+ }
+ public static Logger getLogger() {
+ return log;
+ }
+
+ public static void start() throws IOException, InterruptedException {
+ config = Config.readConfig();
+
+ final JDA jda = JDABuilder.createLight(config.getToken())
+ .setActivity(Activity.playing("Chess"))
+ .build()
+ .awaitReady();
+
+ // Print some information about the bot
+ log.info("Bot connected as {}", jda.getSelfUser().getAsTag());
+ log.info("The bot is present in the following guilds:");
+ for (Guild guild : jda.getGuildCache()) {
+ log.info("\t- {} ({})", guild.getName(), guild.getId());
+ }
+
+ new PGNator(jda, config);
+ }
+
+ public static void main(String[] args) {
+ try {
+ start();
+ jda = getJDA();
+ CommandsBuilder.newBuilder(437970062922612737L)
+ .textCommandBuilder(textCommandsBuilder -> textCommandsBuilder.disableHelpCommand(true))
+ .build(jda, "moe.wah.pgnator.commands"); //Registering listeners is taken care of by the lib
+ } catch (Exception e) {
+ log.error("Failed to start the bot", e);
+ System.exit(-1);
+ }
+ }
+}
diff --git a/src/main/java/moe/wah/pgnator/commands/Pgn2Gif.java b/src/main/java/moe/wah/pgnator/commands/Pgn2Gif.java
new file mode 100644
index 0000000..6634639
--- /dev/null
+++ b/src/main/java/moe/wah/pgnator/commands/Pgn2Gif.java
@@ -0,0 +1,180 @@
+package moe.wah.pgnator.commands;
+
+import com.freya02.botcommands.api.annotations.BotPermissions;
+import com.freya02.botcommands.api.annotations.CommandMarker;
+import com.freya02.botcommands.api.annotations.UserPermissions;
+import com.freya02.botcommands.api.application.ApplicationCommand;
+import com.freya02.botcommands.api.application.annotations.AppOption;
+import com.freya02.botcommands.api.application.slash.GuildSlashEvent;
+import com.freya02.botcommands.api.application.slash.annotations.JDASlashCommand;
+import com.github.alexandreroman.chessimage.ChessRenderer;
+import com.github.alexandreroman.chessimage.ChessThemeLibrary;
+import com.github.bhlangonijr.chesslib.Board;
+import com.github.bhlangonijr.chesslib.game.Game;
+import com.github.bhlangonijr.chesslib.move.Move;
+import com.github.bhlangonijr.chesslib.move.MoveList;
+import com.github.bhlangonijr.chesslib.pgn.PgnHolder;
+import com.sksamuel.scrimage.ImmutableImage;
+import com.sksamuel.scrimage.nio.StreamingGifWriter;
+import moe.wah.pgnator.PGNator;
+import net.dv8tion.jda.api.Permission;
+import net.dv8tion.jda.api.entities.Message;
+import net.dv8tion.jda.api.utils.FileUpload;
+import org.slf4j.Logger;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@CommandMarker
+@BotPermissions(Permission.MESSAGE_SEND_POLLS)
+@UserPermissions(Permission.MESSAGE_MANAGE)
+public class Pgn2Gif extends ApplicationCommand {
+ @JDASlashCommand(
+ name = "pgn2gif",
+ description = "Turns a PGN file into a GIF!"
+ )
+ public void pgn2gif(GuildSlashEvent event,
+ @AppOption(name = "pgn_file", description = "PGN file") Message.Attachment attachment) throws Exception {
+ Logger log = PGNator.getLogger();
+ event.deferReply().queue();
+ Files.createDirectories(Paths.get("./temp/pgn/"));
+ File pgnFile = new File("./temp/pgn/" + attachment.getFileName());
+ attachment.getProxy().downloadToFile(pgnFile).thenAccept(file -> {
+ File outputFile;
+ log.info("Wrote {}", file.getAbsolutePath());
+ try {
+ Files.createDirectories(Paths.get("./temp/outputs"));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ // Load PGN from file
+ PgnHolder pgn = new PgnHolder("./temp/pgn/" + attachment.getFileName());
+ try {
+ outputFile = pgnToGif(pgn);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ //event.getHook().sendMessageFormat("Filename: %s\nSize: %s\nOutput: %s", attachment.getFileName(), String.valueOf(attachment.getSize()), outputFilename).queue();
+ FileUpload fileUpload = FileUpload.fromData(outputFile);
+ event.getHook().sendFiles(fileUpload).queue();
+ log.info("Deleting output gif {}", outputFile.getName());
+ if (!outputFile.delete()) {
+ log.error("Failed to delete {}!", outputFile.getName());
+ }
+ log.info("Deleting PGN {}", pgnFile.getName());
+ if (!pgnFile.delete()) {
+ log.error("Failed to delete {}!", pgnFile.getName());
+ }
+
+ });
+ }
+
+ private static File pgnToGif(PgnHolder pgn) throws Exception {
+ ChessRenderer renderer = new ChessRenderer(ChessThemeLibrary.GREEN_THEME, 60);
+ Game game = pgnToGame(pgn);
+ String boardID = String.valueOf(Instant.now().toEpochMilli());
+ game.loadMoveText();
+
+ MoveList moves = game.getHalfMoves();
+ Board board = new Board();
+ int index = 1;
+ for (Move move : moves) {
+ board.doMove(move);
+ renderBoard(board, renderer, boardID, index);
+
+ // Add 5 extra frames of final board
+ if (index == moves.size()) {
+ for (int x = 0; x < 5; x++) {
+ index++;
+ renderBoard(board, renderer, boardID, index);
+ }
+ }
+ index++;
+ }
+
+ PGNator.getLogger().info("Rendered {} moves", moves.size());
+
+ return boardToGif(boardID);
+ }
+
+ private static void renderBoard(Board board, ChessRenderer renderer, String boardID, int index) {
+ String index_padded = String.format("%03d", index);
+ File targetFile = new File("./temp/outputs", "board_" + boardID + "_" + index_padded + ".png");
+ try (FileOutputStream out = new FileOutputStream(targetFile)) {
+ renderer.render(board.getFen(), out);
+ } catch (IOException e) {
+ PGNator.getLogger().error("Error rendering move {}, {}", index_padded, e.getMessage());
+ }
+ }
+
+ private static Game pgnToGame(PgnHolder pgn) throws Exception {
+ pgn.loadPgn();
+ return pgn.getGames().getFirst();
+ }
+
+ /**
+ * Converts a chess board to a GIF
+ * Output file is board_x.gif where x is the boardID
+ *
+ * @param boardID ID of the board (UNIX epoch)
+ */
+ public static File boardToGif(String boardID) throws Exception {
+ List boardFiles = getBoardFiles(new File("./temp/outputs/"), boardID);
+ if (boardFiles.isEmpty()) {
+ throw new RuntimeException("No PNG files found for board with ID: " + boardID);
+ }
+ List images = new ArrayList<>();
+ for (File file : boardFiles) {
+ BufferedImage image = ImageIO.read(file);
+ images.add(image);
+ }
+ PGNator.getLogger().debug("There are {} frames for board {}", (long) images.size(), boardID);
+ String gif_name = "./temp/outputs/board_" + boardID + ".gif";
+ StreamingGifWriter writer = new StreamingGifWriter(Duration.ofMillis(500), true, false);
+ StreamingGifWriter.GifStream gif = writer.prepareStream(gif_name, BufferedImage.TYPE_INT_ARGB);
+ for (BufferedImage image : images) {
+ gif.writeFrame(ImmutableImage.fromAwt(image));
+ }
+ gif.close();
+ PGNator.getLogger().info("Rendered {}", gif_name);
+ for (File file : boardFiles) {
+ boolean delete_success = file.delete();
+ if (!delete_success) {
+ PGNator.getLogger().error("Failed to delete {}!", file.getName());
+ }
+ }
+ PGNator.getLogger().info("Removed {} temp files", boardFiles.size());
+ return new File(gif_name);
+ }
+
+ /**
+ * Gets the PNG files for the specified board
+ * @param directory Directory to search
+ * @param boardID Board ID (UNIX epoch)
+ * @return A list of board Files
+ */
+ private static List getBoardFiles(File directory, String boardID) {
+ List boardFiles = new ArrayList<>();
+ File[] files = directory.listFiles();
+ assert files != null;
+ Arrays.sort(files);
+ for (File file : files) {
+ if (file.isFile() && file.getName().toLowerCase().endsWith(".png")
+ && file.getName().startsWith("board_" + boardID)) {
+ boardFiles.add(file);
+ }
+ }
+ return boardFiles;
+ }
+}
diff --git a/src/main/java/moe/wah/pgnator/commands/Ping.java b/src/main/java/moe/wah/pgnator/commands/Ping.java
new file mode 100644
index 0000000..5e14bf2
--- /dev/null
+++ b/src/main/java/moe/wah/pgnator/commands/Ping.java
@@ -0,0 +1,27 @@
+package moe.wah.pgnator.commands;
+
+import com.freya02.botcommands.api.annotations.CommandMarker;
+import com.freya02.botcommands.api.application.ApplicationCommand;
+import com.freya02.botcommands.api.application.slash.GuildSlashEvent;
+import com.freya02.botcommands.api.application.slash.annotations.JDASlashCommand;
+import com.freya02.botcommands.api.prefixed.annotations.Category;
+import com.freya02.botcommands.api.prefixed.annotations.Description;
+
+@CommandMarker
+@Category("Utils")
+@Description("Pong!")
+public class Ping extends ApplicationCommand {
+ @JDASlashCommand(
+ name = "ping",
+ description = "Pong!"
+ )
+ public void onPing(GuildSlashEvent event) {
+ event.deferReply().queue();
+
+ final long gatewayPing = event.getJDA().getGatewayPing();
+ event.getJDA().getRestPing()
+ .queue(l -> event.getHook()
+ .sendMessageFormat("Gateway ping: **%d ms**\nRest ping: **%d ms**", gatewayPing, l)
+ .queue());
+ }
+}