From 78828ac9fab80b879cae8f927c178b24517f8cec Mon Sep 17 00:00:00 2001 From: EpicKnarvik97 Date: Thu, 26 Aug 2021 20:18:37 +0200 Subject: [PATCH] First commit. Rewrite started --- pom.xml | 81 + .../BooksWithoutBorders.java | 1794 +++++++++++++++++ .../BooksWithoutBordersListener.java | 432 ++++ .../bookswithoutborders/GenenCrypt.java | 170 ++ .../SubstitutionCipher.java | 87 + src/main/resources/plugin.yml | 81 + 6 files changed, 2645 insertions(+) create mode 100644 pom.xml create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBordersListener.java create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/GenenCrypt.java create mode 100644 src/main/java/net/knarcraft/bookswithoutborders/SubstitutionCipher.java create mode 100644 src/main/resources/plugin.yml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..5cec7ed --- /dev/null +++ b/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + net.knarcraft.bookswithoutborders + books-without-borders + 1.0-SNAPSHOT + jar + + Books Without Borders + + A continuation of the original Books Without Borders + + 1.8 + UTF-8 + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + false + + + + + + + + src/main/resources + true + + + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + jitpack.io + https://jitpack.io + + + + + + org.spigotmc + spigot-api + 1.17.1-R0.1-SNAPSHOT + provided + + + com.github.MilkBowl + VaultAPI + 1.7 + provided + + + diff --git a/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java new file mode 100644 index 0000000..35d4dca --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java @@ -0,0 +1,1794 @@ +package net.knarcraft.bookswithoutborders; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import net.milkbowl.vault.economy.Economy; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.java.JavaPlugin; + +public class BooksWithoutBorders extends JavaPlugin { + protected static int dlimit; + //The separating string between the book title and the book author + protected static String titleAuthorSeparator; + protected static String loreSep; + protected static final String SLASH = System.getProperty("file.separator"); + protected static List firstBooks = new ArrayList<>(); + protected static String fBMessage; + protected static List existingPlayers; + protected static Economy eco; + protected static Material bookPriceType = null; + protected static double bookPriceQuantity; + protected static boolean authorOnlyCopy, useYml, adminDecrypt; + protected static ItemFactory iF; + protected static final Map> loadList = new HashMap<>(); + protected static BooksWithoutBorders bwb; + protected static final BooksWithoutBordersListener bL = new BooksWithoutBordersListener(); + protected static ConsoleCommandSender consoleSender; + + @Override + public void onEnable() { + bwb = this; + consoleSender = this.getServer().getConsoleSender(); + + if (SLASH != null && init()) { + this.getServer().getPluginManager().registerEvents(bL, this); + } else { + this.getPluginLoader().disablePlugin(this); + } + } + + protected boolean init() { + try //initializes Item Factory + { + iF = this.getServer().getItemFactory(); + } catch (java.lang.NoSuchMethodError nsmE) { + consoleSender.sendMessage(ChatColor.RED + "Warning! [BooksWithoutBorders] failed to initialize!"); + consoleSender.sendMessage(ChatColor.RED + "Please confirm the correct version of [BooksWithoutBorders] is"); + consoleSender.sendMessage(ChatColor.RED + "being run for this version of bukkit!"); + return false; + } + + //Pulls config values + if (!loadConfig()) { + return false; + } + + this.getConfig().set("Options.Save_Books_in_Yaml_Format", useYml); + this.getConfig().set("Options.Max_Number_of_Duplicates", dlimit); + this.getConfig().set("Options.Title-Author_Separator", titleAuthorSeparator); + this.getConfig().set("Options.Lore_line_separator", loreSep); + this.getConfig().set("Options.Books_for_new_players", firstBooks); + this.getConfig().set("Options.Message_for_new_players", fBMessage); + if (bookPriceType != null) { + if (bookPriceType != Material.AIR) + this.getConfig().set("Options.Price_to_create_book.Item_type", bookPriceType.toString()); + else + this.getConfig().set("Options.Price_to_create_book.Item_type", "Economy"); + } else + this.getConfig().set("Options.Price_to_create_book.Item_type", "Item type name"); + this.getConfig().set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity); + this.getConfig().set("Options.Admin_Auto_Decrypt", adminDecrypt); + this.getConfig().set("Options.Author_Only_Copy", authorOnlyCopy); + + //Handles old book and quill settings + if (this.getConfig().contains("Options.Require_book_and_quill_to_create_book")) { + consoleSender.sendMessage(ChatColor.GREEN + "[BooksWithoutBorders] Found old config setting \"Require_book_and_quill_to_create_book\""); + consoleSender.sendMessage(ChatColor.GREEN + "Updating to \"Price_to_create_book\" settings"); + + if (this.getConfig().getBoolean("Options.Require_book_and_quill_to_create_book")) { + bookPriceType = Material.WRITABLE_BOOK; + bookPriceQuantity = 1; + this.getConfig().set("Options.Price_to_create_book.Item_type", bookPriceType.toString()); + this.getConfig().set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity); + } + + this.getConfig().set("Options.Require_book_and_quill_to_create_book", null); + } + + this.saveConfig(); + + File fTest = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH); + File efTest = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + "Encrypted" + SLASH); + + if (!fTest.exists()) { + try { + if (!fTest.mkdir()) { + consoleSender.sendMessage(ChatColor.RED + "Saving failed! Aborting..."); + return false; + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + if (!efTest.exists()) { + try { + if (!efTest.mkdir()) { + consoleSender.sendMessage(ChatColor.RED + "Saving failed! Aborting..."); + return false; + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + return loadExistingPlayers(); + } + + protected boolean loadConfig() { + this.reloadConfig(); + try { + useYml = this.getConfig().getBoolean("Options.Save_Books_in_Yaml_Format", true); + dlimit = this.getConfig().getInt("Options.Max_Number_of_Duplicates", 5); + titleAuthorSeparator = this.getConfig().getString("Options.Title-Author_Separator", ","); + loreSep = this.getConfig().getString("Options.Lore_line_separator", "~"); + adminDecrypt = this.getConfig().getBoolean("Options.Admin_Auto_Decrypt", false); + authorOnlyCopy = this.getConfig().getBoolean("Options.Author_Only_Copy", false); + + firstBooks = this.getConfig().getStringList("Options.Books_for_new_players"); + if (this.getConfig().contains("Options.Book_for_new_players")) + firstBooks.add(this.getConfig().getString("Options.Book_for_new_players")); + if (firstBooks.isEmpty()) + firstBooks.add(" "); + + fBMessage = this.getConfig().getString("Options.Message_for_new_players", " "); + + //Converts string into material + String sMaterial = this.getConfig().getString("Options.Price_to_create_book.Item_type", " "); + if (sMaterial.equalsIgnoreCase("Economy")) { + if (setupEconomy()) + bookPriceType = Material.AIR; + else { + consoleSender.sendMessage(ChatColor.RED + "BooksWithoutBorders failed to hook into Vault! Book price not set!"); + bookPriceType = null; + } + } else if (!sMaterial.equalsIgnoreCase(" ")) { + for (Material m : Material.values()) { + if (m.toString().equalsIgnoreCase(sMaterial)) + bookPriceType = m; + } + } + bookPriceQuantity = this.getConfig().getDouble("Options.Price_to_create_book.Required_quantity", 0); + + //makes sure TsepA is a valid value + titleAuthorSeparator = cleanString(titleAuthorSeparator); + if (titleAuthorSeparator.length() != 1) { + consoleSender.sendMessage(ChatColor.RED + "Title-Author_Separator is set to an invalid value!"); + consoleSender.sendMessage(ChatColor.RED + "Reverting to default value of \",\""); + titleAuthorSeparator = ","; + this.getConfig().set("Options.Title-Author_Separator", titleAuthorSeparator); + } + } catch (Exception e) { + consoleSender.sendMessage(ChatColor.RED + "Warning! Config.yml failed to load!"); + consoleSender.sendMessage(ChatColor.RED + "Try Looking for settings that are missing values!"); + return false; + } + + return true; + } + + private boolean setupEconomy() { + Plugin plugin = getServer().getPluginManager().getPlugin("Vault"); + + if (plugin != null) { + RegisteredServiceProvider economyProvider = getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class); + if (economyProvider != null) { + eco = economyProvider.getProvider(); + } + } + + return (eco != null); + } + + protected boolean loadExistingPlayers() { + File fTest = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Existing Players.txt"); + existingPlayers = new ArrayList<>(); + + if (!fTest.exists()) { + try { + if (!fTest.createNewFile()) { + consoleSender.sendMessage(ChatColor.RED + "Could not create Existing Players file! Aborting..."); + return false; + } + PrintWriter pw = new PrintWriter(new FileWriter(fTest)); + OfflinePlayer[] players = this.getServer().getOfflinePlayers(); + + for (OfflinePlayer player : players) { + existingPlayers.add(player.getName()); + } + for (String player : existingPlayers) { + pw.println(player); + } + pw.close(); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } else { + try { + BufferedReader br = new BufferedReader(new FileReader(fTest)); + String nullcheck = br.readLine(); + + while (nullcheck != null) { + existingPlayers.add(nullcheck); + nullcheck = br.readLine(); + } + br.close(); + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + return true; + } + + protected void addExistingPlayer(String playerName) { + existingPlayers.add(playerName); + + try { + PrintWriter pw = new PrintWriter(new FileWriter(this.getDataFolder().getAbsolutePath() + SLASH + "Existing Players.txt", true)); + pw.println(playerName); + pw.close(); + } catch (IOException e) { + consoleSender.sendMessage(ChatColor.RED + "Failed to add " + playerName + " to Existing Players list"); + } + } + + protected boolean hasPlayedBefore(String playerName) { + if (!existingPlayers.contains(playerName)) { + addExistingPlayer(playerName); + return false; + } + + return true; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + + if ((command.getName().equalsIgnoreCase("BooksWithoutBorders") || command.getName().equalsIgnoreCase("bwb"))) { + if (args.length == 0 && sender instanceof Player) { + //lists commands + sender.sendMessage(ChatColor.YELLOW + "Use: /bwb [Command]"); + sender.sendMessage(ChatColor.YELLOW + "[] denote parameters"); + + if (BooksHavePrice()) { + if (bookPriceType != Material.AIR) + sender.sendMessage(ChatColor.RED + "[" + (int) bookPriceQuantity + " " + bookPriceType.toString() + "(s) are required to create a book]"); + else + sender.sendMessage(ChatColor.RED + "[" + BooksWithoutBorders.eco.format(bookPriceQuantity) + " is required to create a book]"); + } + sender.sendMessage(ChatColor.YELLOW + "Commands:"); + + if (sender.hasPermission("bookswithoutborders.load")) { + sender.sendMessage(ChatColor.YELLOW + "\nLoad [file name or number] [# of copies] [true/false]:"); + sender.sendMessage(ChatColor.GREEN + "Creates a book from the specified file and gives it to the player"); + sender.sendMessage(ChatColor.GREEN + "If no file is specified, a list of available files is returned"); + sender.sendMessage(ChatColor.GREEN + "If true is specified the book will be signed, if false it will be"); + sender.sendMessage(ChatColor.GREEN + "unsigned"); + } + if (sender.hasPermission("bookswithoutborders.loadpublic")) { + sender.sendMessage(ChatColor.YELLOW + "loadPublic [file name or number] [# of copies] [true/false]:"); + sender.sendMessage(ChatColor.GREEN + "Same as Load, but views files in the public directory"); + } + if (sender.hasPermission("bookswithoutborders.save")) { + sender.sendMessage("\n" + ChatColor.YELLOW + "Save [true/false]: " + ChatColor.GREEN + "Saves the book the player is"); + sender.sendMessage(ChatColor.GREEN + "holding to a text file in a private directory"); + sender.sendMessage(ChatColor.GREEN + "If true is specified, a book of the same name by the same"); + sender.sendMessage(ChatColor.GREEN + "author will be overwritten by the new book"); + } + if (sender.hasPermission("bookswithoutborders.savepublic")) { + sender.sendMessage(ChatColor.YELLOW + "savePublic [true/false]: " + ChatColor.GREEN + "Same as Save,"); + sender.sendMessage(ChatColor.GREEN + "but saves files in the public directory"); + } + if (sender.hasPermission("bookswithoutborders.give")) { + sender.sendMessage("\n" + ChatColor.YELLOW + "Give [file name or number] [playername] [# of copies] [true/false]:"); + sender.sendMessage(ChatColor.GREEN + "Gives the selected player a book from your personal directory"); + } + if (sender.hasPermission("bookswithoutborders.givepublic")) { + sender.sendMessage(ChatColor.YELLOW + "givePublic [file name or number] [playername] [# of copies] [true/false]:"); + sender.sendMessage(ChatColor.GREEN + "Same as give, but uses books from the public directory"); + } + if (sender.hasPermission("bookswithoutborders.delete")) { + sender.sendMessage(ChatColor.YELLOW + "\nDelete [file name or number]: " + ChatColor.GREEN + "Deletes the specified"); + sender.sendMessage(ChatColor.GREEN + "file in the player's directory"); + sender.sendMessage(ChatColor.GREEN + "If no file is specified, a list of available files is returned"); + } + if (sender.hasPermission("bookswithoutborders.admin")) { + sender.sendMessage(ChatColor.YELLOW + "deletePublic [file name or number]: " + ChatColor.GREEN + "Same as Delete,"); + sender.sendMessage(ChatColor.GREEN + "but deletes files in the public directory"); + sender.sendMessage(ChatColor.YELLOW + "\nReload:" + ChatColor.GREEN + " Reloads BwB's configuration file"); + } + + if (sender.hasPermission("bookswithoutborders.unsign")) + sender.sendMessage("\n" + ChatColor.YELLOW + "Unsign: " + ChatColor.GREEN + "Unsigns the book the player is holding"); + if (sender.hasPermission("bookswithoutborders.copy")) + sender.sendMessage("\n" + ChatColor.YELLOW + "Copy [number of copies]: " + ChatColor.GREEN + "Copies the book the player is holding"); + + if (sender.hasPermission("bookswithoutborders.encrypt")) { + sender.sendMessage("\n" + ChatColor.YELLOW + "Encrypt [key] [style]: " + ChatColor.GREEN + "Encrypts the book the player is holding"); + sender.sendMessage(ChatColor.YELLOW + "[key]" + ChatColor.GREEN + " is required and can be any phrase or number excluding spaces"); + sender.sendMessage(ChatColor.YELLOW + "[style]" + ChatColor.GREEN + " is not required. Possible values are \"DNA\" or \"Magic\""); + } + if (sender.hasPermission("bookswithoutborders.groupencrypt")) { + sender.sendMessage("\n" + ChatColor.YELLOW + "groupEncrypt [group name] [key] [style]: " + ChatColor.GREEN + "Encrypts book so that only players with the" + + "\n bookswithoutborders.decrypt." + ChatColor.YELLOW + "[group name]" + ChatColor.GREEN + " permission may decrypt the book by holding and left clicking the book"); + } + if (sender.hasPermission("bookswithoutborders.decrypt")) { + sender.sendMessage("\n" + ChatColor.YELLOW + "Decrypt [key]: " + ChatColor.GREEN + "Decrypts the book the player is holding"); + sender.sendMessage(ChatColor.YELLOW + "[key]" + ChatColor.GREEN + " is required and MUST be IDENTICAL to the key used to encrypt held book"); + } + if (sender.hasPermission("bookswithoutborders.settitle")) + sender.sendMessage("\n" + ChatColor.YELLOW + "setTitle [title]: " + ChatColor.GREEN + "Sets the title of the book/item the player is holding"); + if (sender.hasPermission("bookswithoutborders.setauthor")) + sender.sendMessage("\n" + ChatColor.YELLOW + "setAuthor [author]: " + ChatColor.GREEN + "Sets the author of the book the player is holding"); + if (sender.hasPermission("bookswithoutborders.setlore")) { + sender.sendMessage("\n" + ChatColor.YELLOW + "setLore [lore]: " + ChatColor.GREEN + "Sets the lore of the item the player is holding"); + sender.sendMessage(ChatColor.GREEN + "Insert the lore_line_separator character to force a new line\n[\"~\" by default]"); + } + if (sender.hasPermission("bookswithoutborders.setbookprice")) + sender.sendMessage("\n" + ChatColor.YELLOW + "setBookPrice [Item/Eco] [quantity]: " + ChatColor.GREEN + "Sets the per-book-price to create a book via commands." + + "\nIf [Item], the item in the player's hand in the amount of [quanity] will be the price.\nIf [Eco], a Vault based economy will be used for price." + + "\nIf neither [Item/Eco] or [quantity] are specified the current price to create books will be removed."); + return true; + } + + if (args.length == 0 && !(sender instanceof Player)) { + //lists commands from console + sender.sendMessage(ChatColor.YELLOW + "Use: /bwb [Command]"); + sender.sendMessage(ChatColor.YELLOW + "[] denote parameters"); + sender.sendMessage(ChatColor.YELLOW + "Commands:"); + sender.sendMessage(ChatColor.YELLOW + "\nReload:" + ChatColor.GREEN + " Reloads BwB's config file"); + sender.sendMessage(ChatColor.YELLOW + "givePublic [file name or number] [playername] [true/false]: " + ChatColor.GREEN); + sender.sendMessage(ChatColor.GREEN + "Gives the selected player a book from the public directory"); + sender.sendMessage(ChatColor.GREEN + "If no file is specified, a list of available files is returned"); + sender.sendMessage(ChatColor.YELLOW + "deletePublic [file name or number]: " + ChatColor.GREEN + "Deletes the specified"); + sender.sendMessage(ChatColor.GREEN + "file in the public directory"); + sender.sendMessage(ChatColor.GREEN + "If no file is specified, a list of available files is returned"); + + return true; + } + + if (args[0].equalsIgnoreCase("reload")) { + if (sender instanceof Player) { + if (!sender.hasPermission("bookswithoutborders.admin") && !sender.hasPermission("*")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to use this command!"); + return false; + } + } + + if (loadConfig() && loadExistingPlayers()) + sender.sendMessage(ChatColor.GREEN + "BooksWithoutBorders configuration reloaded!"); + else { + sender.sendMessage(ChatColor.RED + "Reload Failed!"); + sender.sendMessage(ChatColor.RED + "See console for details"); + } + return true; + } + + //Player only commands + if (sender instanceof Player) { + + if (args[0].equalsIgnoreCase("savePublic")) { + if (!sender.hasPermission("bookswithoutborders.savepublic")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK || ((Player) sender).getItemInHand().getType() == Material.WRITABLE_BOOK) { + if (args.length == 2) { + saveBook((Player) sender, args[1], true); + } else { + saveBook((Player) sender, "false", true); + } + return true; + } else { + sender.sendMessage(ChatColor.RED + "You must be holding a written book or book and quill to save it!"); + return false; + } + } + + if (args[0].equalsIgnoreCase("save")) { + if (!sender.hasPermission("bookswithoutborders.save")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK || ((Player) sender).getItemInHand().getType() == Material.WRITABLE_BOOK) { + if (args.length == 2) { + saveBook((Player) sender, args[1], false); + return true; + } else { + saveBook((Player) sender, "false", false); + return true; + } + } else { + sender.sendMessage(ChatColor.RED + "You must be holding a written book or book and quill to save it!"); + return false; + } + } + + if (args[0].equalsIgnoreCase("loadPublic")) { + if (!sender.hasPermission("bookswithoutborders.loadpublic")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (((Player) sender).getInventory().firstEmpty() == -1) { + sender.sendMessage(ChatColor.RED + "You must have space in your inventory to load books!"); + return false; + } + + if (args.length == 1) { + loadList.put(sender.getName(), listFiles(sender, true, 0, false)); + return true; + } + + for (int x = 0; x < args[1].length(); x++) { + if (!Character.isDigit(args[1].charAt(x))) + break; + if (x == args[1].length() - 1) + loadList.put(sender.getName(), listFiles(sender, true, 0, true)); + } + + ItemStack newBook; + try { + if (args.length == 4) + newBook = loadBook(sender, cleanString(args[1]), args[3], "public", Integer.parseInt(args[2])); + else if (args.length == 3) { + if (args[2].equalsIgnoreCase("true") || args[2].equalsIgnoreCase("false")) + newBook = loadBook(sender, cleanString(args[1]), args[2], "public"); + else + newBook = loadBook(sender, cleanString(args[1]), "true", "public", Integer.parseInt(args[2])); + } else + newBook = loadBook(sender, cleanString(args[1]), "true", "public"); + + if (newBook != null) { + ((Player) sender).getInventory().addItem(newBook); + sender.sendMessage(ChatColor.GREEN + "Book created!"); + return true; + } else { + sender.sendMessage(ChatColor.RED + "Book failed to load!"); + return false; + } + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Invalid number of book copies specified!"); + return false; + } + } + + if (args[0].equalsIgnoreCase("load")) { + if (!sender.hasPermission("bookswithoutborders.load")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (((Player) sender).getInventory().firstEmpty() == -1) { + sender.sendMessage(ChatColor.RED + "You must have space in your inventory to load books!"); + return false; + } + + if (args.length == 1) { + loadList.put(sender.getName(), listFiles(sender, false, 0, false)); + return true; + } + + for (int x = 0; x < args[1].length(); x++) { + if (!Character.isDigit(args[1].charAt(x))) + break; + if (x == args[1].length() - 1) + loadList.put(sender.getName(), listFiles(sender, false, 0, true)); + } + + ItemStack newBook; + try { + + if (args.length == 4) + newBook = loadBook(sender, cleanString(args[1]), args[3], "player", Integer.parseInt(args[2])); + else if (args.length == 3) { + if (args[2].equalsIgnoreCase("true") || args[2].equalsIgnoreCase("false")) + newBook = loadBook(sender, cleanString(args[1]), args[2], "player"); + else + newBook = loadBook(sender, cleanString(args[1]), "true", "player", Integer.parseInt(args[2])); + } else + newBook = loadBook(sender, cleanString(args[1]), "true", "player"); + + if (newBook != null) { + ((Player) sender).getInventory().addItem(newBook); + sender.sendMessage(ChatColor.GREEN + "Book created!"); + return true; + } else { + sender.sendMessage(ChatColor.RED + "Book failed to load!"); + return false; + } + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Invalid number of book copies specified!"); + return false; + } + } + + if (args[0].equalsIgnoreCase("give")) { + if (!sender.hasPermission("bookswithoutborders.give")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (!(args.length == 1 || args.length == 3 || args.length == 4 || args.length == 5)) { + sender.sendMessage(ChatColor.RED + "Incorrect number of arguments for this command!"); + return false; + } + + if (args.length == 1) { + loadList.put(sender.getName(), listFiles(sender, false, 0, false)); + return true; + } + + for (int x = 0; x < args[1].length(); x++) { + if (!Character.isDigit(args[1].charAt(x))) + break; + if (x == args[1].length() - 1) + loadList.put(sender.getName(), listFiles(sender, false, 0, true)); + } + + ItemStack newBook; + Player player = this.getServer().getPlayer(args[2]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Player not found!"); + return false; + } + + if (player.getInventory().firstEmpty() == -1) { + sender.sendMessage(ChatColor.RED + "Receiving player must have space in their inventory to receive books!"); + return false; + } + + //bwb give [bookname] [player] [numCopies] [issigned] + try { + + if (args.length == 5) + newBook = loadBook(sender, cleanString(args[1]), args[4], "player", Integer.parseInt(args[3])); + else if (args.length == 4) { + if (args[3].equalsIgnoreCase("true") || args[3].equalsIgnoreCase("false")) + newBook = loadBook(sender, cleanString(args[1]), args[3], "player"); + else + newBook = loadBook(sender, cleanString(args[1]), "true", "player", Integer.parseInt(args[3])); + } else + newBook = loadBook(sender, cleanString(args[1]), "true", "player"); + + if (newBook != null) { + player.getInventory().addItem(newBook); + sender.sendMessage(ChatColor.GREEN + "Book sent!"); + player.sendMessage(ChatColor.GREEN + "Book received!"); + return true; + } else { + sender.sendMessage(ChatColor.RED + "Book failed to load!"); + return false; + } + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Invalid number of book copies specified!"); + return false; + } + } + + if (args[0].equalsIgnoreCase("delete")) { + if (!sender.hasPermission("bookswithoutborders.delete")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + //lists delete-able files + if (args.length == 1) { + loadList.put(sender.getName(), listFiles(sender, false, 0, false)); + return true; + } + //actual deletion + if (args.length == 2) { + if (!loadList.containsKey(sender.getName())) { + sender.sendMessage(ChatColor.RED + "You must first use /bwb delete to create a list of delete-able files!"); + return false; + } else { + if (!loadList.get(sender.getName()).isEmpty()) { + deleteBook(sender, args[1], false); + return true; + } else { + sender.sendMessage(ChatColor.RED + "No files available to delete!"); + return false; + } + } + } + } + + if (args[0].equalsIgnoreCase("unsign")) { + if (!sender.hasPermission("bookswithoutborders.unsign")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (!(((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK)) { + sender.sendMessage(ChatColor.RED + "You must be holding a written book to unsign it!"); + return false; + } + + unsign((Player) sender); + return true; + } + + if (args[0].equalsIgnoreCase("copy")) { + if (!sender.hasPermission("bookswithoutborders.copy")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (!(((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK)) { + sender.sendMessage(ChatColor.RED + "You must be holding a written book to copy it!"); + return false; + } + + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "You must specifiy the number of copies to be made!"); + return false; + } + + try { + if (Integer.parseInt(args[1]) > 0) { + if (authorOnlyCopy && !sender.hasPermission("bookswithoutborders.bypassauthoronlycopy")) { + if (!isAuthor((Player) sender, (BookMeta) ((Player) sender).getItemInHand().getItemMeta())) + return false; + } + + if (BooksHavePrice() && !sender.hasPermission("bookswithoutborders.bypassbookprice")) { + if (!printBook((Player) sender, Integer.parseInt(args[1]))) + return false; + } + + ((Player) sender).getItemInHand().setAmount(((Player) sender).getItemInHand().getAmount() + Integer.parseInt(args[1])); + sender.sendMessage(ChatColor.GREEN + "Book copied!"); + } else { + sender.sendMessage(ChatColor.RED + "Book not copied!"); + sender.sendMessage(ChatColor.RED + "Number specified was invalid!"); + return false; + } + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Book not copied!"); + sender.sendMessage(ChatColor.RED + "Number specified was invalid!"); + return false; + } + return true; + } + + if (args[0].equalsIgnoreCase("encrypt")) { + if (!sender.hasPermission("bookswithoutborders.encrypt")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (!(((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK)) { + sender.sendMessage(ChatColor.RED + "You must be holding a written book to encrypt it!"); + return false; + } + + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "You must specify a key to encrypt a book!"); + return false; + } + if (args.length > 3) { + sender.sendMessage(ChatColor.RED + "Too many command options specified!"); + return false; + } + + if (!((BookMeta) ((Player) sender).getItemInHand().getItemMeta()).hasPages()) { + sender.sendMessage(ChatColor.RED + "Book must have contents to encrypt!"); + return false; + } + + ItemStack eBook; + + if (args.length == 3) { + eBook = bookEncryption((Player) sender, args[1], args[2], ""); + } else { + eBook = bookEncryption((Player) sender, args[1], "", ""); + } + + if (eBook != null) { + ((Player) sender).setItemInHand(eBook); + return true; + } else + return false; + } + + if (args[0].equalsIgnoreCase("groupEncrypt")) { + if (!sender.hasPermission("bookswithoutborders.groupencrypt")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (!(((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK)) { + sender.sendMessage(ChatColor.RED + "You must be holding a written book to encrypt it!"); + return false; + } + + if (args.length < 3) { + sender.sendMessage(ChatColor.RED + "You must specify a key to encrypt a book!"); + return false; + } + if (args.length > 4) { + sender.sendMessage(ChatColor.RED + "Too many command options specified!"); + return false; + } + + if (!((BookMeta) ((Player) sender).getItemInHand().getItemMeta()).hasPages()) { + sender.sendMessage(ChatColor.RED + "Book must have contents to encrypt!"); + return false; + } + + if (((Player) sender).getItemInHand().getItemMeta().hasLore() + && ((Player) sender).getItemInHand().getItemMeta().getLore().get(0).contains(" encrypted]")) { + sender.sendMessage(ChatColor.RED + "Book is already group encrypted!"); + return false; + } + + ItemStack eBook; + + if (args.length == 4) { + eBook = bookEncryption((Player) sender, args[2], args[3], args[1]); + } else { + eBook = bookEncryption((Player) sender, args[2], "", args[1]); + } + + if (eBook != null) { + ((Player) sender).setItemInHand(eBook); + return true; + } else + return false; + } + + if (args[0].equalsIgnoreCase("decrypt")) { + if (!sender.hasPermission("bookswithoutborders.decrypt")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (!(((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK)) { + sender.sendMessage(ChatColor.RED + "You must be holding a written book to decrypt it!"); + return false; + } + + if (args.length == 1 && adminDecrypt && sender.hasPermission("bookswithoutborders.admin") && sender instanceof Player) { + String path = this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + "Encrypted" + SLASH; + String fname = (!((BookMeta) ((Player) sender).getItemInHand().getItemMeta()).hasTitle()) ? "Untitled," + sender.getName() : + ((BookMeta) ((Player) sender).getItemInHand().getItemMeta()).getTitle() + titleAuthorSeparator + ((BookMeta) ((Player) sender).getItemInHand().getItemMeta()).getAuthor(); + File eDir = new File(path); + + String key = ""; + for (int i = 0; i < eDir.list().length; i++) { + if (eDir.list()[i].contains(fname)) { + key = eDir.list()[i].substring(eDir.list()[i].indexOf("[") + 1, eDir.list()[i].indexOf("]")); + break; + } + } + + if (!key.equalsIgnoreCase("")) { + ItemStack book = eLoad(((Player) sender), key, false); + if (book != null) { + ((Player) sender).setItemInHand(book); + sender.sendMessage(ChatColor.GREEN + "Book auto-decrypted!"); + return true; + } else + return false; + } else { + sender.sendMessage(ChatColor.RED + "No matching encypted book found!"); + return false; + } + } + + StringBuilder key = new StringBuilder();//converts user supplied key into integer form + for (int x = 0; x < args[1].length(); x++) + key.append(Character.getNumericValue(Character.codePointAt(args[1], x))); + + ItemStack book = eLoad(((Player) sender), key.toString(), true); + if (book != null) { + ((Player) sender).setItemInHand(book); + sender.sendMessage(ChatColor.GREEN + "Book decrypted!"); + return true; + } else + return false; + } + + if (args[0].equalsIgnoreCase("setTitle")) { + if (!sender.hasPermission("bookswithoutborders.settitle")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "Too few command arguments!"); + return false; + } + + if (((Player) sender).getItemInHand().getType() == Material.AIR) { + sender.sendMessage(ChatColor.RED + "You must be holding an item to set title!"); + return false; + } + + StringBuilder title = new StringBuilder(); + for (int x = 1; x < args.length; x++) { + if (x == 1) + title.append(args[x]); + else + title.append(" ").append(args[x]); + } + title = new StringBuilder(ChatColor.translateAlternateColorCodes('&', title.toString())); + + ItemMeta meta; + if (((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK) { + BookMeta bMeta = (BookMeta) ((Player) sender).getItemInHand().getItemMeta(); + bMeta.setTitle(title.toString()); + meta = bMeta; + } else { + ItemMeta iMeta = ((Player) sender).getItemInHand().getItemMeta(); + iMeta.setDisplayName(title.toString()); + meta = iMeta; + } + + ((Player) sender).getItemInHand().setItemMeta(meta); + sender.sendMessage(ChatColor.GREEN + "Title set to " + title + "!"); + return true; + } + + if (args[0].equalsIgnoreCase("setAuthor")) { + if (!sender.hasPermission("bookswithoutborders.setauthor")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "Too few command arguments!"); + return false; + } + + if (!(((Player) sender).getItemInHand().getType() == Material.WRITTEN_BOOK)) { + sender.sendMessage(ChatColor.RED + "You must be holding a written book to set author!"); + return false; + } + + BookMeta meta = (BookMeta) ((Player) sender).getItemInHand().getItemMeta(); + StringBuilder author = new StringBuilder(); + for (int x = 1; x < args.length; x++) { + if (x == 1) + author.append(args[x]); + else + author.append(" ").append(args[x]); + } + meta.setAuthor(author.toString()); + ((Player) sender).getItemInHand().setItemMeta(meta); + sender.sendMessage(ChatColor.GREEN + "Book author set to " + author + "!"); + return true; + } + + if (args[0].equalsIgnoreCase("setLore")) { + if (!sender.hasPermission("bookswithoutborders.setlore")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (args.length < 2) { + sender.sendMessage(ChatColor.RED + "Missing a command argument!"); + return false; + } + + if (((Player) sender).getItemInHand().getType() == Material.AIR) { + sender.sendMessage(ChatColor.RED + "Must be holding an item to set lore!"); + return false; + } + + StringBuilder rawlore = new StringBuilder(); + for (int x = 1; x < args.length; x++) { + if (x == 1) + rawlore.append(args[x]); + else + rawlore.append(" ").append(args[x]); + } + + rawlore = new StringBuilder(ChatColor.translateAlternateColorCodes('&', rawlore.toString())); + String[] lores = rawlore.toString().split(loreSep); + List nulore = new ArrayList<>(Arrays.asList(lores)); + + ItemMeta meta = ((Player) sender).getItemInHand().getItemMeta(); + meta.setLore(nulore); + ((Player) sender).getItemInHand().setItemMeta(meta); + sender.sendMessage(ChatColor.GREEN + "Added lore to item!"); + return true; + } + } + + if (args[0].equalsIgnoreCase("givePublic")) { + if (sender instanceof Player) { + if (!sender.hasPermission("bookswithoutborders.givepublic")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + } + + if (!(args.length == 1 || args.length == 3 || args.length == 4 || args.length == 5)) { + sender.sendMessage(ChatColor.RED + "Incorrect number of arguments for this command!"); + return false; + } + + if (args.length == 1) { + loadList.put(sender.getName(), listFiles(sender, true, 0, false)); + return true; + } + + for (int x = 0; x < args[1].length(); x++) { + if (!Character.isDigit(args[1].charAt(x))) + break; + if (x == args[1].length() - 1) + loadList.put(sender.getName(), listFiles(sender, true, 0, true)); + } + + ItemStack newBook; + Player player = this.getServer().getPlayer(args[2]); + if (player == null) { + sender.sendMessage(ChatColor.RED + "Player not found!"); + return false; + } + + if (player.getInventory().firstEmpty() == -1) { + sender.sendMessage(ChatColor.RED + "Receiving player must have space in their inventory to receive books!"); + return false; + } + //bwb give [bookname] [player] [numCopies] [issigned] + try { + if (args.length == 5) + newBook = loadBook(sender, cleanString(args[1]), args[4], "public", Integer.parseInt(args[3])); + else if (args.length == 4) { + if (args[3].equalsIgnoreCase("true") || args[3].equalsIgnoreCase("false")) + newBook = loadBook(sender, cleanString(args[1]), args[3], "public"); + else + newBook = loadBook(sender, cleanString(args[1]), "true", "public", Integer.parseInt(args[3])); + } else + newBook = loadBook(sender, cleanString(args[1]), "true", "public"); + + if (newBook != null) { + player.getInventory().addItem(newBook); + sender.sendMessage(ChatColor.GREEN + "Book sent!"); + player.sendMessage(ChatColor.GREEN + "Book received!"); + return true; + } else { + sender.sendMessage(ChatColor.RED + "Book failed to load!"); + return false; + } + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "Invalid number of book copies specified!"); + return false; + } + } + + if (args[0].equalsIgnoreCase("deletepublic")) { + + if (sender instanceof Player) { + if (!sender.hasPermission("bookswithoutborders.admin")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + } + + //lists delete-able files + if (args.length == 1) { + loadList.put(sender.getName(), listFiles(sender, true, 0, false)); + return true; + } + //actual deletion + if (args.length == 2) { + if (!loadList.containsKey(sender.getName())) { + sender.sendMessage(ChatColor.RED + "You must first use /bwb deletePublic to create a list of delete-able files!"); + return false; + } else { + if (!loadList.get(sender.getName()).isEmpty()) { + deleteBook((sender), args[1], true); + return true; + } else { + sender.sendMessage(ChatColor.RED + "No files available to delete!"); + return false; + } + } + } + } + + if (args[0].equalsIgnoreCase("setBookPrice")) { + if (!sender.hasPermission("bookswithoutborders.setbookprice")) { + sender.sendMessage(ChatColor.RED + " You don't have permission to use this command!"); + return false; + } + + if (args.length == 1) { + bookPriceType = null; + bookPriceQuantity = 0; + this.getConfig().set("Options.Price_to_create_book.Item_type", "Item type name"); + this.getConfig().set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity); + this.saveConfig(); + + sender.sendMessage(ChatColor.GREEN + "Price to create books removed!"); + return true; + } + + if (args.length < 3) { + sender.sendMessage(ChatColor.RED + "[Item/Eco] and [quantity] must be specified!"); + return false; + } + + if (!args[1].equalsIgnoreCase("Item") && !args[1].equalsIgnoreCase("Eco")) { + sender.sendMessage(ChatColor.RED + "Price type must be \"Item\" or \"Eco\"!"); + return false; + } + + try { + if (Double.parseDouble(args[2]) <= 0) { + sender.sendMessage(ChatColor.RED + "[quantity] must be greater than 0!"); + return false; + } + + if (args[1].equalsIgnoreCase("Item")) { + if (((Player) sender).getItemInHand().getType() == Material.AIR) { + sender.sendMessage(ChatColor.RED + "Must be holding an item to set an [Item] price!"); + return false; + } + + bookPriceType = ((Player) sender).getItemInHand().getType(); + bookPriceQuantity = Double.parseDouble(args[2]); + this.getConfig().set("Options.Price_to_create_book.Item_type", bookPriceType.toString()); + this.getConfig().set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity); + this.saveConfig(); + + sender.sendMessage(ChatColor.GREEN + "Book creation price set to " + (int) bookPriceQuantity + " " + bookPriceType.toString() + "(s)!"); + return true; + } else if (args[1].equalsIgnoreCase("Eco")) { + if (setupEconomy()) { + bookPriceQuantity = Double.parseDouble(args[2]); + bookPriceType = Material.AIR; + this.getConfig().set("Options.Price_to_create_book.Item_type", "Economy"); + this.getConfig().set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity); + this.saveConfig(); + + sender.sendMessage(ChatColor.GREEN + "Book creation price set to " + eco.format(bookPriceQuantity) + "!"); + return true; + } else { + sender.sendMessage(ChatColor.RED + "BooksWithoutBorders failed to hook into Vault! Book price not set!"); + return false; + } + + } + } catch (NumberFormatException e) { + sender.sendMessage(ChatColor.RED + "[quantity] must be a number!"); + } + } + + sender.sendMessage(ChatColor.RED + "Command not recognized! Use " + ChatColor.YELLOW + "/BwB" + ChatColor.RED + " for more info!"); + } + return false; + } + + protected String cleanString(String fname) { + //removes illegal characters + if (fname.contains("/")) { + fname = fname.replace("/", ""); + } + if (fname.contains("\\")) { + fname = fname.replace("\\", ""); + } + if (fname.contains("*")) { + fname = fname.replace("*", ""); + } + if (fname.contains(":")) { + fname = fname.replace(":", ""); + } + if (fname.contains("|")) { + fname = fname.replace("|", ""); + } + if (fname.contains("<")) { + fname = fname.replace("<", ""); + } + if (fname.contains(">")) { + fname = fname.replace(">", ""); + } + if (fname.contains("?")) { + fname = fname.replace("?", ""); + } + if (fname.contains("\"")) { + fname = fname.replace("\"", ""); + } + return fname; + } + + protected String fixName(String fname, Boolean isLoad) { + if (isLoad) { + fname = fname.replace("_", " "); + } else { + fname = fname.replace(" ", "_"); + } + return fname; + } + + protected void bookToYml(String path, String fname, BookMeta book) throws IOException { + FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File(path, "blank")); + + if (book.hasTitle()) + bookYml.set("Title", book.getTitle()); + if (book.hasAuthor()) + bookYml.set("Author", book.getAuthor()); + if (book.hasPages()) + bookYml.set("Pages", book.getPages()); + if (book.hasLore()) + bookYml.set("Lore", book.getLore()); + + bookYml.save(path + fname + ".yml"); + } + + protected BookMeta bookFromYml(File file, BookMeta bDat) { + //Handles old style loading + if (file.getName().substring(file.getName().lastIndexOf(".")).equals(".txt")) + bDat = bookFromTXT(file.getName(), file, bDat); + else { + try { + FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file); + + bDat.setTitle(bookYml.getString("Title", "Untitled")); + bDat.setAuthor(bookYml.getString("Author", "Unknown")); + bDat.setPages(bookYml.getStringList("Pages")); + bDat.setLore(bookYml.getStringList("Lore")); + } catch (IllegalArgumentException e) { + return null; + } + } + + return bDat; + } + + protected void bookToTXT(String path, String fname, BookMeta book) throws IOException { + FileWriter fw = new FileWriter(path + fname + ".txt"); + PrintWriter pw = new PrintWriter(fw); + List pages = book.getPages(); + + pw.println("[Book]"); + for (String page : pages) { + pw.println(page); + } + pw.close(); + } + + protected BookMeta bookFromTXT(String fname, File file, BookMeta bDat) { + String author; + String title; + if (fname.contains(titleAuthorSeparator)) { + author = fname.substring(fname.indexOf(titleAuthorSeparator) + 1, fname.length() - 4); + title = fname.substring(0, fname.indexOf(titleAuthorSeparator)); + } else { + author = "Unknown"; + title = fname.substring(0, fname.length() - 4); + } + + title = fixName(title, true); + + List rawPages = new ArrayList<>(); + String nullcheck; + + try { + FileReader fr = new FileReader(file); + BufferedReader br = new BufferedReader(fr); + + rawPages.add(br.readLine()); + + if (rawPages.get(0) == null) { + br.close(); + return null; + } + + //if a book is being loaded its content need not be adjusted + if (rawPages.get(0).equalsIgnoreCase("[Book]")) { + rawPages.remove(0); + nullcheck = br.readLine(); + while (nullcheck != null) { + rawPages.add(nullcheck); + nullcheck = br.readLine(); + } + } else//adjusts content to page length + { + while (rawPages.get(rawPages.size() - 1) != null) { + rawPages = pageFormat(rawPages); + rawPages.add(br.readLine()); + } + } + br.close(); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + + List pages = new ArrayList<>(cleanList(rawPages)); + + bDat.setAuthor(author); + bDat.setTitle(title); + bDat.setPages(pages); + + return bDat; + } + + protected void saveBook(Player player, String dupe, Boolean pub) { + BookMeta book = (BookMeta) player.getItemInHand().getItemMeta(); + String path; + if (pub) { + path = this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH; + } else { + path = this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + cleanString(player.getName()) + SLASH; + } + String fname; + if (!book.hasTitle()) { + fname = "Untitled," + player.getName(); + } else { + fname = book.getTitle() + titleAuthorSeparator + book.getAuthor(); + } + fname = cleanString(fname); + fname = fixName(fname, false); + int dupes = 0; + + //checks to see if the file name is already taken and adjusts it if so + File file = new File(path); + + //if this is a private save, make sure the dir exists + if (!file.exists()) { + if (!file.mkdir()) { + player.sendMessage(ChatColor.RED + "Saving Failed! If this continues to happen, consult server admin!"); + return; + } + } + + if (file.listFiles().length > 0) { + File[] fl = file.listFiles(); + for (int x = 0; x < fl.length; x++) { + if (dupes == 0) { + if (fl[x].getName().equalsIgnoreCase(fname)) { + dupes++; + x = -1; + } + } else { + if (fl[x].getName().equalsIgnoreCase("(" + dupes + ")" + fname)) { + dupes++; + x = -1; + } + } + } + } + if (dupes > 0) { + if (!fname.contains("Untitled") && dupe.equalsIgnoreCase("false")) { + player.sendMessage(ChatColor.RED + "Book is already saved!"); + player.sendMessage(ChatColor.RED + "Use " + ChatColor.YELLOW + "/bwb Save true " + ChatColor.RED + "to overwrite!"); + return; + } + + if (dupes > dlimit) { + player.sendMessage(ChatColor.RED + "Maximum amount of " + fname + " duplicates reached!"); + player.sendMessage(ChatColor.RED + "Use " + ChatColor.YELLOW + "/bwb Save true " + ChatColor.RED + "to overwrite!"); + return; + } + + if (fname.contains("Untitled") && dupe.equalsIgnoreCase("false")) { + fname = "(" + dupes + ")" + fname; + } + } + + try { + if (useYml) + bookToYml(path, fname, book); + else + bookToTXT(path, fname, book); + + player.sendMessage(ChatColor.GREEN + "Book Saved as \"" + fname + "\""); + } catch (IOException e) { + e.printStackTrace(); + } + + } + + protected ItemStack loadBook(CommandSender sender, String fname, String isSigned, String dir) { + return loadBook(sender, fname, isSigned, dir, 1); + } + + protected ItemStack loadBook(CommandSender sender, String fname, String isSigned, String dir, int numCopies) { + //checks if player is using list number to load + for (int x = 0; x < fname.length(); x++) { + if (!Character.isDigit(fname.charAt(x))) { + break; + } + if (x == fname.length() - 1 && Character.isDigit(fname.charAt(x)) && loadList.containsKey(sender.getName())) { + if (Integer.parseInt(fname) <= loadList.get(sender.getName()).size()) { + fname = loadList.get(sender.getName()).get(Integer.parseInt(fname) - 1); + } + } + } + + File file; + NameCheck: + { + //Extension is already present + if (fname.lastIndexOf(".") != -1) { + if (fname.substring(fname.lastIndexOf(".")).equals(".yml") || fname.substring(fname.lastIndexOf(".")).equals(".txt")) { + if (dir.equalsIgnoreCase("public")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + fname); + + else if (dir.equalsIgnoreCase("player")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + cleanString(sender.getName()) + SLASH + fname); + + else if (!dir.equalsIgnoreCase("")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + "Encrypted" + SLASH + dir + SLASH + fname); + + else + file = null; + + if (!file.isFile()) { + sender.sendMessage(ChatColor.RED + "Incorrect file name!"); + return null; + } + break NameCheck; + } + } + //No extension present + if (dir.equalsIgnoreCase("public")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + fname + ".yml"); + + else if (dir.equalsIgnoreCase("player")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + cleanString(sender.getName()) + SLASH + fname + ".yml"); + + else if (!dir.equalsIgnoreCase("")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + "Encrypted" + SLASH + dir + SLASH + fname + ".yml"); + + else + file = null; + + if (!file.isFile()) { + if (dir.equalsIgnoreCase("public")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + fname + ".txt"); + + else if (dir.equalsIgnoreCase("player")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + cleanString(sender.getName()) + SLASH + fname + ".txt"); + + else if (!dir.equalsIgnoreCase("")) + file = new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + "Encrypted" + SLASH + dir + SLASH + fname + ".txt"); + + else + file = null; + + if (!file.isFile()) { + sender.sendMessage(ChatColor.RED + "Incorrect file name!"); + return null; + } + } + } + + ItemStack book; + BookMeta bDat = (BookMeta) iF.getItemMeta(Material.WRITTEN_BOOK); + if (isSigned.equalsIgnoreCase("true")) { + book = new ItemStack(Material.WRITTEN_BOOK); + } else { + book = new ItemStack(Material.WRITABLE_BOOK); + } + + bookFromYml(file, bDat); + if (bDat == null) { + sender.sendMessage(ChatColor.RED + "File was blank!!"); + return null; + } + + if (!dir.equalsIgnoreCase("public") && !dir.equalsIgnoreCase("player") && !dir.equalsIgnoreCase("") && bDat.hasLore()) { + List newLore = new ArrayList<>(bDat.getLore()); + newLore.remove(0); + bDat.setLore(newLore); + } + + book.setItemMeta(bDat); + book.setAmount(numCopies); + + if (BooksHavePrice() && !sender.hasPermission("bookswithoutborders.bypassbookprice") && + (dir.equalsIgnoreCase("public") || dir.equalsIgnoreCase("player") || dir.equalsIgnoreCase(""))) { + if (!printBook((Player) sender, numCopies)) + return null; + } + + return book; + } + + protected Boolean eSave(Player player, BookMeta book, String key) { + String path = this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + "Encrypted" + SLASH; + String fname = (!book.hasTitle()) ? "Untitled," + player.getName() : + book.getTitle() + titleAuthorSeparator + book.getAuthor(); + + fname = "[" + key + "]" + fname; + fname = cleanString(fname); + fname = fixName(fname, false); + + //cancels saving if file is already encrypted + File file = (useYml) ? new File(path + fname + ".yml") : new File(path + fname + ".txt"); + if (file.isFile()) { + return true; + } + + try { + bookToYml(path, fname, book); + } catch (IOException e) { + e.printStackTrace(); + player.sendMessage(ChatColor.RED + "Encrypted failed!"); + return false; + } + return true; + } + + protected BookMeta groupESave(Player player, BookMeta book, String key, String groupName) { + String path = this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + "Encrypted" + SLASH + cleanString(groupName) + SLASH; + File dirTest = new File(path); + //Creates group dir + if (!dirTest.exists()) { + try { + if (!dirTest.mkdir()) { + consoleSender.sendMessage(ChatColor.RED + "Unable to create encryption group folder!"); + return null; + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + //Creates file + String fname = (!book.hasTitle()) ? "Untitled," + player.getName() : + book.getTitle() + titleAuthorSeparator + book.getAuthor(); + + fname = cleanString(fname); + fname = fixName(fname, false); + + List nuLore = new ArrayList<>(); + nuLore.add(ChatColor.GRAY + "[" + groupName + " encrypted]"); + if (book.hasLore()) + nuLore.addAll(book.getLore()); + book.setLore(nuLore); + + //Save file + File file = (useYml) ? new File(path + fname + ".yml") : new File(path + fname + ".txt"); + if (!file.isFile()) { + try { + bookToYml(path, fname, book); + } catch (IOException e) { + e.printStackTrace(); + player.sendMessage(ChatColor.RED + "Group encrypted failed!"); + return null; + } + } + + return book; + } + + protected ItemStack eLoad(Player player, String key, boolean deleteEncryptedFile) { + BookMeta book = (BookMeta) player.getItemInHand().getItemMeta(); + String path = this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + "Encrypted" + SLASH; + + String fname = (!book.hasTitle()) ? "Untitled," + player.getName() : book.getTitle() + titleAuthorSeparator + book.getAuthor(); + fname = "[" + key + "]" + fname; + fname = cleanString(fname); + fname = fixName(fname, false); + + File file = new File(path + fname + ".yml"); + if (!file.isFile()) { + file = new File(path + fname + ".txt"); + + if (!file.isFile()) { + player.sendMessage(ChatColor.RED + "Incorrect decryption key!"); + return null; + } + } else { + try { + book = bookFromYml(file, book); + } catch (Exception e) { + player.sendMessage(ChatColor.RED + "Decryption failed!"); + return null; + } + } + + if (deleteEncryptedFile) { + try { + file.delete(); + } catch (Exception e) { + consoleSender.sendMessage(ChatColor.RED + "Book encryption data failed to delete upon decryption!"); + consoleSender.sendMessage(ChatColor.RED + "File location:" + file.getPath()); + } + } + + ItemStack nubook = new ItemStack(Material.WRITTEN_BOOK);//Book(book.getAuthor(), book.getTitle(), pages, 1, 387); + nubook.setItemMeta(book); + nubook.setAmount(player.getItemInHand().getAmount()); + return nubook; + } + + protected List listFiles(CommandSender sender, Boolean pub, int offset, boolean silent) { + List fList = new ArrayList<>(); + File file = (pub) ? new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH) : + new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + cleanString(sender.getName()) + SLASH); + File[] fl = file.listFiles(); + + if (!file.exists()) { + sender.sendMessage(ChatColor.RED + "No books have been saved!"); + return null; + } + + if (fl.length == 0) { + sender.sendMessage(ChatColor.RED + "No books have been saved!"); + return null; + } + + if (!silent) + sender.sendMessage(ChatColor.GREEN + "Available Books:"); + for (File value : fl) { + if (value.isFile()) { + fList.add(value.getName()); + if (!silent) + sender.sendMessage(ChatColor.GRAY + "[" + (fList.size() + offset) + "] " + value.getName()/*.substring(0, fl[x].getName().length()-4)*/); + } + } + + return fList; + } + + protected List pageFormat(List rawPages) { + //adds newline if content does not exceed page limit + if (rawPages.get(rawPages.size() - 1).length() <= 254 && !rawPages.get(rawPages.size() - 1).isEmpty()) { + rawPages.set(rawPages.size() - 1, (rawPages.get(rawPages.size() - 1)) + "\n"); + } + + //combines lines until page limit is reached + if (rawPages.size() > 1) { + if (rawPages.get(rawPages.size() - 2).length() + rawPages.get(rawPages.size() - 1).length() <= 256) { + rawPages.set(rawPages.size() - 2, (rawPages.get(rawPages.size() - 2)) + (rawPages.get(rawPages.size() - 1))); + rawPages.remove(rawPages.size() - 1); + } + } + + + //splits page if it is too long + if (rawPages.get(rawPages.size() - 1).length() > 256) { + int lS;//the last space before a pagebreak + while (rawPages.get(rawPages.size() - 1).length() > 256) { + //avoiding word split requires spaces + if (rawPages.get(rawPages.size() - 1).substring(0, 256).contains(" ")) { + lS = rawPages.get(rawPages.size() - 1).substring(0, 256).lastIndexOf(" "); + rawPages.add(rawPages.get(rawPages.size() - 1).substring(lS + 1)); + //adds newline if content does not exceed page limit + if (rawPages.get(rawPages.size() - 1).length() <= 254) { + rawPages.set(rawPages.size() - 1, (rawPages.get(rawPages.size() - 1)) + "\n"); + } + //removes excess page content + rawPages.set(rawPages.size() - 2, rawPages.get(rawPages.size() - 2).substring(0, lS)); + } else//if solid wall of text + { + rawPages.add(rawPages.get(rawPages.size() - 1).substring(256)); + //adds newline if content does not exceed page limit + if (rawPages.get(rawPages.size() - 1).length() <= 254) { + rawPages.set(rawPages.size() - 1, (rawPages.get(rawPages.size() - 1)) + "\n"); + } + //removes excess page content + rawPages.set(rawPages.size() - 2, rawPages.get(rawPages.size() - 2).substring(0, 256)); + } + } + } + return rawPages; + } + + protected List cleanList(List rawPages) { + String nullcheck; + //String[] pages; + //clears rawpages of bad values + ListIterator li = rawPages.listIterator(); + while (li.hasNext()) { + nullcheck = li.next(); + if (nullcheck == null) { + li.remove(); + } else { + if (nullcheck.replace(" ", "").isEmpty()) { + li.remove(); + } + } + } + /*pages = new String[li.previousIndex()+1]; + while(li.hasPrevious()) + {pages[li.previousIndex()] = li.previous();}*/ + + return rawPages; + } + + protected ItemStack bookEncryption(Player player, String key, String style, String groupName) { + StringBuilder ikey = new StringBuilder();//converts user supplied key into integer form + for (int x = 0; x < key.length(); x++) { + ikey.append(Character.getNumericValue(Character.codePointAt(key, x))); + } + + BookMeta book = (BookMeta) player.getItemInHand().getItemMeta(); + + if (!book.hasPages()) { + player.sendMessage(ChatColor.RED + "Book is empty!"); + return null; + } + + BookMeta nuMeta = null; + boolean wasSaved = (groupName.equalsIgnoreCase("")) ? eSave(player, book, ikey.toString()) : (nuMeta = groupESave(player, book, ikey.toString(), groupName)) != null; + if (wasSaved) { + ItemStack ecbook; + List ecPages = new ArrayList<>(); + List nuPages; + + if (style.equalsIgnoreCase("dna")) { + GenenCrypt gc = new GenenCrypt(ikey.substring(0, (ikey.length())));//Length used to be divided by /4 + for (int x = 0; x < book.getPages().size(); x++) { + ecPages.add(gc.encrypt(book.getPage(x + 1))); + } + } else if (style.equalsIgnoreCase("magic")) { + for (int i = 0; i < book.getPages().size(); i++) { + String page = book.getPage(i + 1); + page = "ァk" + page.replace("ァ", ""); + + ecPages.add(page); + } + } else { + SubstitutionCipher sc = new SubstitutionCipher(); + for (int x = 0; x < book.getPages().size(); x++) { + ecPages.add(sc.encrypt(book.getPage(x + 1), ikey.toString())); + } + } + + ecPages = pageFormat(ecPages); + nuPages = cleanList(ecPages); + + player.sendMessage(ChatColor.GREEN + "Book encrypted!"); + ecbook = new ItemStack(Material.WRITTEN_BOOK); + book.setPages(nuPages); + ecbook.setItemMeta(book); + ecbook.setAmount(player.getItemInHand().getAmount()); + if (nuMeta != null) + ecbook.setItemMeta(nuMeta); + + return ecbook; + } + return null; + } + + protected void deleteBook(CommandSender sender, String fname, Boolean ispub) { + //checks if player is using list number to load + for (int x = 0; x < fname.length(); x++) { + if (!Character.isDigit(fname.charAt(x))) { + break; + } + if (x == fname.length() - 1 && Character.isDigit(fname.charAt(x)) && loadList.containsKey(sender.getName())) { + if (Integer.parseInt(fname) <= loadList.get(sender.getName()).size()) { + fname = loadList.get(sender.getName()).get(Integer.parseInt(fname) - 1); + } + } + } + + File file; + //TODO Convert namecheck into a method maybe + NameCheck: + { + //Extension is already present + if (fname.lastIndexOf(".") != -1) { + if (fname.substring(fname.lastIndexOf(".")).equals(".yml") || fname.substring(fname.lastIndexOf(".")).equals(".txt")) { + file = (ispub) ? new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + fname) : + new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + cleanString(sender.getName()) + SLASH + fname); + + if (!file.isFile()) { + sender.sendMessage(ChatColor.RED + "Incorrect file name!"); + return; + } + break NameCheck; + } + } + + //No extension present + file = (ispub) ? new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + fname + ".yml") : + new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + cleanString(sender.getName()) + SLASH + fname + ".yml"); + + if (!file.isFile()) { + file = (ispub) ? new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + fname + ".txt") : + new File(this.getDataFolder().getAbsolutePath() + SLASH + "Books" + SLASH + cleanString(sender.getName()) + SLASH + fname + ".txt"); + + if (!file.isFile()) { + sender.sendMessage(ChatColor.RED + "Incorrect file name!"); + return; + } + } + } + + try { + file.delete(); + } catch (Exception e) { + sender.sendMessage(ChatColor.RED + "Deletion failed!"); + return; + } + + sender.sendMessage(ChatColor.GREEN + "\"" + fname + "\" deleted successfully"); + } + + protected void unsign(Player player) { + BookMeta oldbook = (BookMeta) player.getItemInHand().getItemMeta(); + ItemStack newbook = new ItemStack(Material.WRITABLE_BOOK); + newbook.setItemMeta(oldbook); + + player.setItemInHand(newbook); + } + + protected boolean BooksHavePrice() { + return (bookPriceType != null && bookPriceQuantity > 0); + } + + protected boolean printBook(Player player, int numCopies) { + if (bookPriceType == Material.AIR) { + if ((BooksWithoutBorders.eco.getBalance(player.getName()) - (bookPriceQuantity * numCopies)) >= 0) { + BooksWithoutBorders.eco.withdrawPlayer(player.getName(), (bookPriceQuantity * numCopies)); + player.sendMessage(ChatColor.GREEN + BooksWithoutBorders.eco.format(bookPriceQuantity * numCopies) + " withdrawn to create " + numCopies + " book(s)"); + player.sendMessage(ChatColor.GREEN + "New balance: " + BooksWithoutBorders.eco.format(BooksWithoutBorders.eco.getBalance(player.getName()))); + return true; + } else { + player.sendMessage(ChatColor.RED + BooksWithoutBorders.eco.format(bookPriceQuantity * numCopies) + " is required for this command!"); + return false; + } + } else if (player.getInventory().contains(bookPriceType, (int) bookPriceQuantity * numCopies)) { + + int clearedAmount = 0, reqAmount = (int) bookPriceQuantity * numCopies; + while (clearedAmount != reqAmount) { + if (player.getInventory().getItem(player.getInventory().first(bookPriceType)).getAmount() <= reqAmount - clearedAmount) { + clearedAmount += player.getInventory().getItem(player.getInventory().first(bookPriceType)).getAmount(); + player.getInventory().clear(player.getInventory().first(bookPriceType)); + } else { + clearedAmount = reqAmount; + player.getInventory().getItem(player.getInventory().first(bookPriceType)).setAmount(player.getInventory().getItem(player.getInventory().first(bookPriceType)).getAmount() - (clearedAmount)); + } + } + return true; + } + + player.sendMessage(ChatColor.RED + String.valueOf((int) bookPriceQuantity * numCopies) + " " + bookPriceType.toString() + "(s) are required for this command!"); + return false; + } + + protected boolean printBook(Player player) { + return printBook(player, 1); + } + + protected boolean isAuthor(Player player, BookMeta book) { + if (cleanString(player.getName()).equalsIgnoreCase(cleanString(book.getAuthor()))) + return true; + + player.sendMessage(ChatColor.RED + "You must be the author of this book to use this command!"); + return false; + } +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBordersListener.java b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBordersListener.java new file mode 100644 index 0000000..38d54e2 --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBordersListener.java @@ -0,0 +1,432 @@ +package net.knarcraft.bookswithoutborders; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Sign; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerItemHeldEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.inventory.meta.ItemMeta; + +/** + * A listener for relevant events + */ +public class BooksWithoutBordersListener implements Listener { + + private final String slash = BooksWithoutBorders.SLASH; + private final String bookFolder = BooksWithoutBorders.bwb.getDataFolder().getAbsolutePath() + slash + "Books" + slash; + + /** + * Updates old books to a newer format + * + * @param player

