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()); + } +}