The player holding the book

+ * @param oldBook

Metadata about the held book

+ * @return

An updated book

+ */ + public ItemStack updateBook(Player player, BookMeta oldBook) { + //handles hacked title-less books + if (oldBook.getTitle() == null || oldBook.getTitle().length() < 3) { + return null; + } + + if (oldBook.getTitle().substring(oldBook.getTitle().length() - 3).equalsIgnoreCase("[U]")) { + String fileName; + + if (oldBook.getAuthor() != null && oldBook.getAuthor().equalsIgnoreCase("unknown")) { + //Unknown author is ignored + fileName = oldBook.getTitle(); + } else { + fileName = oldBook.getTitle() + BooksWithoutBorders.titleAuthorSeparator + oldBook.getAuthor(); + } + + String cleanPlayerName = BooksWithoutBorders.bwb.cleanString(player.getName()); + + String[] possiblePaths = new String[]{ + bookFolder + fileName + ".yml", + bookFolder + fileName + ".txt", + bookFolder + cleanPlayerName + slash + fileName + ".yml", + bookFolder + cleanPlayerName + slash + fileName + ".txt" + }; + + for (String path : possiblePaths) { + File file = new File(path); + if (file.isFile()) { + return BooksWithoutBorders.bwb.loadBook(player, fileName, "true", "player"); + } + } + return null; + } + + return null; + } + + @EventHandler + public void onHold(PlayerItemHeldEvent e) { + Player player = e.getPlayer(); + int selectedSlot = e.getNewSlot(); + PlayerInventory playerInventory = player.getInventory(); + ItemStack selectedItem = playerInventory.getItem(selectedSlot); + + //Ignore irrelevant items + if (selectedItem == null || selectedItem.getType() != Material.WRITTEN_BOOK) { + return; + } + + ItemMeta itemMetadata = selectedItem.getItemMeta(); + if (itemMetadata == null) { + return; + } + + //Update the book the user is viewing + updateBookInHand(player, itemMetadata, true); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent e) { + //Handle new players + if (!BooksWithoutBorders.bwb.hasPlayedBefore(e.getPlayer().getName())) { + Player player = e.getPlayer(); + boolean sendMessage = true; + + //Gives new players necessary books + for (String bookName : BooksWithoutBorders.firstBooks) { + sendMessage = giveBookToNewPlayer(bookName, player, sendMessage); + } + } + + //Updates any books in either hand + ItemStack mainHandItem = e.getPlayer().getInventory().getItemInMainHand(); + ItemStack offHandItem = e.getPlayer().getInventory().getItemInOffHand(); + if (mainHandItem.getType() == Material.WRITTEN_BOOK) { + ItemMeta itemMetadata = mainHandItem.getItemMeta(); + updateBookInHand(e.getPlayer(), itemMetadata, true); + } + if (offHandItem.getType() == Material.WRITTEN_BOOK) { + ItemMeta itemMetadata = offHandItem.getItemMeta(); + updateBookInHand(e.getPlayer(), itemMetadata, false); + } + } + + /** + * Gives a book to a player joining for the first time + * @param bookName

The name of the book to give

+ * @param player

The player which just joined

+ * @param sendMessage

Whether to send a message to the joining player

+ * @return

True if a message has yet to be sent

+ */ + private boolean giveBookToNewPlayer(String bookName, Player player, boolean sendMessage) { + + if (!bookName.equalsIgnoreCase(" ")) { + //handles loadlist numbers + for (int x = 0; x < bookName.length(); x++) { + if (!Character.isDigit(bookName.charAt(x))) { + break; + } + if (x == bookName.length() - 1) { + BooksWithoutBorders.loadList.put(player.getName(), BooksWithoutBorders.bwb.listFiles(player, true, 0, true)); + } + } + + + ItemStack newBook = BooksWithoutBorders.bwb.loadBook(player, bookName, "true", "public"); + if (newBook != null) { + player.getInventory().addItem(newBook); + } + + if (!BooksWithoutBorders.fBMessage.equalsIgnoreCase(" ") && newBook != null && sendMessage) { + sendMessage = false; + final Player fp = player; + BooksWithoutBorders.bwb.getServer().getScheduler().scheduleSyncDelayedTask(BooksWithoutBorders.bwb, + () -> fp.sendMessage(BooksWithoutBorders.fBMessage), 40L); + } + } + return sendMessage; + } + + /** + * Updates a book in one of the player's hands + * + * @param player

The player to update

+ * @param itemMetadata

Information about the held book

+ * @param mainHand

Whether to update the book in the player's main hand

+ */ + private void updateBookInHand(Player player, ItemMeta itemMetadata, boolean mainHand) { + PlayerInventory playerInventory = player.getInventory(); + ItemStack updatedBook = updateBook(player, (BookMeta) itemMetadata); + if (updatedBook != null) { + if (mainHand) { + playerInventory.setItemInMainHand(updatedBook); + } else { + playerInventory.setItemInOffHand(updatedBook); + } + } + } + + @EventHandler + public void onSignChange(SignChangeEvent e) { + if (e.getLine(0).equalsIgnoreCase("[BwB]") && e.getPlayer().hasPermission("bookswithoutborders.signs")) { + e.setLine(0, ChatColor.DARK_GREEN + "[BwB]"); + + if ((e.getLine(1).equalsIgnoreCase("[Encrypt]") || e.getLine(1).equalsIgnoreCase("[Decrypt]") || e.getLine(1).equalsIgnoreCase("[Give]")) && e.getLine(2).replace(" ", "").length() > 0) { + e.setLine(1, ChatColor.DARK_BLUE + e.getLine(1)); + if (e.getLine(1).equalsIgnoreCase(ChatColor.DARK_BLUE + "[Encrypt]") || e.getLine(1).equalsIgnoreCase(ChatColor.DARK_BLUE + "[Decrypt]")) { + e.setLine(2, ChatColor.MAGIC + e.getLine(2)); + e.setLine(3, ChatColor.DARK_BLUE + e.getLine(3)); + } + + if (e.getLine(1).equalsIgnoreCase(ChatColor.DARK_BLUE + "[Give]")) { + if (e.getLine(2).length() > 13 || e.getLine(3).length() > 13) { + e.getPlayer().sendMessage(ChatColor.RED + "[Give] signs' 3rd and 4th lines must be 13 characters or less!"); + e.setLine(2, ChatColor.DARK_RED + e.getLine(2)); + e.setLine(3, ChatColor.DARK_RED + e.getLine(3)); + return; + } + + //Tests if a full file name has been supplied + if ((new File(BooksWithoutBorders.bwb.getDataFolder().getAbsolutePath() + BooksWithoutBorders.SLASH + "Books" + BooksWithoutBorders.SLASH + e.getLine(2) + e.getLine(3)).isFile()) || + (new File(BooksWithoutBorders.bwb.getDataFolder().getAbsolutePath() + BooksWithoutBorders.SLASH + "Books" + BooksWithoutBorders.SLASH + e.getLine(2) + e.getLine(3) + ".txt").isFile()) || + (new File(BooksWithoutBorders.bwb.getDataFolder().getAbsolutePath() + BooksWithoutBorders.SLASH + "Books" + BooksWithoutBorders.SLASH + e.getLine(2) + e.getLine(3) + ".yml").isFile())) { + e.setLine(2, ChatColor.DARK_GREEN + e.getLine(2)); + e.setLine(3, ChatColor.DARK_GREEN + e.getLine(3)); + } + + //Tests if a load list number has been supplied + else { + File dirFile = new File(BooksWithoutBorders.bwb.getDataFolder().getAbsolutePath() + BooksWithoutBorders.SLASH + "Books"); + for (int x = 0; x < e.getLine(2).length(); x++) { + if (!Character.isDigit(e.getLine(2).charAt(x))) { + e.setLine(2, ChatColor.DARK_RED + e.getLine(2)); + e.setLine(3, ChatColor.DARK_RED + e.getLine(3)); + break; + } + if (x == e.getLine(2).length() - 1) { + List fList = new ArrayList<>(); + for (int y = 0; y < dirFile.list().length; y++) { + if (dirFile.listFiles()[y].isFile()) { + fList.add(dirFile.listFiles()[y].getName()); + } + } + + try { + if (Integer.parseInt(e.getLine(2)) >= 0 && Integer.parseInt(e.getLine(2)) <= fList.size()) { + BooksWithoutBorders.loadList.put(e.getPlayer().getName(), BooksWithoutBorders.bwb.listFiles(e.getPlayer(), true, 0, true)); + e.setLine(2, ChatColor.DARK_GREEN + e.getLine(2)); + e.setLine(3, ChatColor.DARK_GREEN + e.getLine(3)); + } else { + e.setLine(2, ChatColor.DARK_RED + e.getLine(2)); + e.setLine(3, ChatColor.DARK_RED + e.getLine(3)); + } + } catch (NumberFormatException ignored) { + } + } + } + } + } + } else { + e.setLine(1, ChatColor.DARK_RED + e.getLine(1)); + } + } + } + + @EventHandler + public void onClick(PlayerInteractEvent e) { + if (e.getClickedBlock() == null) { + return; + } + + Material clickedBlockType = e.getClickedBlock().getType(); + Player player = e.getPlayer(); + PlayerInventory playerInventory = player.getInventory(); + EquipmentSlot hand = e.getHand(); + if (hand == null) { + return; + } + ItemStack heldItem = playerInventory.getItem(hand); + Material heldItemType = heldItem.getType(); + + if (e.getAction() == Action.RIGHT_CLICK_BLOCK && (Tag.SIGNS.isTagged(clickedBlockType) || Tag.WALL_SIGNS.isTagged(clickedBlockType))) { + //The player right-clicked a sign + Sign sign = (Sign) e.getClickedBlock().getState(); + if (signLineEquals(sign, 0, "[BooksWithoutBorders]", ChatColor.DARK_GREEN)) { + + if (signLineEquals(sign, 1, "[Encrypt]", ChatColor.DARK_BLUE)) { + encryptHeldBookUsingSign(sign, heldItemType, player, hand); + } else if (signLineEquals(sign, 1, "[Decrypt]", ChatColor.DARK_BLUE)) { + decryptHeldBookUsingSign(sign, heldItemType, player, hand); + } else if (signLineEquals(sign, 1, "[Give]", ChatColor.DARK_BLUE) && + getSignLineColor(sign, 2) == ChatColor.DARK_GREEN) { + giveBook(sign, player); + } + } + } else if (heldItemType == Material.WRITTEN_BOOK && (e.getAction() == Action.LEFT_CLICK_AIR + || e.getAction() == Action.LEFT_CLICK_BLOCK)) { + BookMeta oldBook = (BookMeta) heldItem.getItemMeta(); + if (oldBook == null) { + return; + } + + decryptBook(oldBook, player, heldItem, hand); + } + } + + /** + * Decrypts a book decryptable for a given group + * + * @param oldBook

Metadata for the encrypted book

+ * @param player

The player decrypting the book

+ * @param heldItem

The type of the held book

+ * @param hand

The hand the player is using to hold the book

+ */ + private void decryptBook(BookMeta oldBook, Player player, ItemStack heldItem, EquipmentSlot hand) { + ItemStack newBook; + + //Check if the book is encrypted by Books Without Borders + if (!oldBook.hasLore() || oldBook.getLore() == null || !oldBook.getLore().get(0).contains(" encrypted]")) { + return; + } + + String groupName = oldBook.getLore().get(0).substring(3).split(" encrypted")[0]; + + //Permission check + if (!player.hasPermission("bookswithoutborders.decrypt." + groupName) && + !(BooksWithoutBorders.adminDecrypt && player.hasPermission("bookswithoutborders.admin"))) { + return; + } + + String fileName = oldBook.getTitle() + BooksWithoutBorders.titleAuthorSeparator + oldBook.getAuthor(); + + String encryptionFile = BooksWithoutBorders.bwb.cleanString(groupName) + slash + fileName + ".yml"; + + File file = new File(bookFolder + "Encrypted" + slash + encryptionFile); + if (!file.isFile()) { + file = new File(bookFolder + fileName + ".txt"); + if (!file.isFile()) { + return; + } + } + newBook = BooksWithoutBorders.bwb.loadBook(player, fileName, "true", groupName, heldItem.getAmount()); + + player.getInventory().setItem(hand, newBook); + player.closeInventory(); + player.sendMessage(ChatColor.GREEN + "Book auto-decrypted!"); + } + + /** + * Encrypts and replaces the player's used book + * + * @param sign

The clicked sign

+ * @param heldItemType

The item type used to click the sign

+ * @param player

The player which clicked the sign

+ * @param hand

The EquipmentSlot of the used hand

+ */ + private void encryptHeldBookUsingSign(Sign sign, Material heldItemType, Player player, EquipmentSlot hand) { + ItemStack eBook; + if (heldItemType == Material.WRITTEN_BOOK) { + player.closeInventory(); + eBook = BooksWithoutBorders.bwb.bookEncryption(player, sign.getLine(2).substring(2), sign.getLine(3).substring(2), ""); + if (eBook != null) { + player.getInventory().setItem(hand, eBook); + } + } + } + + /** + * Gives a player the book specified on a sign + * + * @param sign

The sign the user clicked

+ * @param player

The player which clicked the sign

+ */ + private void giveBook(Sign sign, Player player) { + String fileName = sign.getLine(2).substring(2); + boolean isLoadListNumber = false; + + for (int x = 0; x < fileName.length(); x++) { + if (!Character.isDigit(fileName.charAt(x))) + break; + if (x == fileName.length() - 1) { + BooksWithoutBorders.loadList.put(player.getName(), BooksWithoutBorders.bwb.listFiles(player, true, 0, true)); + isLoadListNumber = true; + } + } + + if (!isLoadListNumber && sign.getLine(3).length() >= 2) + fileName = sign.getLine(2).substring(2) + sign.getLine(3).substring(2); + + ItemStack newBook = BooksWithoutBorders.bwb.loadBook(player, fileName, "true", "public"); + + if (newBook != null) { + player.getInventory().addItem(newBook); + player.sendMessage(ChatColor.GREEN + "Received book!"); + } else { + player.sendMessage(ChatColor.RED + "Book failed to load!"); + } + } + + /** + * Decrypts and replaces the player's used book + * + * @param sign

The clicked sign

+ * @param heldItemType

The item type used to click the sign

+ * @param player

The player which clicked the sign

+ * @param hand

The EquipmentSlot of the used hand

+ */ + private void decryptHeldBookUsingSign(Sign sign, Material heldItemType, Player player, EquipmentSlot hand) { + //Decrypt the held book and replace it + if (heldItemType == Material.WRITTEN_BOOK) { + player.closeInventory(); + + //Converts user supplied key into integer form + StringBuilder key = new StringBuilder(); + String lineText = ChatColor.stripColor(sign.getLine(2)); + for (int x = 0; x < lineText.length(); x++) { + key.append(Character.getNumericValue(Character.codePointAt(lineText, x))); + } + + ItemStack book = BooksWithoutBorders.bwb.eLoad(player, key.toString(), false); + if (book != null) { + player.getInventory().setItem(hand, book); + player.sendMessage(ChatColor.GREEN + "Book decrypted!"); + } + } + } + + /** + * Gets the color of a line on a sign + * + * @param sign

The sign to check

+ * @param lineNumber

The line number to check

+ * @return

The color of the sign

+ */ + private ChatColor getSignLineColor(Sign sign, int lineNumber) { + return ChatColor.getByChar(sign.getLine(lineNumber).substring(0, 2)); + } + + /** + * Checks if a line on a sign equals some string, and that the sign is the correct color + * + * @param sign

The sign to read

+ * @param lineNumber

The sign line to read

+ * @param compareTo

The string to look for

+ * @param color

The color to match

+ * @return

True if the given string is what's on the sign

+ */ + private boolean signLineEquals(Sign sign, int lineNumber, String compareTo, ChatColor color) { + String line = sign.getLine(lineNumber); + return line.equalsIgnoreCase(color + compareTo); + } + +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/GenenCrypt.java b/src/main/java/net/knarcraft/bookswithoutborders/GenenCrypt.java new file mode 100644 index 0000000..bd22a85 --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/GenenCrypt.java @@ -0,0 +1,170 @@ +package net.knarcraft.bookswithoutborders; + +import java.util.Random; +import java.util.ArrayList; +import java.util.HashMap; + +public class GenenCrypt { + + private final Random ranGen; + private final String[] bases; + private final ArrayList originalCodonList; + private final ArrayList shuffledCodonList; + private final String[] charList; + private final HashMap codonTable; + private final HashMap decryptTable; + private final String key; + + public GenenCrypt(String key){ + + // define the initial, unshuffled codon list of 4 base codons + originalCodonList = new ArrayList<>(); + bases = new String[]{"A", "T", "G", "C"}; + for(int i = 0; i < 4; i++){ + for(int j = 0; j < 4; j++){ + for(int k = 0; k < 4; k++){ + for(int l = 0; l < 4; l++){ + originalCodonList.add("" + bases[i] + bases[j] + bases[k] + bases[l]); + } + } + } + } + + // make a random number generator with a seed based on the key + this.key = key; + long longKey = 0; + for(int i = 0; i < key.length(); i++){ + longKey += (int)key.charAt(i); + } + ranGen = new java.util.Random(longKey); + + // use the random number generator and the originalCodonList to make a shuffled list + shuffledCodonList = new ArrayList<>(); + while(originalCodonList.size() > 0){ + int index = ranGen.nextInt(originalCodonList.size()); + shuffledCodonList.add(originalCodonList.get(index)); + originalCodonList.remove(index); + } + + // define the characters that can be encoded, 64 in total + // 26 capital letters + // 10 digits + // space, newline, and tab + // the symbols . , ? " ! @ # $ % ^ & * ( ) - + = / _ \ : ; < > + charList = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", " ", "\t", "\n", ".", ",", "?", "\"", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "/", "_", "\\", ":", ";", "<", ">", "|"}; + + // define the codon table to encode text + codonTable = new HashMap<>(); + for(int i = 0; i < charList.length; i++){ + String[] tempArray = new String[]{shuffledCodonList.get(4 * i), shuffledCodonList.get(4 * i + 1), shuffledCodonList.get(4 * i + 2), shuffledCodonList.get(4 * i + 3)}; + //System.out.println(i); + codonTable.put(charList[i], tempArray); + } + + // define the decryption table + decryptTable = new HashMap<>(); + for(int i = 0; i < codonTable.size(); i++){ + String s = charList[i]; + String[] sa = codonTable.get(s); + decryptTable.put(sa[0], s); + decryptTable.put(sa[1], s); + decryptTable.put(sa[2], s); + decryptTable.put(sa[3], s); + } + + + } + + public void printShuffledList(){ + for (String s : shuffledCodonList) { + System.out.println(s); + } + } + public void printOriginalList(){ + for (String s : originalCodonList) { + System.out.println(s); + } + } + public void printCodonTable(){ + // print the codon table + for(int i = 0; i < codonTable.size(); i++){ + String s = charList[i]; + String[] sa = codonTable.get(s); + switch (s) { + case "\t": + System.out.println(i + "\t" + "\\t" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]); + break; + case "\n": + System.out.println(i + "\t" + "\\n" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]); + break; + case " ": + System.out.println(i + "\t" + "\" \"" + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]); + break; + default: + System.out.println(i + "\t" + s + "\t" + sa[0] + ", " + sa[1] + ", " + sa[2] + ", " + sa[3]); + break; + } + } + } + + public String encrypt(String input){ + StringBuilder output = new StringBuilder(); + for(int i = 0; i < input.length(); i++){ + // insert junk bases + int offset = key.charAt(i % key.length()); + StringBuilder junk = new StringBuilder(); + for(int j = 0; j < offset; j++){ + junk.append(bases[ranGen.nextInt(4)]); + } + output.append(junk); + int choose = ranGen.nextInt(4); + String s = ("" + input.charAt(i)).toUpperCase(); + if(codonTable.containsKey(s)){ + String[] sa = codonTable.get(s); + output.append(sa[choose]); + } + } + // add some junk bases to the end of the cipher text + int offset = key.charAt(input.length() % key.length()); + StringBuilder junk = new StringBuilder(); + for(int j = 0; j < offset; j++){ + junk.append(bases[ranGen.nextInt(4)]); + } + output.append(junk); + return output.toString(); + } + + public String decrypt(String in){ + String input = "" + in; + StringBuilder output = new StringBuilder(); + int keyCount = 0; + int junk = key.charAt(keyCount % key.length()); + while(input.length() > junk){ + // cuts out the junk bases + input = input.substring(junk); + // get the codon, decrypt the codon, remove it from the input string + String codon = input.substring(0, 4); + output.append(decryptTable.get(codon)); + input = input.substring(4); + // increment the key counter and update junk + keyCount++; + junk = key.charAt(keyCount % key.length()); + } + return output.toString(); + } + + /*public static void main(String[] args){ + GenenCrypt gc = new GenenCrypt("Another Key"); + gc.printCodonTable(); + System.out.println(); + System.out.println(); + System.out.println(); + System.out.println("Encrypting the line \"Hello World!\""); + String encrypted = gc.encrypt("Hello World!"); + System.out.println(encrypted); + System.out.println(); + System.out.println("Decrypting the ciphertext"); + System.out.println(gc.decrypt(encrypted)); + + }*/ +} diff --git a/src/main/java/net/knarcraft/bookswithoutborders/SubstitutionCipher.java b/src/main/java/net/knarcraft/bookswithoutborders/SubstitutionCipher.java new file mode 100644 index 0000000..89b653e --- /dev/null +++ b/src/main/java/net/knarcraft/bookswithoutborders/SubstitutionCipher.java @@ -0,0 +1,87 @@ +package net.knarcraft.bookswithoutborders; + +import java.math.BigInteger; +import java.util.StringTokenizer; + +public class SubstitutionCipher { + + public SubstitutionCipher() { + + } + + // encrypts a string using a substitution cipher. + // the substitution is made harder to crack by + // using a string for the key, it is converted + // a series of offsets that each character in the + // original message is offset by + public String encrypt(String in, String key) { + StringBuilder output = new StringBuilder(); + if (key != null && key.length() > 0) { + StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key + // converts each number in the key to an integer and adds to an array + int[] offsetArray = new int[tokenizer.countTokens()]; + for (int i = 0; i < offsetArray.length; i++) { + String nt = tokenizer.nextToken(); + + try { + offsetArray[i] = Integer.parseInt(nt); + } catch (NumberFormatException e) { + BigInteger big = new BigInteger(nt); + offsetArray[i] = Math.abs(big.intValue()); + } + } + + int offsetPosition = 0; + for (int i = 0; i < in.length(); i++) { + output.append((char) (in.charAt(i) + offsetArray[offsetPosition])); //encrypts the letter and adds to the output string + // uses the next offset in the key, goes back to first offset if at end of list + if (offsetPosition < offsetArray.length - 1) { + offsetPosition++; + } else { + offsetPosition = 0; + } + } + } + return output.toString(); + } + + // decrypts a string using the same substitution method, + // but in reverse. Could probably be combined into one + // method with a flag for encryption / decryption, but + // I'm lazy. + public String decrypt(String in, String key) { + StringBuilder output = new StringBuilder(); + if (key != null && key.length() > 0) { + StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key + // converts each number in the key to an integer and adds to an array + int[] offsetArray = new int[tokenizer.countTokens()]; + for (int i = 0; i < offsetArray.length; i++) { + offsetArray[i] = Integer.parseInt(tokenizer.nextToken()); + } + int offsetPosition = 0; + for (int i = 0; i < in.length(); i++) { + output.append((char) (in.charAt(i) - offsetArray[offsetPosition])); //encrypts the letter and adds to the output string + // uses the next offset in the key, goes back to first offset if at end of list + if (offsetPosition < offsetArray.length - 1) { + offsetPosition++; + } else { + offsetPosition = 0; + } + } + } + return output.toString(); + } + + + // the one time pad encryption is very secure, and + // encryption works just like decryption, but is + // vulnerable if the same key is used more than once. + public String oneTimePad(String in, String key) { + StringBuilder output = new StringBuilder(); + for (int i = 0; i < in.length(); i++) { + output.append((char) (in.charAt(i) ^ key.charAt(i % key.length()))); + } + return output.toString(); + } +} + diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..4164168 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,81 @@ +name: Books-Without-Borders +version: '${project.version}' +main: net.knarcraft.bookswithoutborders.bookswithoutborders.BooksWithoutBorders +api-version: 1.17 +prefix: Books Without Borders +authors: [ EpicKnarvik97, AkiraAkiba ] +description: A continuation of the original Books Without Borders +softdepend: + - Vault +website: ???? +dev-url: ???? +commands: + BooksWithoutBorders: + description: Lists Books Without Borders's commands and uses. + aliases: bwb +permissions: + bookswithoutborders.admin: + description: Grants all permissions + default: op + children: + bookswithoutborders.use: true + bookswithoutborders.unsign: true + bookswithoutborders.copy: true + bookswithoutborders.loadpublic: true + bookswithoutborders.savepublic: true + bookswithoutborders.encrypt: true + bookswithoutborders.decrypt: true + bookswithoutborders.signs: true + bookswithoutborders.give: true + bookswithoutborders.givepublic: true + bookswithoutborders.settitle: true + bookswithoutborders.setauthor: true + bookswithoutborders.setlore: true + bookswithoutborders.bypassauthoronlycopy: true + bookswithoutborders.bypassbookprice: true + bookswithoutborders.groupencrypt: true + bookswithoutborders.setbookprice: true + bookswithoutborders.use: + description: Allows player to use commands and to save/load/delete in their personal directory + children: + bookswithoutborders.save: true + bookswithoutborders.load: true + bookswithoutborders.delete: true + bookswithoutborders.save: + description: Allows player to save books to their personal directory + bookswithoutborders.load: + description: Allows player to load books from their personal directory + bookswithoutborders.delete: + description: Allows player to delete books from their personal directory + bookswithoutborders.unsign: + description: Allows player to use unsign command + bookswithoutborders.copy: + description: Allows player to use copy command + bookswithoutborders.loadpublic: + description: Allows player to load in the public directory + bookswithoutborders.savepublic: + description: Allows player to save in the public directory + bookswithoutborders.encrypt: + description: Allows player to encrypt books + bookswithoutborders.groupencrypt: + description: Allows player to set group based encryption + bookswithoutborders.decrypt: + description: Allows player to decrypt books + bookswithoutborders.signs: + description: Allows player to create signs that give/encrypt/decrypt books + bookswithoutborders.give: + description: Allows player to give another player one of their privately saved books + bookswithoutborders.givepublic: + description: Allows player to give another player a book from the public directory + bookswithoutborders.settitle: + description: Allows player to set the title of the currenlty held book + bookswithoutborders.setauthor: + description: Allows player to set the author of the currently held book + bookswithoutborders.setlore: + description: Allows player to set the lore of the currently held item + bookswithoutborders.bypassauthoronlycopy: + description: Allows player to ignore Author_Only_Copy config setting + bookswithoutborders.bypassbookprice: + description: Allows player to ignore Price_to_create_book config setting + bookswithoutborders.setbookprice: + description: Allows player to set the cost of creating a book \ No newline at end of file