33 Commits

Author SHA1 Message Date
63ca95d247 Makes formatting on book signing require the format permission
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-14 20:01:34 +02:00
f05a15586a Makes some changes to formatting and un-signing
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
Makes the unsign command convert formatting codes to human editable ones
Makes the format command work on unsigned books
2025-08-14 19:08:21 +02:00
888287b447 Updates the README with new commands and options
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-14 02:20:31 +02:00
5d5ed725d9 Fixes an exception when trying to load an empty book list 2025-08-14 02:19:57 +02:00
bde43e78ca Prevents book migration from locking up the server thread
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-14 01:09:58 +02:00
790e3d1531 Adds a migrate command for fixing book names and text -> yml 2025-08-14 00:23:22 +02:00
4243c484c4 Merge branch 'refs/heads/dev' into string-formatting
# Conflicts:
#	src/main/java/net/knarcraft/bookswithoutborders/command/CommandSave.java
2025-08-13 21:56:47 +02:00
baa0200f20 Fixes a missing space in a command prompt
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-13 21:41:41 +02:00
61957d0e1e Makes a string translatable
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-13 21:39:33 +02:00
f08d378295 Merge fixes
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-10 19:21:14 +02:00
e43649fef4 Merge branch 'refs/heads/dev' into string-formatting
# Conflicts:
#	src/main/java/net/knarcraft/bookswithoutborders/BooksWithoutBorders.java
2025-08-10 19:13:14 +02:00
af094f9931 Adds commands for adding book title pages and for deleting book pages
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-10 19:10:08 +02:00
324658070a Replaces a static string
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-10 16:55:31 +02:00
21b5b9647d Replaces static strings for some commands 2025-08-10 15:50:19 +02:00
32f0f9f7a1 Rewrites encryption
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
Adds an optional real encryption mode, which encrypts pages using AES, without saving plaintext.
Re-implements the old magic encryption in non-real encryption mode.
Fixes incorrect key generation for use in the substitution cipher and the gene cipher.
Removes the option for saving books as txt.
Adds tests for all encryption methods.
Saves all necessary decryption data when storing encrypted books.
Removes the old book updating code.
2025-08-10 14:23:18 +02:00
0ac051e24e Improves tab-completions for books with spaces
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-09 13:56:50 +02:00
fef38b894f Makes deletebook support filenames with spaces
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-08 15:23:07 +02:00
b5bff2400b Makes givebook support filenames with spaces 2025-08-08 15:05:54 +02:00
b963f83dee Supports spaces in author filtering
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-08 12:07:18 +02:00
a986411990 Improves tab-completion when loading books with spaces 2025-08-08 11:33:46 +02:00
6cd8895cce Adds full support for spaces in book names, and fixes unsigned book loading 2025-08-08 02:06:08 +02:00
f6d5108c7b Fixes an exception encountered when importing a text file as a book
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-08 00:49:54 +02:00
fb225a2351 Cleans up the decrypt command somewhat 2025-08-08 00:48:04 +02:00
e23e861e32 Fixes some problems related to the changed economy class 2025-08-07 20:33:54 +02:00
aa02f5ca2b Changes some static classes into instantiated classes 2025-08-07 20:10:30 +02:00
150dff7a03 Removes static strings from the clear command 2025-08-07 17:11:59 +02:00
ad08d65c80 Merge branch 'refs/heads/dev' into string-formatting 2025-08-07 16:42:34 +02:00
8affc42eaa Fixes a hard-coded , separator and improves book list formatting
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-07 16:35:43 +02:00
b1da544109 Starts adding string translations and cleanup 2025-08-07 16:00:37 +02:00
2146f00014 Fixes an error not differentiating between the two command variations
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-06 00:00:50 +02:00
14dd99ac85 Adds an error when /settitle is given a book name > 32 characters
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
2025-08-05 16:27:33 +02:00
a3e3326786 Removes unnecessary duplicate file name cleaning 2025-08-05 04:49:19 +02:00
73157330d8 Enables bookshelf peeking by default 2025-08-05 04:48:31 +02:00
68 changed files with 3211 additions and 1214 deletions

View File

@@ -12,7 +12,7 @@ Books without Borders has got your back!
### Features ### Features
- Export written books and book and quills to .txt or .yml files - Export written books and book and quills to .yml files
- Import books from files as written books or unsigned books - Import books from files as written books or unsigned books
- Text files can be any length, and the import process fits the content to the correct page length - Text files can be any length, and the import process fits the content to the correct page length
- Books can be saved privately, or to a directory visible server wide - Books can be saved privately, or to a directory visible server wide
@@ -24,47 +24,70 @@ Books without Borders has got your back!
- Configurable option to require certain items or pay via Vault compatible economy to create books via command - Configurable option to require certain items or pay via Vault compatible economy to create books via command
- Add lore to any item with a simple command - Add lore to any item with a simple command
- Supports adding and saving color to title, lore, and book contents - Supports adding and saving color to title, lore, and book contents
- Color and formatting codes can be manually turned into formatting using /formatbook - Color and formatting codes can be manually turned into formatting using `/formatbook`
- Clear a book, and start anew with /clearbook - Clear a book, and start anew with `/clearbook`
- Formatting and color codes can be turned into formatting once any book is signed. This is enabled through a config - Formatting and color codes can be turned into formatting once any book is signed. This is enabled through a config
value value
- Change generation of books. Create tattered books for your RPG server! - Change generation of books. Create tattered books for your RPG server!
- Optionally, make it impossible to duplicate the original version of a book - Optionally, make it impossible to duplicate the original version of a book
- Optionally, hit a bookshelf while sneaking to display the contained books. The bookshelf can be given a title and lore - Optionally, hit a bookshelf while sneaking to display the contained books. The bookshelf can be given a title and lore
with `/setBookshelfData`.
- Easily add a title page or chapter page (for an unsigned book, you can add a blank page as well)
with `/addBookTitlePage`.
- Remove extra blank pages or unneeded chapter pages with `/deleteBookPage`
#### Group encryption #### Group encryption
- Group encryption allows every player with the bookswithoutborders.decrypt.\<group> permission to decrypt the encrypted - Group encryption allows every player with the bookswithoutborders.decrypt.\<group> permission to decrypt the encrypted
book without using a password. book without using a password.
### Migration from previous versions
- The `/migrateBooks` command allows for easy fixing of old book naming, changing the title author separator (the
default changed from `,` to `¤`, as a comma is a natural character to use in a title), or updating books saved as txt
to yml.
- Note that if real encryption is enabled, migrating the books will store them unencrypted (like they used to before
real encryption was implemented) afterward.
### Book formatting
- Formatting codes are automatically turned back into `&` codes after un-signing a book.
- `/formatbook` can be used on an unsigned book to preview formatting, but note that RGB colors will show up as
incorrect colors. That's just how that works. You must sign the book to see the real result.
- `/formatbook` can be used on a signed book (if `Format_Book_After_Signing` is disabled) in order to make any color or
formatting codes in the book display as intended.
### Commands: ### Commands:
An in-game description of available commands is available through the /bwb command. An in-game description of available commands is available through the /bwb command.
| Command | Arguments | Description | | Command | Arguments | Permission | Description |
|----------------------|----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |----------------------|----------------------------------------------------------------------------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| /bookswithoutborders | None | Displays information about commands (and permissions if the user has bookswithoutborders.admin) | | /bookswithoutborders | None | | Displays information about commands (and permissions if the user has bookswithoutborders.admin) |
| /copybook | \<# of copies> | Copies the book the player is holding | | /copybook | \<# of copies> | bookswithoutborders.copy | Copies the book the player is holding |
| /decryptbook | \<key> | Decrypts the book the player is holding. "key" is required and MUST be IDENTICAL to the key used to encrypt the held book | | /decryptbook | \<key> | bookswithoutborders.decrypt | Decrypts the book the player is holding. "key" is required and MUST be IDENTICAL to the key used to encrypt the held book |
| /deletebook | \<file name or number> | Deletes the specified file in the player's directory | | /deletebook | \<file name or number> | bookswithoutborders.delete | Deletes the specified file in the player's directory |
| /deletepublicbook | \<file name or number> | Same as deletebook, but deletes files in the public directory | | /deletepublicbook | \<file name or number> | bookswithoutborders.admin | Same as deletebook, but deletes files in the public directory |
| /encryptbook | \<key> \[encryption style] | Encrypts the book the player is holding. "key" is required and can be any phrase or number excluding spaces. "style" is not required. Possible values are "DNA" or "" | | /encryptbook | \<key> \[encryption style] | bookswithoutborders.encrypt | Encrypts the book the player is holding. "key" is required and can be any phrase or number excluding spaces. "style" is not required. Possible values are "DNA" or "" |
| /formatbook | None | Formats the held written book (converts color and formatting codes to the corresponding formatted text) | | /formatbook | None | bookswithoutborders.format | Formats the held written book (converts color and formatting codes to the corresponding formatted text) |
| /givebook | \<file name or number> \<playername> \[# of copies (num)] \[signed (true/false)] | Gives the selected player a book from your personal directory | | /givebook | \<file name or number> \<playername> \[# of copies (num)] \[signed (true/false)] | bookswithoutborders.give | Gives the selected player a book from your personal directory |
| /givepublicbook | \<file name or number> \<playername> \[# of copies (num)] \[signed (true/false)] | Same as givebook, but uses books from the public directory | | /givepublicbook | \<file name or number> \<playername> \[# of copies (num)] \[signed (true/false)] | bookswithoutborders.givepublic | Same as givebook, but uses books from the public directory |
| /loadbook | \<file name or number> \[# of copies] \[signed (true/false)] | Creates a book from the specified file and gives it to the player. If no file is specified, a list of available files is returned. If true is specified, the book will be signed, if false it will be unsigned | | /loadbook | \<file name or number> \[# of copies] \[signed (true/false)] | bookswithoutborders.load | Creates a book from the specified file and gives it to the player. If no file is specified, a list of available files is returned. If true is specified, the book will be signed, if false it will be unsigned |
| /loadpublicbook | \<file name or number> \[# of copies] \[signed (true/false)] | Same as loadbook, but views files in the public directory | | /loadpublicbook | \<file name or number> \[# of copies] \[signed (true/false)] | bookswithoutborders.loadpublic | Same as loadbook, but views files in the public directory |
| /reload | None | Reloads BwB's configuration file | | /reload | None | bookswithoutborders.reload | Reloads BwB's configuration file |
| /savebook | \[overwrite (true/false)] | Saves the book the player is holding to a text file in a private directory. If true is specified, a book of the same name by the same author will be overwritten by the new book | | /savebook | \[overwrite (true/false)] | bookswithoutborders.save | Saves the book the player is holding to a text file in a private directory. If true is specified, a book of the same name by the same author will be overwritten by the new book |
| /savepublicbook | \[overwrite (true/false)] | Same as savebook, but saves files in the public directory | | /savepublicbook | \[overwrite (true/false)] | bookswithoutborders.savepublic | Same as savebook, but saves files in the public directory |
| /setbookauthor | \<author> | Sets the author of the book the player is holding | | /setbookauthor | \<author> | bookswithoutborders.setauthor | Sets the author of the book the player is holding |
| /setbookgeneration | \<generation> | Sets the generation of the held book (ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, TATTERED) | | /setbookgeneration | \<generation> | bookswithoutborders.setgeneration | Sets the generation of the held book (ORIGINAL, COPY_OF_ORIGINAL, COPY_OF_COPY, TATTERED) |
| /setbookprice | \<item/eco> \<quantity> | Sets the per-book price to create a book via commands. If "Item", the item in the player's hand in the amount of \<quantity> will be the price. If "Eco", a Vault based economy will be used for price. If neither \<Item/Eco> nor \<quantity> are specified, the current price to create books will be removed. | | /setbookprice | \<item/eco> \<quantity> | bookswithoutborders.setbookprice | Sets the per-book price to create a book via commands. If "Item", the item in the player's hand in the amount of \<quantity> will be the price. If "Eco", a Vault based economy will be used for price. If neither \<Item/Eco> nor \<quantity> are specified, the current price to create books will be removed. |
| /setlore | \<new lore> | Sets the lore of the item the player is holding. Insert the lore_line_separator character to force a new line ("~" by default) | | /setlore | \<new lore> | bookswithoutborders.setlore | Sets the lore of the item the player is holding. Insert the lore_line_separator character to force a new line ("~" by default) |
| /settitle | \<title> | Sets the title of the book/item the player is holding | | /settitle | \<title> | bookswithoutborders.settitle | Sets the title of the book/item the player is holding |
| /unsignbook | None | Un-signs the book the player is holding | | /unsignbook | None | bookswithoutborders.unsign | Un-signs the book the player is holding |
| /clearbook | None | Removes all text from the held un-signed book | | /clearbook | None | bookswithoutborders.clear | Removes all text from the held un-signed book |
| /setBookshelfData | \<delete/name/lore> \text> \[more text] | Sets the name/lore for a bookshelf which is shown when peeking at its contents. | | /setBookshelfData | \<delete/name/lore> \text> \[more text] | bookswithoutborders.editbookshelf | Sets the name/lore for a bookshelf which is shown when peeking at its contents. |
| /addBookTitlePage | \[page index] \[title~description] | bookswithoutborders.addtitlepage | Adds a blank page, title page or chapter page depending on input and whether the book is signed |
| /deleteBookPage | \<page> | bookswithoutborders.deletepage | Deletes one page from a book |
| /migrateBooks | None | bookswithoutborders.admin | Migrates all txt books to yml, and fixes any incorrect filenames. |
### Permissions: ### Permissions:
@@ -73,7 +96,7 @@ An in-game description of available commands is available through the /bwb comma
| bookswithoutborders.* | Grants all permissions | | bookswithoutborders.* | Grants all permissions |
| bookswithoutborders.admin | Grants all permissions | | bookswithoutborders.admin | Grants all permissions |
| bookswithoutborders.use | bookswithoutborders.use - Allows player to use commands to save/load/delete in their personal directory, and peeking at bookshelves if enabled | | bookswithoutborders.use | bookswithoutborders.use - Allows player to use commands to save/load/delete in their personal directory, and peeking at bookshelves if enabled |
| bookswithoutborders.alterbooks | Allows player to change books' data such as lore/title/author/generation/formatting and un-signing books | | bookswithoutborders.alterbooks | Allows player to change books' data such as lore/title/author/generation/formatting and un-signing books, and setting bookshelf data |
| bookswithoutborders.reload | Allows player to reload this plugin | | bookswithoutborders.reload | Allows player to reload this plugin |
| bookswithoutborders.format | Allows a player to format a book | | bookswithoutborders.format | Allows a player to format a book |
| bookswithoutborders.save | Allows a player to save books to their personal directory | | bookswithoutborders.save | Allows a player to save books to their personal directory |
@@ -102,6 +125,8 @@ An in-game description of available commands is available through the /bwb comma
| bookswithoutborders.clear | Allows player to clear the contents of the held writable book | | bookswithoutborders.clear | Allows player to clear the contents of the held writable book |
| bookswithoutborders.peekbookshelf | Allows player to left-click a bookshelf to see the contents of the shelf | | bookswithoutborders.peekbookshelf | Allows player to left-click a bookshelf to see the contents of the shelf |
| bookswithoutborders.editbookshelf | Allows player to set name/lore for bookshelves, used for peeking | | bookswithoutborders.editbookshelf | Allows player to set name/lore for bookshelves, used for peeking |
| bookswithoutborders.addtitlepage | Allows player to add a blank title page to a book |
| bookswithoutborders.deletepage | Allows player to delete a page from a book |
### Signs ### Signs
@@ -126,8 +151,7 @@ The **_decrypt_** sign must have **\[Decrypt]** on its second line. The third li
### Configuration options: ### Configuration options:
| Option | Description | | Option | Description |
|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Save_Books_in_Yaml_Format | Whether to use YAML for saved books instead of just storing them as text |
| Max_Number_of_Duplicates | The maximum number of duplicates of a saved book allowed | | Max_Number_of_Duplicates | The maximum number of duplicates of a saved book allowed |
| Author_Separator | The separator used to separate the book title and the book author | | Author_Separator | The separator used to separate the book title and the book author |
| Lore_line_separator | The separator used to denote a new line in the book/item lore | | Lore_line_separator | The separator used to denote a new line in the book/item lore |
@@ -142,3 +166,4 @@ The **_decrypt_** sign must have **\[Decrypt]** on its second line. The third li
| Format_Book_After_Signing | Whether to automatically format every book when it's signed | | Format_Book_After_Signing | Whether to automatically format every book when it's signed |
| Change_Generation_On_Copy | Whether to display "COPY" or "COPY_OF_COPY" instead of "ORIGINAL" when a book is copied. This also uses the vanilla behavior where a copy of a copy or tattered book cannot be copied further. | | Change_Generation_On_Copy | Whether to display "COPY" or "COPY_OF_COPY" instead of "ORIGINAL" when a book is copied. This also uses the vanilla behavior where a copy of a copy or tattered book cannot be copied further. |
| Enable_Book_Peeking | Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents. | | Enable_Book_Peeking | Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents. |
| Use_Real_Encryption | Enables true AES encryption instead of the very fake legacy encryption. The encryption key is stored in the book file to allow admin decryption, but looking at the encrypted book in the file system, only reveals the encrypted pages. |

View File

@@ -20,7 +20,7 @@
<description>A continuation of the original Books Without Borders</description> <description>A continuation of the original Books Without Borders</description>
<properties> <properties>
<java.version>16</java.version> <java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> </properties>

View File

@@ -1,10 +1,12 @@
package net.knarcraft.bookswithoutborders; package net.knarcraft.bookswithoutborders;
import net.knarcraft.bookswithoutborders.command.CommandAddTitlePage;
import net.knarcraft.bookswithoutborders.command.CommandBooksWithoutBorders; import net.knarcraft.bookswithoutborders.command.CommandBooksWithoutBorders;
import net.knarcraft.bookswithoutborders.command.CommandClear; import net.knarcraft.bookswithoutborders.command.CommandClear;
import net.knarcraft.bookswithoutborders.command.CommandCopy; import net.knarcraft.bookswithoutborders.command.CommandCopy;
import net.knarcraft.bookswithoutborders.command.CommandDecrypt; import net.knarcraft.bookswithoutborders.command.CommandDecrypt;
import net.knarcraft.bookswithoutborders.command.CommandDelete; import net.knarcraft.bookswithoutborders.command.CommandDelete;
import net.knarcraft.bookswithoutborders.command.CommandDeletePage;
import net.knarcraft.bookswithoutborders.command.CommandDeletePublic; import net.knarcraft.bookswithoutborders.command.CommandDeletePublic;
import net.knarcraft.bookswithoutborders.command.CommandEncrypt; import net.knarcraft.bookswithoutborders.command.CommandEncrypt;
import net.knarcraft.bookswithoutborders.command.CommandFormat; import net.knarcraft.bookswithoutborders.command.CommandFormat;
@@ -13,6 +15,7 @@ import net.knarcraft.bookswithoutborders.command.CommandGivePublic;
import net.knarcraft.bookswithoutborders.command.CommandGroupEncrypt; import net.knarcraft.bookswithoutborders.command.CommandGroupEncrypt;
import net.knarcraft.bookswithoutborders.command.CommandLoad; import net.knarcraft.bookswithoutborders.command.CommandLoad;
import net.knarcraft.bookswithoutborders.command.CommandLoadPublic; import net.knarcraft.bookswithoutborders.command.CommandLoadPublic;
import net.knarcraft.bookswithoutborders.command.CommandMigrate;
import net.knarcraft.bookswithoutborders.command.CommandReload; import net.knarcraft.bookswithoutborders.command.CommandReload;
import net.knarcraft.bookswithoutborders.command.CommandSave; import net.knarcraft.bookswithoutborders.command.CommandSave;
import net.knarcraft.bookswithoutborders.command.CommandSavePublic; import net.knarcraft.bookswithoutborders.command.CommandSavePublic;
@@ -24,16 +27,23 @@ import net.knarcraft.bookswithoutborders.command.CommandSetLore;
import net.knarcraft.bookswithoutborders.command.CommandSetTitle; import net.knarcraft.bookswithoutborders.command.CommandSetTitle;
import net.knarcraft.bookswithoutborders.command.CommandUnSign; import net.knarcraft.bookswithoutborders.command.CommandUnSign;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.config.BwBCommand;
import net.knarcraft.bookswithoutborders.config.StaticMessage;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.container.MigrationRequest;
import net.knarcraft.bookswithoutborders.handler.BookshelfHandler; import net.knarcraft.bookswithoutborders.handler.BookshelfHandler;
import net.knarcraft.bookswithoutborders.listener.BookEventListener; import net.knarcraft.bookswithoutborders.listener.BookEventListener;
import net.knarcraft.bookswithoutborders.listener.BookshelfListener; import net.knarcraft.bookswithoutborders.listener.BookshelfListener;
import net.knarcraft.bookswithoutborders.listener.PlayerEventListener; import net.knarcraft.bookswithoutborders.listener.PlayerEventListener;
import net.knarcraft.bookswithoutborders.listener.SignEventListener; import net.knarcraft.bookswithoutborders.listener.SignEventListener;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.formatting.Translator;
import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.UpdateChecker; import net.knarcraft.knarlib.util.UpdateChecker;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -47,39 +57,66 @@ import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSuccessColor;
/** /**
* The main Books Without Borders class * The main Books Without Borders class
*/ */
public class BooksWithoutBorders extends JavaPlugin { public class BooksWithoutBorders extends JavaPlugin {
private static ItemFactory itemFactory;
private static @NotNull Map<UUID, List<String>> playerBooksList = new HashMap<>();
private static @NotNull List<String> publicBooksList = new ArrayList<>();
private static Map<Character, Integer> publicLetterIndex;
private static Map<UUID, Map<Character, Integer>> playerLetterIndex;
private static BooksWithoutBorders booksWithoutBorders; private static BooksWithoutBorders booksWithoutBorders;
private static ConsoleCommandSender consoleSender;
private static BookshelfHandler bookshelfHandler; private ItemFactory itemFactory;
private @NotNull Map<UUID, List<String>> playerBooksList = new HashMap<>();
private @NotNull List<String> publicBooksList = new ArrayList<>();
private Map<Character, Integer> publicLetterIndex;
private Map<UUID, Map<Character, Integer>> playerLetterIndex;
private BookshelfHandler bookshelfHandler;
private StringFormatter stringFormatter;
private BooksWithoutBordersConfig booksWithoutBordersConfig;
private final Queue<MigrationRequest> migrationQueue = new LinkedList<>();
/** /**
* Gets the console sender for printing to the console * Logs a message to the console
* *
* @return <p>The console's console sender</p> * @param level <p>The log level to use</p>
* @param message <p>The message to log</p>
*/ */
@NotNull public static void log(@NotNull Level level, @NotNull String message) {
public static ConsoleCommandSender getConsoleSender() { getInstance().getLogger().log(level, message);
return consoleSender; }
/**
* Gets the configuration for BwB
*
* @return <p>The BwB configuration</p>
*/
public static BooksWithoutBordersConfig getConfiguration() {
return getInstance().booksWithoutBordersConfig;
}
/**
* Gets the string formatter
*
* @return <p>The string formatter</p>
*/
public static StringFormatter getStringFormatter() {
return getInstance().stringFormatter;
}
/**
* Gets the migration queue
*
* @return <p>The migration queue</p>
*/
public static Queue<MigrationRequest> getMigrationQueue() {
return getInstance().migrationQueue;
} }
/** /**
@@ -92,17 +129,22 @@ public class BooksWithoutBorders extends JavaPlugin {
@NotNull @NotNull
public static List<String> getAvailableBooks(@NotNull CommandSender sender, boolean getPublic) { public static List<String> getAvailableBooks(@NotNull CommandSender sender, boolean getPublic) {
if (getPublic) { if (getPublic) {
return new ArrayList<>(publicBooksList); return new ArrayList<>(getInstance().publicBooksList);
} else if (sender instanceof Player player) { } else if (sender instanceof Player player) {
UUID playerUUID = player.getUniqueId(); UUID playerUUID = player.getUniqueId();
if (!playerBooksList.containsKey(playerUUID)) { if (!getInstance().playerBooksList.containsKey(playerUUID)) {
List<String> newFiles = BookFileHelper.listFiles(sender, false); List<String> newFiles = BookFileHelper.listFiles(sender, false);
if (newFiles != null) { if (newFiles != null) {
playerBooksList.put(playerUUID, newFiles); getInstance().playerBooksList.put(playerUUID, newFiles);
playerLetterIndex.put(playerUUID, BookFileHelper.populateLetterIndices(newFiles)); getInstance().playerLetterIndex.put(playerUUID, BookFileHelper.populateLetterIndices(newFiles));
} }
} }
return new ArrayList<>(playerBooksList.get(playerUUID)); List<String> books = getInstance().playerBooksList.get(playerUUID);
if (books != null) {
return new ArrayList<>(books);
} else {
return new ArrayList<>();
}
} else { } else {
return new ArrayList<>(); return new ArrayList<>();
} }
@@ -117,9 +159,9 @@ public class BooksWithoutBorders extends JavaPlugin {
@NotNull @NotNull
public static Map<Character, Integer> getLetterIndex(@Nullable UUID playerIndex) { public static Map<Character, Integer> getLetterIndex(@Nullable UUID playerIndex) {
if (playerIndex == null) { if (playerIndex == null) {
return publicLetterIndex; return getInstance().publicLetterIndex;
} else { } else {
Map<Character, Integer> letterIndex = playerLetterIndex.get(playerIndex); Map<Character, Integer> letterIndex = getInstance().playerLetterIndex.get(playerIndex);
return Objects.requireNonNullElseGet(letterIndex, HashMap::new); return Objects.requireNonNullElseGet(letterIndex, HashMap::new);
} }
} }
@@ -136,11 +178,11 @@ public class BooksWithoutBorders extends JavaPlugin {
return; return;
} }
if (updatePublic) { if (updatePublic) {
publicBooksList = newFiles; getInstance().publicBooksList = newFiles;
publicLetterIndex = BookFileHelper.populateLetterIndices(newFiles); getInstance().publicLetterIndex = BookFileHelper.populateLetterIndices(newFiles);
} else if (sender instanceof Player player) { } else if (sender instanceof Player player) {
playerBooksList.put(player.getUniqueId(), newFiles); getInstance().playerBooksList.put(player.getUniqueId(), newFiles);
playerLetterIndex.put(player.getUniqueId(), BookFileHelper.populateLetterIndices(newFiles)); getInstance().playerLetterIndex.put(player.getUniqueId(), BookFileHelper.populateLetterIndices(newFiles));
} }
} }
@@ -148,8 +190,8 @@ public class BooksWithoutBorders extends JavaPlugin {
* Clears book data such as per-player lists and per-player character indexes * Clears book data such as per-player lists and per-player character indexes
*/ */
public static void clearBookData() { public static void clearBookData() {
playerBooksList = new HashMap<>(); getInstance().playerBooksList = new HashMap<>();
playerLetterIndex = new HashMap<>(); getInstance().playerLetterIndex = new HashMap<>();
} }
@@ -163,12 +205,18 @@ public class BooksWithoutBorders extends JavaPlugin {
PluginDescriptionFile pluginDescriptionFile = this.getDescription(); PluginDescriptionFile pluginDescriptionFile = this.getDescription();
String pluginVersion = pluginDescriptionFile.getVersion(); String pluginVersion = pluginDescriptionFile.getVersion();
Translator translator = new Translator();
translator.registerMessageCategory(Translatable.SUCCESS_COPY);
stringFormatter = new StringFormatter(this.getDescription().getName(), translator);
stringFormatter.setColorConversion(ColorConversion.RGB);
stringFormatter.setSuccessColor(ChatColor.of("#A9FF84"));
stringFormatter.setErrorColor(ChatColor.of("#FF84A9"));
booksWithoutBorders = this; booksWithoutBorders = this;
consoleSender = this.getServer().getConsoleSender();
playerBooksList = new HashMap<>(); playerBooksList = new HashMap<>();
playerLetterIndex = new HashMap<>(); playerLetterIndex = new HashMap<>();
BooksWithoutBordersConfig.initialize(this); booksWithoutBordersConfig = new BooksWithoutBordersConfig(this, translator);
@Nullable List<String> files = BookFileHelper.listFiles(consoleSender, true); @Nullable List<String> files = BookFileHelper.listFiles(this.getServer().getConsoleSender(), true);
if (files != null) { if (files != null) {
publicBooksList = files; publicBooksList = files;
publicLetterIndex = BookFileHelper.populateLetterIndices(files); publicLetterIndex = BookFileHelper.populateLetterIndices(files);
@@ -178,7 +226,7 @@ public class BooksWithoutBorders extends JavaPlugin {
PluginManager pluginManager = this.getServer().getPluginManager(); PluginManager pluginManager = this.getServer().getPluginManager();
if (getSlash() != null && initialize()) { if (getConfiguration().getSlash() != null && initialize()) {
pluginManager.registerEvents(new PlayerEventListener(), this); pluginManager.registerEvents(new PlayerEventListener(), this);
pluginManager.registerEvents(new SignEventListener(), this); pluginManager.registerEvents(new SignEventListener(), this);
pluginManager.registerEvents(new BookEventListener(), this); pluginManager.registerEvents(new BookEventListener(), this);
@@ -207,29 +255,32 @@ public class BooksWithoutBorders extends JavaPlugin {
* Registers all commands used by this plugin * Registers all commands used by this plugin
*/ */
private void registerCommands() { private void registerCommands() {
registerCommand("giveBook", new CommandGive()); registerCommand(BwBCommand.GIVE_BOOK.toString(), new CommandGive());
registerCommand("givePublicBook", new CommandGivePublic()); registerCommand(BwBCommand.GIVE_PUBLIC_BOOK.toString(), new CommandGivePublic());
registerCommand("decryptBook", new CommandDecrypt()); registerCommand(BwBCommand.DECRYPT_BOOK.toString(), new CommandDecrypt());
registerCommand("groupEncryptBook", new CommandGroupEncrypt()); registerCommand(BwBCommand.GROUP_ENCRYPT_BOOK.toString(), new CommandGroupEncrypt());
registerCommand("deleteBook", new CommandDelete()); registerCommand(BwBCommand.DELETE_BOOK.toString(), new CommandDelete());
registerCommand("deletePublicBook", new CommandDeletePublic()); registerCommand(BwBCommand.DELETE_PUBLIC_BOOK.toString(), new CommandDeletePublic());
registerCommand("copyBook", new CommandCopy()); registerCommand(BwBCommand.COPY_BOOK.toString(), new CommandCopy());
registerCommand("unSignBook", new CommandUnSign()); registerCommand(BwBCommand.UNSIGN_BOOK.toString(), new CommandUnSign());
registerCommand("encryptBook", new CommandEncrypt()); registerCommand(BwBCommand.ENCRYPT_BOOK.toString(), new CommandEncrypt());
registerCommand("setBookPrice", new CommandSetBookPrice()); registerCommand(BwBCommand.SET_BOOK_PRICE.toString(), new CommandSetBookPrice());
registerCommand("setLore", new CommandSetLore()); registerCommand(BwBCommand.SET_LORE.toString(), new CommandSetLore());
registerCommand("savePublicBook", new CommandSavePublic()); registerCommand(BwBCommand.SAVE_PUBLIC_BOOK.toString(), new CommandSavePublic());
registerCommand("saveBook", new CommandSave()); registerCommand(BwBCommand.SAVE_BOOK.toString(), new CommandSave());
registerCommand("setBookAuthor", new CommandSetAuthor()); registerCommand(BwBCommand.SET_BOOK_AUTHOR.toString(), new CommandSetAuthor());
registerCommand("setTitle", new CommandSetTitle()); registerCommand(BwBCommand.SET_TITLE.toString(), new CommandSetTitle());
registerCommand("loadBook", new CommandLoad()); registerCommand(BwBCommand.LOAD_BOOK.toString(), new CommandLoad());
registerCommand("loadPublicBook", new CommandLoadPublic()); registerCommand(BwBCommand.LOAD_PUBLIC_BOOK.toString(), new CommandLoadPublic());
registerCommand("booksWithoutBorders", new CommandBooksWithoutBorders()); registerCommand(BwBCommand.BOOKS_WITHOUT_BORDERS.toString(), new CommandBooksWithoutBorders());
registerCommand("reload", new CommandReload()); registerCommand(BwBCommand.RELOAD.toString(), new CommandReload());
registerCommand("formatBook", new CommandFormat()); registerCommand(BwBCommand.FORMAT_BOOK.toString(), new CommandFormat());
registerCommand("setBookGeneration", new CommandSetGeneration()); registerCommand(BwBCommand.SET_BOOK_GENERATION.toString(), new CommandSetGeneration());
registerCommand("clearBook", new CommandClear()); registerCommand(BwBCommand.CLEAR_BOOK.toString(), new CommandClear());
registerCommand("setBookshelfData", new CommandSetBookshelfData()); registerCommand(BwBCommand.SET_BOOKSHELF_DATA.toString(), new CommandSetBookshelfData());
registerCommand(BwBCommand.ADD_TITLE_PAGE.toString(), new CommandAddTitlePage());
registerCommand(BwBCommand.DELETE_PAGE.toString(), new CommandDeletePage());
registerCommand(BwBCommand.MIGRATE.toString(), new CommandMigrate());
} }
/** /**
@@ -243,7 +294,7 @@ public class BooksWithoutBorders extends JavaPlugin {
if (pluginCommand != null) { if (pluginCommand != null) {
pluginCommand.setExecutor(executor); pluginCommand.setExecutor(executor);
} else { } else {
sendErrorMessage(consoleSender, "Failed to register command " + commandName); getLogger().log(Level.SEVERE, "Failed to register command " + commandName);
} }
} }
@@ -256,20 +307,16 @@ public class BooksWithoutBorders extends JavaPlugin {
//Initialize Item Factory //Initialize Item Factory
try { try {
itemFactory = this.getServer().getItemFactory(); itemFactory = this.getServer().getItemFactory();
} catch (java.lang.NoSuchMethodError nsmE) { } catch (java.lang.NoSuchMethodError noSuchMethodError) {
sendErrorMessage(consoleSender, "Warning! [BooksWithoutBorders] failed to initialize!"); getLogger().log(Level.SEVERE, """
sendErrorMessage(consoleSender, "Please confirm the correct version of [BooksWithoutBorders] is"); Warning! [BooksWithoutBorders] failed to initialize!
sendErrorMessage(consoleSender, "being run for this version of bukkit!"); Please confirm the correct version of [BooksWithoutBorders] is
return false; being run for this version of spigot!""");
}
//Load config
if (!BooksWithoutBordersConfig.loadConfig()) {
return false; return false;
} }
//Save config with loaded values to fix invalid config values //Save config with loaded values to fix invalid config values
BooksWithoutBordersConfig.saveConfigValues(); getConfiguration().saveConfigValues();
return testFileSaving(); return testFileSaving();
} }
@@ -280,7 +327,7 @@ public class BooksWithoutBorders extends JavaPlugin {
* @return <p>The bookshelf handler</p> * @return <p>The bookshelf handler</p>
*/ */
public static BookshelfHandler getBookshelfHandler() { public static BookshelfHandler getBookshelfHandler() {
return bookshelfHandler; return getInstance().bookshelfHandler;
} }
/** /**
@@ -290,7 +337,7 @@ public class BooksWithoutBorders extends JavaPlugin {
*/ */
@NotNull @NotNull
public static ItemFactory getItemFactory() { public static ItemFactory getItemFactory() {
return itemFactory; return getInstance().itemFactory;
} }
/** /**
@@ -299,27 +346,27 @@ public class BooksWithoutBorders extends JavaPlugin {
* @return <p>True if necessary folders exist</p> * @return <p>True if necessary folders exist</p>
*/ */
private boolean testFileSaving() { private boolean testFileSaving() {
File fileTest = new File(getBookFolder()); File fileTest = new File(getConfiguration().getBookFolder());
File encryptedFileTest = new File(getBookFolder() + "Encrypted" + getSlash()); File encryptedFileTest = new File(getConfiguration().getEncryptedBookPath());
if (!fileTest.exists()) { if (!fileTest.exists()) {
try { try {
if (!fileTest.mkdir()) { if (!fileTest.mkdir()) {
sendErrorMessage(consoleSender, "Saving failed! Aborting..."); getLogger().log(Level.SEVERE, StaticMessage.BOOK_SAVING_FAILED.toString());
return false; return false;
} }
} catch (Exception exception) { } catch (Exception exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create necessary folders"); getLogger().log(Level.SEVERE, StaticMessage.BOOK_FOLDER_CREATE_FAILED.toString());
return false; return false;
} }
} }
if (!encryptedFileTest.exists()) { if (!encryptedFileTest.exists()) {
try { try {
if (!encryptedFileTest.mkdir()) { if (!encryptedFileTest.mkdir()) {
sendErrorMessage(consoleSender, "Saving failed! Aborting..."); getLogger().log(Level.SEVERE, StaticMessage.BOOK_SAVING_FAILED.toString());
return false; return false;
} }
} catch (Exception exception) { } catch (Exception exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create necessary folders"); getLogger().log(Level.SEVERE, StaticMessage.BOOK_FOLDER_CREATE_FAILED.toString());
return false; return false;
} }
} }
@@ -333,7 +380,7 @@ public class BooksWithoutBorders extends JavaPlugin {
* @param message <p>The message to send</p> * @param message <p>The message to send</p>
*/ */
public static void sendSuccessMessage(@NotNull CommandSender sender, @NotNull String message) { public static void sendSuccessMessage(@NotNull CommandSender sender, @NotNull String message) {
sender.sendMessage(getSuccessColor() + message); sender.sendMessage(getConfiguration().getSuccessColor() + message);
} }
/** /**
@@ -343,7 +390,7 @@ public class BooksWithoutBorders extends JavaPlugin {
* @param message <p>The message to send</p> * @param message <p>The message to send</p>
*/ */
public static void sendErrorMessage(@NotNull CommandSender sender, @NotNull String message) { public static void sendErrorMessage(@NotNull CommandSender sender, @NotNull String message) {
sender.sendMessage(getErrorColor() + message); sender.sendMessage(getConfiguration().getErrorColor() + message);
} }
} }

View File

@@ -0,0 +1,157 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* A command for adding a title page to a book
*/
public class CommandAddTitlePage implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (!(sender instanceof Player player)) {
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false;
}
ItemStack heldBook = InventoryHelper.getHeldBook(player);
if (heldBook == null) {
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_NOT_HOLDING_ANY_BOOK);
return false;
}
int index;
if (arguments.length < 1) {
if (InventoryHelper.notHoldingOneWrittenBookCheck(player, "You must be holding a written book to " +
"add an author title page!", "You cannot add an author title page to two books at once!")) {
return false;
}
index = 0;
} else {
try {
index = Integer.parseInt(arguments[0]) - 1;
} catch (NumberFormatException exception) {
BooksWithoutBorders.sendErrorMessage(sender, "Invalid page index given!");
return false;
}
}
String title = null;
if (arguments.length > 1) {
// Get all arguments as a space-separated string
StringBuilder builder = new StringBuilder(arguments[1]);
for (int i = 2; i < arguments.length; i++) {
builder.append(" ").append(arguments[i]);
}
title = builder.toString();
}
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
if (bookMeta == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata for the held book!");
return false;
}
List<String> pages = new ArrayList<>(bookMeta.getPages());
if (index < 0) {
BooksWithoutBorders.sendErrorMessage(sender, "The given page index is out of bounds!");
return false;
}
if (title == null && heldBook.getType() == Material.WRITTEN_BOOK) {
String loreSeparator = BooksWithoutBorders.getConfiguration().getLoreSeparator();
if (index > pages.size()) {
pages.add(formatTitle(bookMeta.getTitle() + loreSeparator + "By: " + bookMeta.getAuthor()));
} else {
pages.add(index, formatTitle(bookMeta.getTitle() + loreSeparator + "By: " + bookMeta.getAuthor()));
}
} else if (title == null) {
if (index > pages.size()) {
pages.add("");
} else {
pages.add(index, "");
}
} else {
if (index > pages.size()) {
pages.add(formatTitle(title));
} else {
pages.add(index, formatTitle(title));
}
}
bookMeta.setPages(pages);
heldBook.setItemMeta(bookMeta);
BooksWithoutBorders.sendSuccessMessage(sender, "Title page added!");
return true;
}
/**
* Formats a book title
*
* @param input <p>The input to format</p>
* @return <p>The formatted input</p>
*/
private String formatTitle(@NotNull String input) {
String loreSeparator = BooksWithoutBorders.getConfiguration().getLoreSeparator();
if (input.contains(loreSeparator)) {
String[] parts = input.split(loreSeparator);
StringBuilder output = new StringBuilder("\n");
output.append(ChatColor.UNDERLINE).append(ChatColor.BOLD).append(BookFormatter.stripColor(parts[0])).append(ChatColor.RESET);
for (int i = 1; i < parts.length; i++) {
output.append("\n").append("\n").append(ChatColor.ITALIC).append(BookFormatter.stripColor(parts[i])).append(ChatColor.RESET);
}
return output.toString();
} else {
return ChatColor.UNDERLINE + ChatColor.BOLD.toString() + input;
}
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
if (!(commandSender instanceof Player player)) {
return List.of("1", "2", "3", "4");
}
ItemStack heldBook = InventoryHelper.getHeldBook(player);
if (heldBook != null) {
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
if (bookMeta != null) {
List<String> pages = new ArrayList<>();
pages.add("1");
for (int i = 1; i <= bookMeta.getPages().size(); i++) {
pages.add(String.valueOf(i + 1));
}
return pages;
}
}
} else if (arguments.length == 2) {
return List.of("Title", "Chapter~Description");
}
return List.of();
}
}

View File

@@ -2,7 +2,12 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.config.BwBCommand;
import net.knarcraft.bookswithoutborders.config.Permission;
import net.knarcraft.bookswithoutborders.config.StaticMessage;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.manager.EconomyManager;
import net.knarcraft.knarlib.formatting.StringFormatter;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -13,10 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendErrorMessage;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getCommandColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSuccessColor;
/** /**
* Command executor for the books without borders (bwb) command * Command executor for the books without borders (bwb) command
@@ -24,29 +26,75 @@ import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig
public class CommandBooksWithoutBorders implements TabExecutor { public class CommandBooksWithoutBorders implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
sender.sendMessage(getCommandColor() + "[] denote optional parameters"); @NotNull String[] arguments) {
sender.sendMessage(getCommandColor() + "<> denote required parameters"); StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
sender.sendMessage(getCommandColor() + "{} denote required permission"); String header = stringFormatter.replacePlaceholders(Translatable.NEUTRAL_COMMANDS_HEADER,
sender.sendMessage(getCommandColor() + "In some cases, commands with required parameters can be called with no parameters"); List.of("{bookPrice}", "{commands}"), List.of(getBookPrice(), getCommands(sender)));
if (sender instanceof Player) { sender.sendMessage(header);
showPlayerCommands(sender);
} else {
showConsoleCommands(sender);
}
return true; return true;
} }
/**
* Gets the list of commands
*
* @param sender <p>The command sender trying to see available commands</p>
* @return <p>The string representation of all commands</p>
*/
@NotNull
private String getCommands(@NotNull CommandSender sender) {
if (sender instanceof Player) {
return showPlayerCommands(sender);
} else {
return showConsoleCommands(sender);
}
}
/**
* Gets the price of duplicating a book
*
* @return <p>The book price</p>
*/
@NotNull
private String getBookPrice() {
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
if (!config.booksHavePrice()) {
return "";
}
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
Material bookPriceType = config.getBookPriceType();
double bookPriceQuantity = config.getBookPriceQuantity();
if (bookPriceType != Material.AIR) {
return stringFormatter.replacePlaceholders(Translatable.NEUTRAL_COMMANDS_BOOK_PRICE_ITEM,
List.of("{quantity}", "{type}"),
List.of(String.valueOf((int) bookPriceQuantity), bookPriceType.toString()));
} else {
EconomyManager economyManager = BooksWithoutBorders.getConfiguration().getEconomyManager();
if (economyManager.getEconomy() == null) {
return BooksWithoutBorders.getStringFormatter().getUnFormattedColoredMessage(Translatable.ERROR_VAULT_COST_BUT_UNAVAILABLE) + "\n";
} else {
return stringFormatter.replacePlaceholder(Translatable.NEUTRAL_COMMANDS_BOOK_PRICE_ECO,
"{price}", economyManager.getEconomy().format(bookPriceQuantity));
}
}
}
/** /**
* Shows all commands available to the console * Shows all commands available to the console
* *
* @param sender <p>The console which sent the command</p> * @param sender <p>The console which sent the command</p>
*/ */
private void showConsoleCommands(@NotNull CommandSender sender) { @NotNull
sender.sendMessage(getCommandColor() + "Commands:"); private String showConsoleCommands(@NotNull CommandSender sender) {
showCommandInfo("deletePublicBook", sender); StringBuilder builder = new StringBuilder();
showCommandInfo("givePublicBook", sender); for (BwBCommand command : BwBCommand.values()) {
showCommandInfo("reload", sender); if (!command.requiresPlayer()) {
builder.append(showCommandInfo(command.toString(), sender));
}
}
return builder.toString();
} }
/** /**
@@ -54,44 +102,13 @@ public class CommandBooksWithoutBorders implements TabExecutor {
* *
* @param sender <p>The player which sent the command</p> * @param sender <p>The player which sent the command</p>
*/ */
private void showPlayerCommands(@NotNull CommandSender sender) { @NotNull
//Lists all commands private String showPlayerCommands(@NotNull CommandSender sender) {
Material bookPriceType = BooksWithoutBordersConfig.getBookPriceType(); StringBuilder builder = new StringBuilder();
double bookPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity(); for (BwBCommand command : BwBCommand.values()) {
if (BooksWithoutBordersConfig.booksHavePrice()) { builder.append(showCommandInfo(command.toString(), sender));
if (bookPriceType != Material.AIR) {
sendErrorMessage(sender, "[" + (int) bookPriceQuantity + " " + bookPriceType.toString() +
"(s) are required to create a book]");
} else {
sendErrorMessage(sender, "[" + EconomyHelper.getEconomy().format(bookPriceQuantity) +
" is required to create a book]");
} }
} return builder.toString();
sender.sendMessage(getCommandColor() + "Commands:");
showCommandInfo("booksWithoutBorders", sender);
showCommandInfo("copyBook", sender);
showCommandInfo("decryptBook", sender);
showCommandInfo("deleteBook", sender);
showCommandInfo("deletePublicBook", sender);
showCommandInfo("encryptBook", sender);
showCommandInfo("formatBook", sender);
showCommandInfo("giveBook", sender);
showCommandInfo("givePublicBook", sender);
showCommandInfo("groupEncryptBook", sender);
showCommandInfo("loadBook", sender);
showCommandInfo("loadPublicBook", sender);
showCommandInfo("reload", sender);
showCommandInfo("saveBook", sender);
showCommandInfo("savePublicBook", sender);
showCommandInfo("setAuthor", sender);
showCommandInfo("setBookGeneration", sender);
showCommandInfo("setBookPrice", sender);
showCommandInfo("setLore", sender);
showCommandInfo("setTitle", sender);
showCommandInfo("unsignBook", sender);
showCommandInfo("clearBook", sender);
showCommandInfo("setBookshelfData", sender);
} }
/** /**
@@ -100,22 +117,32 @@ public class CommandBooksWithoutBorders implements TabExecutor {
* @param command <p>The command to get information about</p> * @param command <p>The command to get information about</p>
* @param sender <p>The sender asking to see command info</p> * @param sender <p>The sender asking to see command info</p>
*/ */
private void showCommandInfo(@NotNull String command, @NotNull CommandSender sender) { @NotNull
private String showCommandInfo(@NotNull String command, @NotNull CommandSender sender) {
PluginCommand pluginCommand = BooksWithoutBorders.getInstance().getCommand(command); PluginCommand pluginCommand = BooksWithoutBorders.getInstance().getCommand(command);
if (pluginCommand != null) { if (pluginCommand == null) {
BooksWithoutBorders.log(Level.SEVERE, StringFormatter.replacePlaceholder(
StaticMessage.COMMAND_NOT_REGISTERED.toString(), "{command}", command));
return "";
}
String permission = pluginCommand.getPermission(); String permission = pluginCommand.getPermission();
if (permission == null || sender.hasPermission(permission)) { if (permission != null && !sender.hasPermission(permission)) {
String commandInfo = "\n" + getCommandColor() + pluginCommand.getUsage().replace("<command>", return "";
pluginCommand.getName()) + ": " + getSuccessColor() + pluginCommand.getDescription(); }
if (sender.hasPermission("bookswithoutborders.admin")) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
String commandDescription = stringFormatter.replacePlaceholders(Translatable.NEUTRAL_COMMANDS_COMMAND,
List.of("{usage}", "{description}"), List.of(pluginCommand.getUsage().replace("<command>",
pluginCommand.getName()), pluginCommand.getDescription()));
if (sender.hasPermission(Permission.ADMIN.toString())) {
if (permission == null) { if (permission == null) {
permission = "None"; permission = stringFormatter.getUnFormattedColoredMessage(Translatable.NEUTRAL_COMMANDS_COMMAND_NO_PERMISSION_REQUIRED);
}
commandInfo += getCommandColor() + " {" + permission + "}";
}
sender.sendMessage(commandInfo);
} }
commandDescription += stringFormatter.replacePlaceholder(Translatable.NEUTRAL_COMMANDS_COMMAND_PERMISSION, "{permission}", permission);
} }
return commandDescription;
} }
@Override @Override

View File

@@ -1,7 +1,9 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -22,13 +24,18 @@ public class CommandClear implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] args) { @NotNull String[] args) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false; return false;
} }
if (InventoryHelper.notHoldingOneWritableBookCheck(player, "You must be holding a writable book to " + if (InventoryHelper.notHoldingOneWritableBookCheck(player,
"clear it!", "You cannot clear two books at once!")) { stringFormatter.replacePlaceholder(Translatable.ERROR_NOT_HOLDING_WRITABLE_BOOK, "{action}",
stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_CLEAR)),
stringFormatter.replacePlaceholder(Translatable.ERROR_ONLY_ONE_BOOK, "{action}",
stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_CLEAR)))) {
return false; return false;
} }
@@ -36,7 +43,7 @@ public class CommandClear implements TabExecutor {
ItemStack heldBook = InventoryHelper.getHeldBook(player, false); ItemStack heldBook = InventoryHelper.getHeldBook(player, false);
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta(); BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
if (bookMeta == null) { if (bookMeta == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata for the held book!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_METADATA_MISSING);
return false; return false;
} }
bookMeta.setPages(""); bookMeta.setPages("");
@@ -44,7 +51,7 @@ public class CommandClear implements TabExecutor {
bookMeta.setGeneration(null); bookMeta.setGeneration(null);
bookMeta.setTitle(null); bookMeta.setTitle(null);
heldBook.setItemMeta(bookMeta); heldBook.setItemMeta(bookMeta);
BooksWithoutBorders.sendSuccessMessage(sender, "Book cleared!"); stringFormatter.displaySuccessMessage(sender, Translatable.SUCCESS_CLEARED);
return true; return true;
} }

View File

@@ -2,10 +2,12 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.config.Permission;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.utility.BookHelper; import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@@ -28,18 +30,23 @@ public class CommandCopy implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false; return false;
} }
if (InventoryHelper.notHoldingOneWrittenBookCheck(player, "You must be holding a written book to copy it!", if (InventoryHelper.notHoldingOneWrittenBookCheck(player,
"You cannot copy two books at once!")) { stringFormatter.replacePlaceholder(Translatable.ERROR_NOT_HOLDING_WRITTEN_BOOK, "{action}",
stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_COPY)),
stringFormatter.replacePlaceholder(Translatable.ERROR_ONLY_ONE_BOOK, "{action}",
stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_COPY)))) {
return false; return false;
} }
if (arguments.length < 1) { if (arguments.length < 1) {
BooksWithoutBorders.sendErrorMessage(player, "You must specify the number of copies to be made!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_COPY_COUNT_NOT_SPECIFIED);
return false; return false;
} }
@@ -47,12 +54,12 @@ public class CommandCopy implements TabExecutor {
ItemStack heldBook = InventoryHelper.getHeldBook(player, true); ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
int copies = Integer.parseInt(arguments[0]); int copies = Integer.parseInt(arguments[0]);
if (copies <= 0) { if (copies <= 0) {
throw new NumberFormatException("Number of copies must be larger than 0"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_COPY_NEGATIVE_AMOUNT);
return false;
} }
return performCopy(copies, player, heldBook); return performCopy(copies, player, heldBook);
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
BooksWithoutBorders.sendErrorMessage(player, "Book not copied!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_COPY_INVALID_AMOUNT);
BooksWithoutBorders.sendErrorMessage(player, "Number specified was invalid!");
return false; return false;
} }
} }
@@ -66,16 +73,18 @@ public class CommandCopy implements TabExecutor {
* @return <p>True if the copying was successful</p> * @return <p>True if the copying was successful</p>
*/ */
private boolean performCopy(int copies, @NotNull Player player, @NotNull ItemStack heldBook) { private boolean performCopy(int copies, @NotNull Player player, @NotNull ItemStack heldBook) {
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
//Make sure the player owns the book if authorOnlyCopy is enabled //Make sure the player owns the book if authorOnlyCopy is enabled
if (BooksWithoutBordersConfig.getAuthorOnlyCopy() && if (config.getAuthorOnlyCopy() &&
!player.hasPermission("bookswithoutborders.bypassAuthorOnlyCopy")) { !player.hasPermission(Permission.BYPASS_AUTHOR_ONLY_COPY.toString())) {
if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) { if (BookHelper.isNotAuthor(player, (BookMeta) Objects.requireNonNull(heldBook.getItemMeta()))) {
return false; return false;
} }
} }
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta(); BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
if (BooksWithoutBordersConfig.changeGenerationOnCopy() && bookMeta != null) { if (config.changeGenerationOnCopy() && bookMeta != null) {
return copyNextGenerationBook(bookMeta, player, copies); return copyNextGenerationBook(bookMeta, player, copies);
} else { } else {
//Make sure the player can pay for the copying //Make sure the player can pay for the copying
@@ -84,7 +93,7 @@ public class CommandCopy implements TabExecutor {
} }
heldBook.setAmount(heldBook.getAmount() + copies); heldBook.setAmount(heldBook.getAmount() + copies);
BooksWithoutBorders.sendSuccessMessage(player, "Book copied!"); BooksWithoutBorders.getStringFormatter().displaySuccessMessage(player, Translatable.SUCCESS_COPY);
return true; return true;
} }
} }
@@ -97,9 +106,10 @@ public class CommandCopy implements TabExecutor {
* @return <p>True if the payment failed</p> * @return <p>True if the payment failed</p>
*/ */
private boolean paymentUnSuccessful(@NotNull Player player, int copies) { private boolean paymentUnSuccessful(@NotNull Player player, int copies) {
return BooksWithoutBordersConfig.booksHavePrice() && BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
!player.hasPermission("bookswithoutborders.bypassBookPrice") && return (config.booksHavePrice() &&
EconomyHelper.cannotPayForBookPrinting(player, copies); !player.hasPermission(Permission.BYPASS_BOOK_PRICE.toString()) &&
config.getEconomyManager().cannotPayForBookPrinting(player, copies));
} }
/** /**
@@ -114,14 +124,13 @@ public class CommandCopy implements TabExecutor {
//Copy the vanilla behavior of refusing copying any further //Copy the vanilla behavior of refusing copying any further
if (bookMeta.getGeneration() == BookMeta.Generation.COPY_OF_COPY || if (bookMeta.getGeneration() == BookMeta.Generation.COPY_OF_COPY ||
bookMeta.getGeneration() == BookMeta.Generation.TATTERED) { bookMeta.getGeneration() == BookMeta.Generation.TATTERED) {
BooksWithoutBorders.sendErrorMessage(player, "You cannot copy this book any further. " + BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_BOOK_COPIED_TOO_FAR);
"You must have the original or a direct copy.");
return false; return false;
} }
//Make sure the player can fit the book in their inventory //Make sure the player can fit the book in their inventory
int nextAvailableSlot = player.getInventory().firstEmpty(); int nextAvailableSlot = player.getInventory().firstEmpty();
if (nextAvailableSlot == -1) { if (nextAvailableSlot == -1) {
BooksWithoutBorders.sendErrorMessage(player, "You need an available slot in your inventory."); BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_INVENTORY_FULL);
return false; return false;
} }
//Make sure the player can pay for the copying //Make sure the player can pay for the copying

View File

@@ -1,10 +1,12 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.Permission;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.utility.BookHelper; import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper; import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -17,42 +19,74 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
/** /**
* Command executor for the decrypt command * Command executor for the decrypt command
*/ */
public class CommandDecrypt implements TabExecutor { public class CommandDecrypt implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false; return false;
} }
if (InventoryHelper.notHoldingOneWrittenBookCheck(player, if (InventoryHelper.notHoldingOneWrittenBookCheck(player,
"You must be holding a written book to decrypt it!", stringFormatter.replacePlaceholder(Translatable.ERROR_NOT_HOLDING_WRITTEN_BOOK, "{action}",
"You cannot decrypt two books at once!")) { stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_DECRYPT)),
stringFormatter.replacePlaceholder(Translatable.ERROR_ONLY_ONE_BOOK, "{action}",
stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_DECRYPT)))) {
return false; return false;
} }
ItemStack heldItem = InventoryHelper.getHeldBook(player, true); ItemStack heldItem = InventoryHelper.getHeldBook(player, true);
BookMeta bookMetadata = (BookMeta) heldItem.getItemMeta(); BookMeta bookMetadata = (BookMeta) heldItem.getItemMeta();
if (bookMetadata == null) { if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(player, "Your book seems to be corrupt!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_METADATA_MISSING);
return false; return false;
} }
//Warning: admin decrypt only allows decrypting files created by the same player. Not sure if intended //Warning: admin decrypt only allows decrypting files created by the same player. Not sure if intended
if (args.length == 0 && BooksWithoutBordersConfig.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin")) { if (arguments.length == 0 && BooksWithoutBorders.getConfiguration().getAdminDecrypt() &&
String path = getBookFolder() + "Encrypted" + getSlash(); player.hasPermission(Permission.ADMIN.toString())) {
return adminDecrypt(player, bookMetadata);
} else if (arguments.length == 0) {
stringFormatter.displayErrorMessage(player, Translatable.ERROR_DECRYPT_MISSING_KEY);
return false;
}
File encryptedDirectory = new File(path); //Decrypt the book normally
ItemStack book = EncryptionHelper.loadEncryptedBook(player, arguments[0], true, false);
if (book == null) {
book = EncryptionHelper.loadEncryptedBookLegacy(player, arguments[0], true);
}
if (book != null) {
InventoryHelper.setHeldWrittenBook(player, book);
stringFormatter.displaySuccessMessage(player, Translatable.SUCCESS_DECRYPTED);
return true;
} else {
stringFormatter.displayErrorMessage(player, Translatable.ERROR_DECRYPT_FAILED);
return false;
}
}
/**
* Uses the admin decrypt to decrypt a book without providing the password
*
* @param player <p>The admin decrypting the book</p>
* @param bookMetadata <p>The metadata of the book to decrypt</p>
* @return <p>True if successful</p>
*/
private boolean adminDecrypt(@NotNull Player player, @NotNull BookMeta bookMetadata) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
File encryptedDirectory = new File(BooksWithoutBorders.getConfiguration().getEncryptedBookPath());
String[] encryptedFiles = encryptedDirectory.list(); String[] encryptedFiles = encryptedDirectory.list();
if (encryptedFiles == null) { if (encryptedFiles == null) {
BooksWithoutBorders.sendErrorMessage(player, "Could not find any encrypted files!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_ENCRYPTED_DIRECTORY_EMPTY_OR_MISSING);
return false; return false;
} }
@@ -67,33 +101,19 @@ public class CommandDecrypt implements TabExecutor {
if (!key.equalsIgnoreCase("")) { if (!key.equalsIgnoreCase("")) {
//Decrypt the book //Decrypt the book
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false); ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false, true);
if (book == null) {
book = EncryptionHelper.loadEncryptedBookLegacy(player, key, false);
}
if (book != null) { if (book != null) {
InventoryHelper.setHeldWrittenBook(player, book); InventoryHelper.setHeldWrittenBook(player, book);
BooksWithoutBorders.sendSuccessMessage(player, "Book auto-decrypted!"); stringFormatter.displaySuccessMessage(player, Translatable.SUCCESS_AUTO_DECRYPTED);
return true; return true;
} else { } else {
return false; return false;
} }
} else { } else {
BooksWithoutBorders.sendErrorMessage(player, "No matching encrypted book found!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_ENCRYPTED_BOOK_UNKNOWN);
return false;
}
} else if (args.length == 0) {
BooksWithoutBorders.sendErrorMessage(player, "No decryption password given!");
return false;
}
String key = EncryptionHelper.getNumberKeyFromStringKey(args[0]);
//Decrypt the book
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, true);
if (book != null) {
InventoryHelper.setHeldWrittenBook(player, book);
BooksWithoutBorders.sendSuccessMessage(player, "Book decrypted!");
return true;
} else {
BooksWithoutBorders.sendErrorMessage(player, "Failed to decrypt book!");
return false; return false;
} }
} }
@@ -103,9 +123,7 @@ public class CommandDecrypt implements TabExecutor {
@NotNull String[] arguments) { @NotNull String[] arguments) {
int argumentCount = arguments.length; int argumentCount = arguments.length;
if (argumentCount == 1) { if (argumentCount == 1) {
List<String> info = new ArrayList<>(); return List.of("<password>");
info.add("<password>");
return info;
} }
return new ArrayList<>(); return new ArrayList<>();
} }

View File

@@ -1,10 +1,15 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BwBCommand;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.gui.PagedBookIndex; import net.knarcraft.bookswithoutborders.gui.PagedBookIndex;
import net.knarcraft.bookswithoutborders.state.BookDirectory; import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookHelper; import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -13,7 +18,6 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@@ -25,7 +29,7 @@ public class CommandDelete implements TabExecutor {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (!(sender instanceof Player)) { if (!(sender instanceof Player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false; return false;
} }
@@ -41,26 +45,23 @@ public class CommandDelete implements TabExecutor {
* @return <p>True if the book was deleted successfully</p> * @return <p>True if the book was deleted successfully</p>
*/ */
protected boolean deleteBook(@NotNull CommandSender sender, @NotNull String[] arguments, boolean deletePublic) { protected boolean deleteBook(@NotNull CommandSender sender, @NotNull String[] arguments, boolean deletePublic) {
String command = deletePublic ? "deletepublicbook" : "deletebook"; String command = deletePublic ? BwBCommand.DELETE_PUBLIC_BOOK.toString().toLowerCase() :
BwBCommand.DELETE_BOOK.toString().toLowerCase();
if (PagedBookIndex.displayPage(arguments, sender, deletePublic, command)) { if (PagedBookIndex.displayPage(arguments, sender, deletePublic, command)) {
return true; return true;
} }
//Delete the file //Delete the file
if (arguments.length == 1) {
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, deletePublic); List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, deletePublic);
if (!availableBooks.isEmpty()) { if (availableBooks.isEmpty()) {
performBookDeletion(sender, arguments[0], deletePublic); BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_DELETE_EMPTY);
return false;
}
performBookDeletion(sender, InputCleaningHelper.mergeArguments(arguments, 0), deletePublic);
//Update the book list //Update the book list
BooksWithoutBorders.updateBooks(sender, deletePublic); BooksWithoutBorders.updateBooks(sender, deletePublic);
return true; return true;
} else {
BooksWithoutBorders.sendErrorMessage(sender, "No files available to delete!");
return false;
}
}
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect number of arguments for this command!");
return false;
} }
/** /**
@@ -71,6 +72,7 @@ public class CommandDelete implements TabExecutor {
* @param isPublic <p>Whether the book to delete is public or not</p> * @param isPublic <p>Whether the book to delete is public or not</p>
*/ */
public void performBookDeletion(@NotNull CommandSender sender, @NotNull String fileName, @NotNull Boolean isPublic) { public void performBookDeletion(@NotNull CommandSender sender, @NotNull String fileName, @NotNull Boolean isPublic) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
//If the file name is an index of the load list, load the book //If the file name is an index of the load list, load the book
try { try {
int loadListIndex = Integer.parseInt(fileName); int loadListIndex = Integer.parseInt(fileName);
@@ -88,19 +90,20 @@ public class CommandDelete implements TabExecutor {
//Send message if no such file could be found //Send message if no such file could be found
if (file == null) { if (file == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_INCORRECT_FILE_NAME);
return; return;
} }
//Try to delete the file //Try to delete the file
try { try {
if (file.delete()) { if (file.delete()) {
BooksWithoutBorders.sendSuccessMessage(sender, "\"" + fileName + "\" deleted successfully"); stringFormatter.displaySuccessMessage(sender,
stringFormatter.replacePlaceholder(Translatable.SUCCESS_DELETED, "{file}", fileName));
} else { } else {
BooksWithoutBorders.sendErrorMessage(sender, "Deletion failed without an exception!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_DELETE_FAILED_SILENT);
} }
} catch (Exception e) { } catch (Exception e) {
BooksWithoutBorders.sendErrorMessage(sender, "Deletion failed!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_DELETE_FAILED_EXCEPTION);
} }
} }
@@ -114,18 +117,21 @@ public class CommandDelete implements TabExecutor {
* Performs tab completion * Performs tab completion
* *
* @param sender <p>The sender of the command</p> * @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p> * @param arguments <p>The arguments given</p>
* @param deletePublic <p>Whether to delete a public book</p> * @param deletePublic <p>Whether to delete a public book</p>
* @return <p>A list of available arguments</p> * @return <p>A list of available arguments</p>
*/ */
@NotNull @NotNull
protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] args, boolean deletePublic) { protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] arguments, boolean deletePublic) {
int argumentCount = args.length; List<String> filtered = TabCompletionHelper.filterMatchingContains(
if (argumentCount == 1) { BooksWithoutBorders.getAvailableBooks(sender, deletePublic),
return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, deletePublic), InputCleaningHelper.mergeArguments(arguments, 0));
args[0]); if (arguments.length > 1) {
return TabCompletionTypeHelper.getCleanedTabCompletions(arguments, filtered);
} else {
return filtered;
} }
return new ArrayList<>();
} }
} }

View File

@@ -0,0 +1,91 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* A command for deleting a single page from a book
*/
public class CommandDeletePage implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s, @NotNull String[] arguments) {
if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false;
}
if (arguments.length == 0) {
BooksWithoutBorders.sendErrorMessage(sender, "You must supply a page index");
return false;
}
ItemStack heldBook = InventoryHelper.getHeldBook(player);
if (heldBook == null) {
BooksWithoutBorders.sendErrorMessage(sender, "You must be holding a book to perform this command");
return false;
}
int index;
try {
index = Integer.parseInt(arguments[0]) - 1;
} catch (NumberFormatException exception) {
BooksWithoutBorders.sendErrorMessage(sender, "Invalid page index given!");
return false;
}
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
if (bookMeta == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata for the held book!");
return false;
}
List<String> pages = new ArrayList<>(bookMeta.getPages());
if (index < 0 || index >= pages.size()) {
BooksWithoutBorders.sendErrorMessage(sender, "The given page index is out of bounds!");
return false;
}
pages.remove(index);
bookMeta.setPages(pages);
heldBook.setItemMeta(bookMeta);
BooksWithoutBorders.sendSuccessMessage(sender, "Page deleted!");
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (arguments.length == 1) {
if (!(commandSender instanceof Player player)) {
return List.of("1", "2", "3", "4");
}
ItemStack heldBook = InventoryHelper.getHeldBook(player);
if (heldBook != null) {
BookMeta bookMeta = (BookMeta) heldBook.getItemMeta();
if (bookMeta != null) {
List<String> pages = new ArrayList<>();
for (int i = 0; i < bookMeta.getPages().size(); i++) {
pages.add(String.valueOf(i + 1));
}
return pages;
}
}
}
return List.of();
}
}

View File

@@ -13,12 +13,14 @@ import java.util.List;
public class CommandDeletePublic extends CommandDelete implements TabExecutor { public class CommandDeletePublic extends CommandDelete implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] arguments) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) {
return deleteBook(sender, arguments, true); return deleteBook(sender, arguments, true);
} }
@Override @Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] arguments) { public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias,
@NotNull String[] arguments) {
return doTabCompletion(sender, arguments, true); return doTabCompletion(sender, arguments, true);
} }

View File

@@ -1,10 +1,12 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle; import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
import net.knarcraft.bookswithoutborders.state.ItemSlot; import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper; import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -26,11 +28,19 @@ public class CommandEncrypt implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (performPreChecks(sender, arguments, 1, "You must specify a key to encrypt a book!") == null) { StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (performPreChecks(sender, arguments, 1,
stringFormatter.getUnFormattedColoredMessage(Translatable.ERROR_ENCRYPT_NO_KEY)) == null) {
return false; return false;
} }
EncryptionStyle encryptionStyle = arguments.length == 2 ? EncryptionStyle.getFromString(arguments[1]) : EncryptionStyle.SUBSTITUTION; EncryptionStyle encryptionStyle = arguments.length == 2 ? EncryptionStyle.getFromString(arguments[1]) : EncryptionStyle.SUBSTITUTION;
// AES is the only reliable method for retaining the plaintext
if (BooksWithoutBorders.getConfiguration().useRealEncryption()) {
encryptionStyle = EncryptionStyle.AES;
}
return encryptBook(encryptionStyle, (Player) sender, arguments[0], ""); return encryptBook(encryptionStyle, (Player) sender, arguments[0], "");
} }
@@ -46,35 +56,38 @@ public class CommandEncrypt implements TabExecutor {
@Nullable @Nullable
protected BookMeta performPreChecks(@NotNull CommandSender sender, @NotNull String[] arguments, protected BookMeta performPreChecks(@NotNull CommandSender sender, @NotNull String[] arguments,
int necessaryArguments, @NotNull String missingArgumentsError) { int necessaryArguments, @NotNull String missingArgumentsError) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return null; return null;
} }
if (InventoryHelper.notHoldingOneWrittenBookCheck(player, if (InventoryHelper.notHoldingOneWrittenBookCheck(player,
"You must be holding a written book to encrypt it!", stringFormatter.replacePlaceholder(Translatable.ERROR_NOT_HOLDING_WRITTEN_BOOK, "{action}",
"You cannot encrypt two books at once!")) { stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_ENCRYPT)),
stringFormatter.replacePlaceholder(Translatable.ERROR_ONLY_ONE_BOOK, "{action}",
stringFormatter.getUnFormattedColoredMessage(Translatable.ACTION_ENCRYPT)))) {
return null; return null;
} }
int argumentCount = arguments.length; int argumentCount = arguments.length;
if (argumentCount < necessaryArguments) { if (argumentCount < necessaryArguments) {
BooksWithoutBorders.sendErrorMessage(player, missingArgumentsError); stringFormatter.displayErrorMessage(player, missingArgumentsError);
return null; return null;
} }
if (argumentCount > necessaryArguments + 1) { if (argumentCount > necessaryArguments + 1) {
BooksWithoutBorders.sendErrorMessage(player, "Too many command options specified!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_TOO_MANY_ARGUMENTS_COMMAND);
return null; return null;
} }
ItemStack heldBook = InventoryHelper.getHeldBook(player, true); ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta(); BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta();
if (bookMetadata == null) { if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(player, "Your book seems to be corrupt!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_METADATA_MISSING);
return null; return null;
} }
if (!bookMetadata.hasPages()) { if (!bookMetadata.hasPages()) {
BooksWithoutBorders.sendErrorMessage(player, "Book must have contents to encrypt!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_ENCRYPT_EMPTY);
return null; return null;
} }
return bookMetadata; return bookMetadata;
@@ -121,25 +134,29 @@ public class CommandEncrypt implements TabExecutor {
int argumentsCount = args.length; int argumentsCount = args.length;
List<String> encryptionStyles = new ArrayList<>(); List<String> encryptionStyles = new ArrayList<>();
encryptionStyles.add("dna"); for (EncryptionStyle encryptionStyle : EncryptionStyle.values()) {
encryptionStyles.add("substitution"); encryptionStyles.add(encryptionStyle.toString());
if (argumentsCount == 1) {
List<String> info = new ArrayList<>();
info.add("<password>");
return info;
} else if (argumentsCount == 2) {
if (groupEncrypt) {
List<String> info = new ArrayList<>();
info.add("<group>");
return info;
} else {
return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[1]);
} }
} else if (argumentsCount == 3 && groupEncrypt) {
if (groupEncrypt) {
if (argumentsCount == 1) {
return List.of("<group>");
} else if (argumentsCount == 2) {
return List.of("<password>");
} else if (argumentsCount == 3) {
return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[2]); return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[2]);
} }
return new ArrayList<>(); } else {
if (argumentsCount == 1) {
return List.of("<password>");
} else if (argumentsCount == 2) {
if (BooksWithoutBorders.getConfiguration().useRealEncryption()) {
return List.of();
}
return TabCompletionHelper.filterMatchingStartsWith(encryptionStyles, args[1]);
}
}
return List.of();
} }

View File

@@ -1,8 +1,10 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.utility.BookFormatter; import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -22,27 +24,28 @@ public class CommandFormat implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false; return false;
} }
if (InventoryHelper.notHoldingOneWrittenBookCheck(player, "You must be holding a written book to format it!", ItemStack heldBook = InventoryHelper.getHeldBook(player);
"You cannot format two books at once!")) { if (heldBook == null) {
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_NOT_HOLDING_ANY_BOOK);
return false; return false;
} }
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
BookMeta meta = (BookMeta) heldBook.getItemMeta(); BookMeta meta = (BookMeta) heldBook.getItemMeta();
if (meta == null) { if (meta == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to get metadata from the held book!"); stringFormatter.displayErrorMessage(player, Translatable.ERROR_METADATA_MISSING);
return false; return false;
} }
heldBook.setItemMeta(BookFormatter.formatPages(meta)); heldBook.setItemMeta(BookFormatter.formatPages(meta));
BooksWithoutBorders.sendSuccessMessage(sender, "Book formatted!"); stringFormatter.displaySuccessMessage(sender, Translatable.SUCCESS_FORMATTED);
return true; return true;
} }

View File

@@ -1,11 +1,14 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.gui.PagedBookIndex; import net.knarcraft.bookswithoutborders.gui.PagedBookIndex;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.formatting.StringFormatter;
import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
import org.bukkit.Bukkit;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -29,7 +32,7 @@ public class CommandGive implements TabExecutor {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (!(sender instanceof Player)) { if (!(sender instanceof Player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false; return false;
} }
@@ -52,38 +55,49 @@ public class CommandGive implements TabExecutor {
return true; return true;
} }
if (arguments.length == 1 || arguments.length > 4) { int argumentCount = arguments.length;
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect number of arguments for this command!");
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
if (arguments.length == 1) {
stringFormatter.displayErrorMessage(sender, Translatable.ERROR_GIVE_NO_RECIPIENT);
return false; return false;
} }
//Organize and parse input //Organize and parse input
String bookIdentifier = arguments[0]; String bookIdentifier;
String receivingPlayerName = arguments[1]; String receivingPlayerName;
String copies = "1"; String copies = "1";
String isSigned = "true"; String isSigned = "true";
if (arguments.length == 4) {
copies = arguments[2]; if (argumentCount > 3 && InputCleaningHelper.isInt(arguments[argumentCount - 2]) &&
isSigned = arguments[3]; InputCleaningHelper.isBoolean(arguments[argumentCount - 1])) {
} else if (arguments.length == 3) { receivingPlayerName = arguments[argumentCount - 3];
if (arguments[2].equalsIgnoreCase("true") || arguments[2].equalsIgnoreCase("false")) { isSigned = arguments[argumentCount - 1];
isSigned = arguments[2]; copies = arguments[argumentCount - 2];
bookIdentifier = InputCleaningHelper.mergeArguments(arguments, 3);
} else if (argumentCount > 2 && InputCleaningHelper.isBoolean(arguments[argumentCount - 1])) {
isSigned = arguments[argumentCount - 1];
receivingPlayerName = arguments[argumentCount - 2];
bookIdentifier = InputCleaningHelper.mergeArguments(arguments, 2);
} else if (argumentCount > 2 && InputCleaningHelper.isInt(arguments[argumentCount - 1])) {
copies = arguments[argumentCount - 1];
receivingPlayerName = arguments[argumentCount - 2];
bookIdentifier = InputCleaningHelper.mergeArguments(arguments, 2);
} else { } else {
copies = arguments[2]; receivingPlayerName = arguments[argumentCount - 1];
} bookIdentifier = InputCleaningHelper.mergeArguments(arguments, 1);
} }
//Try and find the target player //Try and find the target player
Player receivingPlayer = booksWithoutBorders.getServer().getPlayer(receivingPlayerName); Player receivingPlayer = booksWithoutBorders.getServer().getPlayerExact(receivingPlayerName);
if (receivingPlayer == null) { if (receivingPlayer == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Player not found!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_GIVE_RECIPIENT_UNKNOWN);
return false; return false;
} }
//Make sure the receiver is able to fit the book //Make sure the receiver is able to fit the book
if (receivingPlayer.getInventory().firstEmpty() == -1) { if (receivingPlayer.getInventory().firstEmpty() == -1) {
BooksWithoutBorders.sendErrorMessage(sender, "Receiving player must have space in their inventory" + stringFormatter.displayErrorMessage(sender, Translatable.ERROR_GIVE_RECIPIENT_FULL);
" to receive books!");
return false; return false;
} }
@@ -97,7 +111,7 @@ public class CommandGive implements TabExecutor {
try { try {
return loadAndGiveBook(bookIdentifier, sender, receivingPlayer, isSigned, folder, copies); return loadAndGiveBook(bookIdentifier, sender, receivingPlayer, isSigned, folder, copies);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
BooksWithoutBorders.sendErrorMessage(sender, "Invalid number of book copies specified!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_GIVE_INVALID_COPIES_AMOUNT);
return false; return false;
} }
} }
@@ -112,43 +126,49 @@ public class CommandGive implements TabExecutor {
* Performs the actual tab completion * Performs the actual tab completion
* *
* @param sender <p>The sender of the command</p> * @param sender <p>The sender of the command</p>
* @param args <p>The arguments given</p> * @param arguments <p>The arguments given</p>
* @param listPublic <p>Whether to list public files or player files</p> * @param listPublic <p>Whether to list public files or player files</p>
* @return <p>A list of available choices</p> * @return <p>A list of available choices</p>
*/ */
@Nullable @Nullable
protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] args, boolean listPublic) { protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] arguments, boolean listPublic) {
Server server = booksWithoutBorders.getServer(); Server server = booksWithoutBorders.getServer();
int argumentCount = arguments.length;
int argumentCount = args.length;
if (argumentCount > 2) {
//Don't continue with autocompletion if the recipient is invalid
if (server.getPlayer(args[1]) == null) {
return new ArrayList<>();
}
}
if (argumentCount == 1) { if (argumentCount == 1) {
//Return list of books return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, listPublic), arguments[0]);
return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, listPublic), }
args[0]);
} else if (argumentCount == 2) { List<String> output = new ArrayList<>();
//Return online players List<String> books = BooksWithoutBorders.getAvailableBooks(sender, listPublic);
return null; List<String> filtered = TabCompletionHelper.filterMatchingContains(books,
} else if (argumentCount == 3) { InputCleaningHelper.mergeArguments(arguments, 0));
//Number of copies
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleansAndNumbers(1, 3), args[2]); if (!filtered.isEmpty()) {
} else if (argumentCount == 4) { List<String> cleaned = TabCompletionTypeHelper.getCleanedTabCompletions(arguments, filtered);
//Signed if (!books.contains(InputCleaningHelper.mergeArguments(arguments, 1))) {
try { return cleaned;
Integer.parseInt(args[2]); } else {
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleans(), args[3]); output.addAll(cleaned);
} catch (NumberFormatException e) {
return new ArrayList<>();
} }
} }
return new ArrayList<>();
if (argumentCount > 2 && InputCleaningHelper.isBoolean(arguments[argumentCount - 2])) {
return output;
} else if (argumentCount > 2 && server.getPlayerExact(arguments[argumentCount - 3]) != null &&
InputCleaningHelper.isInt(arguments[argumentCount - 2])) {
output.addAll(TabCompletionHelper.filterMatchingStartsWith(
TabCompletionTypeHelper.getBooleans(), arguments[argumentCount - 1]));
} else if (argumentCount > 2 && server.getPlayerExact(arguments[argumentCount - 2]) != null) {
output.addAll(TabCompletionHelper.filterMatchingStartsWith(
TabCompletionTypeHelper.getBooleansAndNumbers(1, 3), arguments[argumentCount - 1]));
} else {
List<String> players = new ArrayList<>();
for (Player player : Bukkit.getOnlinePlayers()) {
players.add(player.getName());
}
output.addAll(TabCompletionHelper.filterMatchingStartsWith(players, arguments[argumentCount - 1]));
}
return output;
} }
/** /**
@@ -165,16 +185,17 @@ public class CommandGive implements TabExecutor {
private boolean loadAndGiveBook(@NotNull String bookIdentifier, @NotNull CommandSender sender, private boolean loadAndGiveBook(@NotNull String bookIdentifier, @NotNull CommandSender sender,
@NotNull Player receivingPlayer, @NotNull String isSigned, @NotNull String folder, @NotNull Player receivingPlayer, @NotNull String isSigned, @NotNull String folder,
@NotNull String copies) throws NumberFormatException { @NotNull String copies) throws NumberFormatException {
StringFormatter stringFormatter = BooksWithoutBorders.getStringFormatter();
String bookToLoad = InputCleaningHelper.cleanString(bookIdentifier); String bookToLoad = InputCleaningHelper.cleanString(bookIdentifier);
ItemStack newBook = BookLoader.loadBook(sender, bookToLoad, isSigned, folder, Integer.parseInt(copies)); ItemStack newBook = BookLoader.loadBook(sender, bookToLoad, isSigned, folder, Integer.parseInt(copies));
if (newBook != null) { if (newBook != null) {
//NOTE: As this method bypasses cost, it should also bypass the generation change //NOTE: As this method bypasses cost, it should also bypass the generation change
receivingPlayer.getInventory().addItem(newBook); receivingPlayer.getInventory().addItem(newBook);
BooksWithoutBorders.sendSuccessMessage(sender, "Book sent!"); stringFormatter.displaySuccessMessage(sender, Translatable.SUCCESS_GIVE_SENT);
BooksWithoutBorders.sendSuccessMessage(receivingPlayer, "Book received!"); stringFormatter.displaySuccessMessage(receivingPlayer, Translatable.SUCCESS_GIVE_RECEIVED);
return true; return true;
} else { } else {
BooksWithoutBorders.sendErrorMessage(sender, "Book failed to load!"); stringFormatter.displayErrorMessage(sender, Translatable.ERROR_GIVE_LOAD_FAILED);
return false; return false;
} }
} }

View File

@@ -1,7 +1,7 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle; import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;

View File

@@ -59,14 +59,21 @@ public class CommandLoad implements TabExecutor {
String bookIdentifier = arguments[0]; String bookIdentifier = arguments[0];
String copies = "1"; String copies = "1";
String isSigned = "true"; String isSigned = "true";
if (argumentCount == 3) {
copies = arguments[1]; if (argumentCount > 1) {
isSigned = arguments[2]; if (argumentCount > 2 && InputCleaningHelper.isInt(arguments[argumentCount - 2]) &&
} else if (argumentCount == 2) { InputCleaningHelper.isBoolean(arguments[argumentCount - 1])) {
if (arguments[1].equalsIgnoreCase("true") || arguments[1].equalsIgnoreCase("false")) { isSigned = arguments[argumentCount - 1];
isSigned = arguments[1]; copies = arguments[argumentCount - 2];
bookIdentifier = InputCleaningHelper.mergeArguments(arguments, 2);
} else if (InputCleaningHelper.isBoolean(arguments[argumentCount - 1])) {
isSigned = arguments[argumentCount - 1];
bookIdentifier = InputCleaningHelper.mergeArguments(arguments, 1);
} else if (InputCleaningHelper.isInt(arguments[argumentCount - 1])) {
copies = arguments[argumentCount - 1];
bookIdentifier = InputCleaningHelper.mergeArguments(arguments, 1);
} else { } else {
copies = arguments[1]; bookIdentifier = InputCleaningHelper.mergeArguments(arguments, 0);
} }
} }
@@ -113,22 +120,31 @@ public class CommandLoad implements TabExecutor {
protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] arguments, boolean loadPublic) { protected List<String> doTabCompletion(@NotNull CommandSender sender, @NotNull String[] arguments, boolean loadPublic) {
int argumentCount = arguments.length; int argumentCount = arguments.length;
if (argumentCount == 1) { if (argumentCount == 1) {
//Return list of books return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, loadPublic), arguments[0]);
return TabCompletionHelper.filterMatchingContains(BooksWithoutBorders.getAvailableBooks(sender, loadPublic), }
arguments[0]);
} else if (argumentCount == 2) { List<String> output = new ArrayList<>();
//Number of copies List<String> books = BooksWithoutBorders.getAvailableBooks(sender, loadPublic);
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleansAndNumbers(1, 3), arguments[1]); List<String> filtered = TabCompletionHelper.filterMatchingContains(books,
} else if (argumentCount == 3) { InputCleaningHelper.mergeArguments(arguments, 0));
//Signed
try { if (!filtered.isEmpty()) {
Integer.parseInt(arguments[1]); List<String> cleaned = TabCompletionTypeHelper.getCleanedTabCompletions(arguments, filtered);
return TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleans(), arguments[2]); if (!books.contains(InputCleaningHelper.mergeArguments(arguments, 1))) {
} catch (NumberFormatException e) { return cleaned;
return new ArrayList<>(); } else {
output.addAll(cleaned);
} }
} }
return new ArrayList<>();
if (InputCleaningHelper.isBoolean(arguments[argumentCount - 2])) {
return output;
} else if (InputCleaningHelper.isInt(arguments[argumentCount - 2])) {
output.addAll(TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleans(), arguments[argumentCount - 1]));
} else {
output.addAll(TabCompletionHelper.filterMatchingStartsWith(TabCompletionTypeHelper.getBooleansAndNumbers(1, 3), arguments[argumentCount - 1]));
}
return output;
} }
} }

View File

@@ -0,0 +1,74 @@
package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.container.MigrationRequest;
import net.knarcraft.bookswithoutborders.thread.MigrationQueueThread;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.logging.Level;
/**
* Command executor for the migrate command
*/
public class CommandMigrate implements TabExecutor {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
if (!(sender instanceof Player player)) {
BooksWithoutBorders.getStringFormatter().displayErrorMessage(sender, Translatable.ERROR_PLAYER_ONLY);
return false;
}
File bookDirectory = new File(BooksWithoutBorders.getConfiguration().getBookFolder());
BooksWithoutBorders.sendSuccessMessage(player, "Starting book migration...");
Queue<MigrationRequest> filesToMigrate = new LinkedList<>();
findFilesToMigrate(bookDirectory, filesToMigrate, player);
BooksWithoutBorders.getMigrationQueue().addAll(filesToMigrate);
BooksWithoutBorders instance = BooksWithoutBorders.getInstance();
MigrationQueueThread queueThread = new MigrationQueueThread();
int taskId = instance.getServer().getScheduler().runTaskTimer(instance, queueThread, 1L, 1L).getTaskId();
queueThread.setTaskId(taskId);
return true;
}
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s,
@NotNull String[] arguments) {
return List.of();
}
/**
* Finds all books that should be migrated
*
* @param folder <p>The folder to search for books</p>
* @param queue <p>The list to append found files to</p>
* @param player <p>The player that initiated the migration</p>
*/
private void findFilesToMigrate(@NotNull File folder, @NotNull Queue<MigrationRequest> queue, @NotNull Player player) {
File[] files = folder.listFiles();
if (files == null) {
BooksWithoutBorders.log(Level.WARNING, "Unable to access directory " + folder.getName() + " !");
return;
}
for (File file : files) {
if (file.isDirectory()) {
findFilesToMigrate(file, queue, player);
} else if (file.isFile()) {
queue.add(new MigrationRequest(file, player));
}
}
}
}

View File

@@ -1,7 +1,6 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
@@ -18,7 +17,7 @@ public class CommandReload implements TabExecutor {
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label,
@NotNull String[] arguments) { @NotNull String[] arguments) {
if (BooksWithoutBordersConfig.loadConfig()) { if (BooksWithoutBorders.getConfiguration().loadConfig()) {
BooksWithoutBorders.sendSuccessMessage(sender, "BooksWithoutBorders configuration reloaded!"); BooksWithoutBorders.sendSuccessMessage(sender, "BooksWithoutBorders configuration reloaded!");
} else { } else {
BooksWithoutBorders.sendErrorMessage(sender, "Reload Failed!"); BooksWithoutBorders.sendErrorMessage(sender, "Reload Failed!");

View File

@@ -23,10 +23,6 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getCommandColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getErrorColor;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getTitleAuthorSeparator;
/** /**
* Command executor for the save command * Command executor for the save command
*/ */
@@ -45,7 +41,7 @@ public class CommandSave implements TabExecutor {
* @param savePublic <p>Whether to save the book in the public directory or the player directory</p> * @param savePublic <p>Whether to save the book in the public directory or the player directory</p>
* @return <p>True if a book was saved successfully</p> * @return <p>True if a book was saved successfully</p>
*/ */
boolean saveHeldBook(@NotNull CommandSender sender, @NotNull String[] arguments, boolean savePublic) { protected boolean saveHeldBook(@NotNull CommandSender sender, @NotNull String[] arguments, boolean savePublic) {
if (!(sender instanceof Player player)) { if (!(sender instanceof Player player)) {
BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!"); BooksWithoutBorders.sendErrorMessage(sender, "This command can only be used by a player!");
return false; return false;
@@ -78,15 +74,17 @@ public class CommandSave implements TabExecutor {
return; return;
} }
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
//Only allow saving of own books if enabled //Only allow saving of own books if enabled
if (BooksWithoutBordersConfig.getAuthorOnlySave() && !saveToPublicFolder && if (config.getAuthorOnlySave() && !saveToPublicFolder &&
(!player.hasPermission("bookswithoutborders.bypassAuthorOnlySave") && (!player.hasPermission("bookswithoutborders.bypassAuthorOnlySave") &&
BookHelper.isNotAuthor(player, book))) { BookHelper.isNotAuthor(player, book))) {
return; return;
} }
String savePath = BookHelper.getBookDirectoryPathString( String savePath = BookHelper.getBookDirectoryPathString(saveToPublicFolder ?
saveToPublicFolder ? BookDirectory.PUBLIC : BookDirectory.PLAYER, player); BookDirectory.PUBLIC : BookDirectory.PLAYER, player);
if (savePath == null) { if (savePath == null) {
BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! Unable to find the save path!"); BooksWithoutBorders.sendErrorMessage(player, "Saving Failed! Unable to find the save path!");
@@ -123,40 +121,37 @@ public class CommandSave implements TabExecutor {
if (foundDuplicates > 0) { if (foundDuplicates > 0) {
//TODO: Decide if this makes sense or needs to be changed //TODO: Decide if this makes sense or needs to be changed
//Skip duplicate book //Skip duplicate book
if (!fileName.contains("Untitled" + getTitleAuthorSeparator()) && !overwrite) { if (!fileName.contains("Untitled" + config.getTitleAuthorSeparator()) && !overwrite) {
BooksWithoutBorders.sendErrorMessage(player, "Book is already saved!"); BooksWithoutBorders.sendErrorMessage(player, "Book is already saved!");
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " + BooksWithoutBorders.sendErrorMessage(player, "Use " + config.getCommandColor() + (saveToPublicFolder ?
getErrorColor() + "to overwrite!"); "/savepublicbook" : "/savebook") + " true " + config.getErrorColor() + "to overwrite!");
return; return;
} }
//Skip if duplicate limit is reached //Skip if duplicate limit is reached
if (foundDuplicates > BooksWithoutBordersConfig.getBookDuplicateLimit()) { if (foundDuplicates > config.getBookDuplicateLimit()) {
BooksWithoutBorders.sendErrorMessage(player, "Maximum amount of " + fileName + BooksWithoutBorders.sendErrorMessage(player, "Maximum amount of " + fileName +
" duplicates reached!"); " duplicates reached!");
BooksWithoutBorders.sendErrorMessage(player, "Use " + getCommandColor() + "/savebook true " + BooksWithoutBorders.sendErrorMessage(player, "Use " + config.getCommandColor() + (saveToPublicFolder ?
getErrorColor() + "to overwrite!"); "/savepublicbook" : "/savebook") + " true " +
config.getErrorColor() + "to overwrite!");
return; return;
} }
//Alter duplicated filename //Alter duplicated filename
if (fileName.contains("Untitled" + getTitleAuthorSeparator()) && !overwrite) { if (fileName.contains("Untitled" + config.getTitleAuthorSeparator()) && !overwrite) {
fileName = "(" + foundDuplicates + ")" + fileName; fileName = "(" + foundDuplicates + ")" + fileName;
} }
} }
try { try {
if (BooksWithoutBordersConfig.getUseYml()) {
BookToFromTextHelper.bookToYml(savePath, fileName, book); BookToFromTextHelper.bookToYml(savePath, fileName, book);
} else {
BookToFromTextHelper.bookToTXT(savePath, fileName, book);
}
//Update the relevant book list //Update the relevant book list
BooksWithoutBorders.updateBooks(player, saveToPublicFolder); BooksWithoutBorders.updateBooks(player, saveToPublicFolder);
BooksWithoutBorders.sendSuccessMessage(player, "Book Saved as \"" + fileName + ChatColor.RESET + "\""); BooksWithoutBorders.sendSuccessMessage(player, "Book Saved as \"" + fileName + ChatColor.RESET + "\"");
} catch (IOException exception) { } catch (IOException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save book"); BooksWithoutBorders.log(Level.SEVERE, "Unable to save book");
} }
} }

View File

@@ -2,7 +2,8 @@ package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.config.StaticMessage;
import net.knarcraft.bookswithoutborders.manager.EconomyManager;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper; import net.knarcraft.bookswithoutborders.utility.TabCompletionTypeHelper;
import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
@@ -70,10 +71,11 @@ public class CommandSetBookPrice implements TabExecutor {
* @param sender <p>The sender of the command</p> * @param sender <p>The sender of the command</p>
*/ */
private void clearItemPrice(@NotNull CommandSender sender) { private void clearItemPrice(@NotNull CommandSender sender) {
BooksWithoutBordersConfig.setBookPriceType(null); BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
BooksWithoutBordersConfig.setBookPriceQuantity(0); config.setBookPriceType(null);
config.setBookPriceQuantity(0);
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", "Item type name"); booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", "Item type name");
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", BooksWithoutBordersConfig.getBookPriceQuantity()); booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", config.getBookPriceQuantity());
booksWithoutBorders.saveConfig(); booksWithoutBorders.saveConfig();
BooksWithoutBorders.sendSuccessMessage(sender, "Price to create books removed!"); BooksWithoutBorders.sendSuccessMessage(sender, "Price to create books removed!");
@@ -98,10 +100,11 @@ public class CommandSetBookPrice implements TabExecutor {
return false; return false;
} }
BooksWithoutBordersConfig.setBookPriceType(heldItem.getType()); BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
BooksWithoutBordersConfig.setBookPriceQuantity(price); config.setBookPriceType(heldItem.getType());
String newPriceType = BooksWithoutBordersConfig.getBookPriceType().toString(); config.setBookPriceQuantity(price);
double newPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity(); String newPriceType = config.getBookPriceType().toString();
double newPriceQuantity = config.getBookPriceQuantity();
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", newPriceType); booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", newPriceType);
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", newPriceQuantity); booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", newPriceQuantity);
booksWithoutBorders.saveConfig(); booksWithoutBorders.saveConfig();
@@ -119,19 +122,21 @@ public class CommandSetBookPrice implements TabExecutor {
* @return <p>True if the price was changed successfully</p> * @return <p>True if the price was changed successfully</p>
*/ */
private boolean setEconomyPrice(@NotNull CommandSender sender, double price) { private boolean setEconomyPrice(@NotNull CommandSender sender, double price) {
if (EconomyHelper.setupEconomy()) { EconomyManager economyManager = BooksWithoutBorders.getConfiguration().getEconomyManager();
BooksWithoutBordersConfig.setBookPriceQuantity(price); if (economyManager.getEconomy() != null) {
BooksWithoutBordersConfig.setBookPriceType(Material.AIR); BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
double newPriceQuantity = BooksWithoutBordersConfig.getBookPriceQuantity(); config.setBookPriceQuantity(price);
config.setBookPriceType(Material.AIR);
double newPriceQuantity = config.getBookPriceQuantity();
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", "Economy"); booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Item_type", "Economy");
booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", newPriceQuantity); booksWithoutBorders.getConfig().set("Options.Price_to_create_book.Required_quantity", newPriceQuantity);
booksWithoutBorders.saveConfig(); booksWithoutBorders.saveConfig();
BooksWithoutBorders.sendSuccessMessage(sender, "Book creation price set to " + BooksWithoutBorders.sendSuccessMessage(sender, "Book creation price set to " +
EconomyHelper.getEconomy().format(newPriceQuantity) + "!"); economyManager.getEconomy().format(newPriceQuantity) + "!");
return true; return true;
} else { } else {
BooksWithoutBorders.sendErrorMessage(sender, "BooksWithoutBorders failed to hook into Vault! Book price not set!"); BooksWithoutBorders.sendErrorMessage(sender, StaticMessage.EXCEPTION_VAULT_PRICE_NOT_CHANGED.toString());
return false; return false;
} }
} }

View File

@@ -1,7 +1,6 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.container.Bookshelf; import net.knarcraft.bookswithoutborders.container.Bookshelf;
import net.knarcraft.bookswithoutborders.handler.BookshelfHandler; import net.knarcraft.bookswithoutborders.handler.BookshelfHandler;
import net.knarcraft.knarlib.util.TabCompletionHelper; import net.knarcraft.knarlib.util.TabCompletionHelper;
@@ -77,7 +76,7 @@ public class CommandSetBookshelfData implements TabExecutor {
BooksWithoutBorders.sendErrorMessage(commandSender, "You must name the bookshelf before " + BooksWithoutBorders.sendErrorMessage(commandSender, "You must name the bookshelf before " +
"assigning lore!"); "assigning lore!");
} else { } else {
List<String> loreParts = Arrays.asList(builder.toString().split(BooksWithoutBordersConfig.getLoreSeparator())); List<String> loreParts = Arrays.asList(builder.toString().split(BooksWithoutBorders.getConfiguration().getLoreSeparator()));
bookshelf.setLore(loreParts); bookshelf.setLore(loreParts);
shelfHandler.save(); shelfHandler.save();
BooksWithoutBorders.sendSuccessMessage(commandSender, "Lore successfully saved"); BooksWithoutBorders.sendSuccessMessage(commandSender, "Lore successfully saved");

View File

@@ -1,7 +1,6 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import net.knarcraft.knarlib.property.ColorConversion; import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper; import net.knarcraft.knarlib.util.ColorHelper;
@@ -47,7 +46,7 @@ public class CommandSetLore implements TabExecutor {
//Format lore //Format lore
rawLore = ColorHelper.translateColorCodes(rawLore, ColorConversion.RGB); rawLore = ColorHelper.translateColorCodes(rawLore, ColorConversion.RGB);
List<String> newLore = Arrays.asList(rawLore.split(BooksWithoutBordersConfig.getLoreSeparator())); List<String> newLore = Arrays.asList(rawLore.split(BooksWithoutBorders.getConfiguration().getLoreSeparator()));
//Update lore //Update lore
ItemMeta meta = heldItem.getItemMeta(); ItemMeta meta = heldItem.getItemMeta();

View File

@@ -41,7 +41,7 @@ public class CommandSetTitle implements TabExecutor {
return false; return false;
} }
String title = String.join(" ", arguments); String title = String.join(" ", arguments).strip();
title = ColorHelper.translateColorCodes(title, ColorConversion.RGB); title = ColorHelper.translateColorCodes(title, ColorConversion.RGB);
ItemMeta itemMetadata = heldItem.getItemMeta(); ItemMeta itemMetadata = heldItem.getItemMeta();
@@ -53,6 +53,10 @@ public class CommandSetTitle implements TabExecutor {
//Get and change metadata //Get and change metadata
ItemMeta newMetaData; ItemMeta newMetaData;
if (heldItem.getType() == Material.WRITTEN_BOOK) { if (heldItem.getType() == Material.WRITTEN_BOOK) {
if (title.length() > 32) {
BooksWithoutBorders.sendErrorMessage(sender, "Book titles are capped at 32 characters!");
return false;
}
BookMeta bookMetadata = (BookMeta) itemMetadata; BookMeta bookMetadata = (BookMeta) itemMetadata;
bookMetadata.setTitle(title); bookMetadata.setTitle(title);
newMetaData = bookMetadata; newMetaData = bookMetadata;

View File

@@ -1,24 +1,20 @@
package net.knarcraft.bookswithoutborders.command; package net.knarcraft.bookswithoutborders.command;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.ItemSlot; import net.knarcraft.bookswithoutborders.state.ItemSlot;
import net.knarcraft.bookswithoutborders.utility.BookHelper; import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper; import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.Material;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor; import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.WritableBookMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.logging.Level;
/** /**
* Command executor for the unsign command * Command executor for the unsign command
@@ -50,7 +46,7 @@ public class CommandUnSign implements TabExecutor {
* @param player <p>The player holding the book</p> * @param player <p>The player holding the book</p>
* @param mainHand <p>Whether the player is holding the book in its main hand or its off hand</p> * @param mainHand <p>Whether the player is holding the book in its main hand or its off hand</p>
*/ */
public void unSignHeldBook(@NotNull Player player, boolean mainHand) { protected void unSignHeldBook(@NotNull Player player, boolean mainHand) {
//Get the old book //Get the old book
BookMeta oldMetadata = InventoryHelper.getHeldBookMetadata(player, mainHand); BookMeta oldMetadata = InventoryHelper.getHeldBookMetadata(player, mainHand);
if (oldMetadata == null) { if (oldMetadata == null) {
@@ -60,33 +56,21 @@ public class CommandUnSign implements TabExecutor {
ItemStack heldBook = InventoryHelper.getHeldBook(player, mainHand); ItemStack heldBook = InventoryHelper.getHeldBook(player, mainHand);
//Only allow the owner to un-sign the book //Only allow the owner to un-sign the book
if (BooksWithoutBordersConfig.getAuthorOnlyUnsign() && !player.hasPermission("bookswithoutborders.bypassAuthorOnlyUnsign")) { if (BooksWithoutBorders.getConfiguration().getAuthorOnlyUnsign() &&
!player.hasPermission("bookswithoutborders.bypassAuthorOnlyUnsign")) {
if (BookHelper.isNotAuthor(player, Objects.requireNonNull(oldMetadata))) { if (BookHelper.isNotAuthor(player, Objects.requireNonNull(oldMetadata))) {
return; return;
} }
} }
WritableBookMeta newMetadata = (BookMeta) BooksWithoutBorders.getItemFactory().getItemMeta(Material.WRITABLE_BOOK); // Give the player the new book
if (newMetadata == null) { ItemStack book = BookHelper.unsignBook(oldMetadata, heldBook.getAmount());
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to create writable book metadata"); if (book == null) {
return; return;
} }
//Create a new unsigned book with the same data reverseColorCodes(book);
ItemStack newBook = new ItemStack(Material.WRITABLE_BOOK, heldBook.getAmount()); InventoryHelper.replaceHeldItem(player, book, mainHand);
if (oldMetadata.hasLore()) {
List<String> oldLore = oldMetadata.getLore();
if (oldLore != null) {
List<String> newLore = new ArrayList<>(oldLore);
newLore.remove(0);
newMetadata.setLore(newLore);
}
}
newMetadata.setPages(oldMetadata.getPages());
newBook.setItemMeta(newMetadata);
// Give the player the new book
InventoryHelper.replaceHeldItem(player, newBook, mainHand);
} }
@Override @Override
@@ -96,4 +80,25 @@ public class CommandUnSign implements TabExecutor {
return new ArrayList<>(); return new ArrayList<>();
} }
/**
* Reverses colors of a previously formatted book
*
* @param book <p>The book to reverse colors of</p>
*/
private void reverseColorCodes(@NotNull ItemStack book) {
try {
BookMeta meta = (BookMeta) book.getItemMeta();
if (meta != null) {
List<String> newPages = new ArrayList<>(meta.getPages().size());
for (String page : meta.getPages()) {
newPages.add(page.replaceAll("§", "&"));
}
meta.setPages(newPages);
book.setItemMeta(meta);
}
} catch (NullPointerException ignored) {
}
}
} }

View File

@@ -1,19 +1,19 @@
package net.knarcraft.bookswithoutborders.config; package net.knarcraft.bookswithoutborders.config;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.EconomyHelper; import net.knarcraft.bookswithoutborders.manager.EconomyManager;
import net.knarcraft.knarlib.formatting.Translator;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.configuration.Configuration; import org.bukkit.configuration.Configuration;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.FileSystems; import java.nio.file.FileSystems;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendErrorMessage;
import static net.knarcraft.bookswithoutborders.BooksWithoutBorders.sendSuccessMessage;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString; import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
/** /**
@@ -21,50 +21,64 @@ import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.clea
*/ */
public class BooksWithoutBordersConfig { public class BooksWithoutBordersConfig {
private static final ChatColor errorColor = ChatColor.RED; private final ChatColor errorColor = ChatColor.RED;
private static final ChatColor successColor = ChatColor.GREEN; private final ChatColor successColor = ChatColor.GREEN;
private static final ChatColor commandColor = ChatColor.YELLOW; private final ChatColor commandColor = ChatColor.YELLOW;
private static final String SLASH = FileSystems.getDefault().getSeparator(); private final String SLASH = FileSystems.getDefault().getSeparator();
private static boolean isInitialized; private boolean isInitialized;
public static String bookFolder; private final String bookFolder;
private static int bookDuplicateLimit; private int bookDuplicateLimit;
private static String titleAuthorSeparator; private String titleAuthorSeparator;
private static String loreSeparator; private String loreSeparator;
private static List<String> firstBooks = new ArrayList<>(); private List<String> firstBooks = new ArrayList<>();
private static String welcomeMessage; private String welcomeMessage;
private static Material bookPriceType = null; private Material bookPriceType = null;
private static double bookPriceQuantity; private double bookPriceQuantity;
private static boolean authorOnlyCopy; private boolean authorOnlyCopy;
private static boolean authorOnlyUnsign; private boolean authorOnlyUnsign;
private static boolean authorOnlySave; private boolean authorOnlySave;
private static boolean useYml; private boolean adminDecrypt;
private static boolean adminDecrypt; private boolean formatBooks;
private static boolean formatBooks; private boolean changeGenerationOnCopy;
private static boolean changeGenerationOnCopy; private boolean enableBookshelfPeeking;
private static boolean enableBookshelfPeeking; private boolean useRealEncryption;
private final Translator translator;
private EconomyManager economyManager;
/** /**
* Initializes the books without borders settings class * Initializes the books without borders settings class
* *
* @param booksWithoutBorders <p>The books without borders object used for getting required data</p> * @param booksWithoutBorders <p>The books without borders object used for getting required data</p>
*/ */
public static void initialize(@NotNull BooksWithoutBorders booksWithoutBorders) { public BooksWithoutBordersConfig(@NotNull BooksWithoutBorders booksWithoutBorders, @NotNull Translator translator) {
if (isInitialized) { if (isInitialized) {
throw new IllegalArgumentException("Settings class initialized twice. This should not happen!"); throw new IllegalArgumentException("Settings class initialized twice. This should not happen!");
} }
this.translator = translator;
isInitialized = true; isInitialized = true;
bookFolder = booksWithoutBorders.getDataFolder().getAbsolutePath() + getSlash() + "Books" + getSlash(); bookFolder = booksWithoutBorders.getDataFolder().getAbsolutePath() + getSlash() + "Books" + getSlash();
loadConfig(); loadConfig();
} }
/**
* Gets the economy manager
*
* @return <p>The economy manager</p>
*/
@NotNull
public EconomyManager getEconomyManager() {
return this.economyManager;
}
/** /**
* Gets the folder used for storing books * Gets the folder used for storing books
* *
* @return <p>The folder used for storing books</p> * @return <p>The folder used for storing books</p>
*/ */
public static String getBookFolder() { public String getBookFolder() {
return bookFolder; return this.bookFolder;
} }
/** /**
@@ -72,8 +86,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The color to use for error messages</p> * @return <p>The color to use for error messages</p>
*/ */
public static ChatColor getErrorColor() { public ChatColor getErrorColor() {
return errorColor; return this.errorColor;
} }
/** /**
@@ -81,8 +95,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The color to use for success messages</p> * @return <p>The color to use for success messages</p>
*/ */
public static ChatColor getSuccessColor() { public ChatColor getSuccessColor() {
return successColor; return this.successColor;
} }
/** /**
@@ -90,8 +104,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The color used to color commands</p> * @return <p>The color used to color commands</p>
*/ */
public static ChatColor getCommandColor() { public ChatColor getCommandColor() {
return commandColor; return this.commandColor;
} }
/** /**
@@ -99,8 +113,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The slash to use for file separators</p> * @return <p>The slash to use for file separators</p>
*/ */
public static String getSlash() { public String getSlash() {
return SLASH; return this.SLASH;
} }
/** /**
@@ -108,8 +122,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>Whether only the book author can copy it</p> * @return <p>Whether only the book author can copy it</p>
*/ */
public static boolean getAuthorOnlyCopy() { public boolean getAuthorOnlyCopy() {
return authorOnlyCopy; return this.authorOnlyCopy;
} }
/** /**
@@ -117,8 +131,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>Whether only the book author can unsign it</p> * @return <p>Whether only the book author can unsign it</p>
*/ */
public static boolean getAuthorOnlyUnsign() { public boolean getAuthorOnlyUnsign() {
return authorOnlyUnsign; return this.authorOnlyUnsign;
} }
/** /**
@@ -126,8 +140,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>Whether a player can only save their own books</p> * @return <p>Whether a player can only save their own books</p>
*/ */
public static boolean getAuthorOnlySave() { public boolean getAuthorOnlySave() {
return authorOnlySave; return this.authorOnlySave;
} }
/** /**
@@ -135,17 +149,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>True if players can peek at the contained books</p> * @return <p>True if players can peek at the contained books</p>
*/ */
public static boolean getEnableBookshelfPeeking() { public boolean getEnableBookshelfPeeking() {
return enableBookshelfPeeking; return this.enableBookshelfPeeking;
}
/**
* Gets whether to use YML, not TXT, for saving books
*
* @return <p>Whether to use YML for saving books</p>
*/
public static boolean getUseYml() {
return useYml;
} }
/** /**
@@ -153,8 +158,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>Whether admins can bypass the encryption password</p> * @return <p>Whether admins can bypass the encryption password</p>
*/ */
public static boolean getAdminDecrypt() { public boolean getAdminDecrypt() {
return adminDecrypt; return this.adminDecrypt;
} }
/** /**
@@ -162,8 +167,8 @@ public class BooksWithoutBordersConfig {
* *
* @param newQuantity <p>The new quantity necessary for payment</p> * @param newQuantity <p>The new quantity necessary for payment</p>
*/ */
public static void setBookPriceQuantity(double newQuantity) { public void setBookPriceQuantity(double newQuantity) {
bookPriceQuantity = newQuantity; this.bookPriceQuantity = newQuantity;
} }
/** /**
@@ -171,8 +176,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The quantity necessary for payment</p> * @return <p>The quantity necessary for payment</p>
*/ */
public static double getBookPriceQuantity() { public double getBookPriceQuantity() {
return bookPriceQuantity; return this.bookPriceQuantity;
} }
/** /**
@@ -183,8 +188,8 @@ public class BooksWithoutBordersConfig {
* *
* @param newType <p>The new item type to use for book pricing</p> * @param newType <p>The new item type to use for book pricing</p>
*/ */
public static void setBookPriceType(Material newType) { public void setBookPriceType(Material newType) {
bookPriceType = newType; this.bookPriceType = newType;
} }
/** /**
@@ -195,8 +200,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The item type used for book pricing</p> * @return <p>The item type used for book pricing</p>
*/ */
public static Material getBookPriceType() { public Material getBookPriceType() {
return bookPriceType; return this.bookPriceType;
} }
/** /**
@@ -204,8 +209,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The welcome message to show new players</p> * @return <p>The welcome message to show new players</p>
*/ */
public static String getWelcomeMessage() { public String getWelcomeMessage() {
return welcomeMessage; return this.welcomeMessage;
} }
/** /**
@@ -213,8 +218,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The book duplicate limit</p> * @return <p>The book duplicate limit</p>
*/ */
public static int getBookDuplicateLimit() { public int getBookDuplicateLimit() {
return bookDuplicateLimit; return this.bookDuplicateLimit;
} }
/** /**
@@ -222,8 +227,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>True if books should change their generation</p> * @return <p>True if books should change their generation</p>
*/ */
public static boolean changeGenerationOnCopy() { public boolean changeGenerationOnCopy() {
return changeGenerationOnCopy; return this.changeGenerationOnCopy;
} }
/** /**
@@ -231,8 +236,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The separator between title and author</p> * @return <p>The separator between title and author</p>
*/ */
public static String getTitleAuthorSeparator() { public String getTitleAuthorSeparator() {
return titleAuthorSeparator; return this.titleAuthorSeparator;
} }
/** /**
@@ -240,8 +245,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The separator used to denote lore newline</p> * @return <p>The separator used to denote lore newline</p>
*/ */
public static String getLoreSeparator() { public String getLoreSeparator() {
return loreSeparator; return this.loreSeparator;
} }
/** /**
@@ -249,8 +254,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>Whether all books should be formatted</p> * @return <p>Whether all books should be formatted</p>
*/ */
public static boolean formatBooks() { public boolean formatBooks() {
return formatBooks; return this.formatBooks;
} }
/** /**
@@ -258,8 +263,8 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>The books to give new players</p> * @return <p>The books to give new players</p>
*/ */
public static List<String> getFirstBooks() { public List<String> getFirstBooks() {
return new ArrayList<>(firstBooks); return new ArrayList<>(this.firstBooks);
} }
/** /**
@@ -267,29 +272,48 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>True if players need to pay for printing books</p> * @return <p>True if players need to pay for printing books</p>
*/ */
public static boolean booksHavePrice() { public boolean booksHavePrice() {
return (bookPriceType != null && bookPriceQuantity > 0); return (this.bookPriceType != null && this.bookPriceQuantity > 0);
}
/**
* Checks whether to use real encryption for encrypted books
*
* @return <p>True if real encryption should be used</p>
*/
public boolean useRealEncryption() {
return this.useRealEncryption;
}
/**
* Gets the path used to store encrypted books
*
* @return <p>The encrypted book path</p>
*/
@NotNull
public String getEncryptedBookPath() {
return getBookFolder() + "Encrypted" + getSlash();
} }
/** /**
* Saves the config * Saves the config
*/ */
public static void saveConfigValues() { public void saveConfigValues() {
ConsoleCommandSender consoleSender = BooksWithoutBorders.getInstance().getServer().getConsoleSender(); Logger logger = BooksWithoutBorders.getInstance().getLogger();
Configuration config = BooksWithoutBorders.getInstance().getConfig(); Configuration config = BooksWithoutBorders.getInstance().getConfig();
config.set(ConfigOption.USE_YAML.getConfigNode(), useYml); config.set(ConfigOption.MAX_DUPLICATES.getConfigNode(), this.bookDuplicateLimit);
config.set(ConfigOption.MAX_DUPLICATES.getConfigNode(), bookDuplicateLimit); config.set(ConfigOption.TITLE_AUTHOR_SEPARATOR.getConfigNode(), this.titleAuthorSeparator);
config.set(ConfigOption.TITLE_AUTHOR_SEPARATOR.getConfigNode(), titleAuthorSeparator); config.set(ConfigOption.LORE_LINE_SEPARATOR.getConfigNode(), this.loreSeparator);
config.set(ConfigOption.LORE_LINE_SEPARATOR.getConfigNode(), loreSeparator); config.set(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode(), this.firstBooks);
config.set(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode(), firstBooks); config.set(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), this.welcomeMessage);
config.set(ConfigOption.MESSAGE_FOR_NEW_PLAYERS.getConfigNode(), welcomeMessage); config.set(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), this.formatBooks);
config.set(ConfigOption.FORMAT_AFTER_SIGNING.getConfigNode(), formatBooks); config.set(ConfigOption.ENABLE_BOOKSHELF_PEEKING.getConfigNode(), this.enableBookshelfPeeking);
config.set(ConfigOption.ENABLE_BOOKSHELF_PEEKING.getConfigNode(), enableBookshelfPeeking); config.set(ConfigOption.USE_REAL_ENCRYPTION.getConfigNode(), this.useRealEncryption);
String itemTypeNode = ConfigOption.PRICE_ITEM_TYPE.getConfigNode(); String itemTypeNode = ConfigOption.PRICE_ITEM_TYPE.getConfigNode();
if (bookPriceType != null) { if (this.bookPriceType != null) {
if (bookPriceType != Material.AIR) { if (this.bookPriceType != Material.AIR) {
config.set(itemTypeNode, bookPriceType.toString()); config.set(itemTypeNode, this.bookPriceType.toString());
} else { } else {
config.set(itemTypeNode, "Economy"); config.set(itemTypeNode, "Economy");
} }
@@ -297,24 +321,23 @@ public class BooksWithoutBordersConfig {
config.set(itemTypeNode, "Item type name"); config.set(itemTypeNode, "Item type name");
} }
config.set(ConfigOption.PRICE_QUANTITY.getConfigNode(), bookPriceQuantity); config.set(ConfigOption.PRICE_QUANTITY.getConfigNode(), this.bookPriceQuantity);
config.set(ConfigOption.ADMIN_AUTO_DECRYPT.getConfigNode(), adminDecrypt); config.set(ConfigOption.ADMIN_AUTO_DECRYPT.getConfigNode(), this.adminDecrypt);
config.set(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), authorOnlyCopy); config.set(ConfigOption.AUTHOR_ONLY_COPY.getConfigNode(), this.authorOnlyCopy);
config.set(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), authorOnlyUnsign); config.set(ConfigOption.AUTHOR_ONLY_UNSIGN.getConfigNode(), this.authorOnlyUnsign);
config.set(ConfigOption.AUTHOR_ONLY_SAVE.getConfigNode(), authorOnlySave); config.set(ConfigOption.AUTHOR_ONLY_SAVE.getConfigNode(), this.authorOnlySave);
config.set(ConfigOption.CHANGE_GENERATION_ON_COPY.getConfigNode(), changeGenerationOnCopy); config.set(ConfigOption.CHANGE_GENERATION_ON_COPY.getConfigNode(), this.changeGenerationOnCopy);
//Handles old book and quill settings //Handles old book and quill settings
if (config.contains("Options.Require_book_and_quill_to_create_book")) { if (config.contains("Options.Require_book_and_quill_to_create_book")) {
sendSuccessMessage(consoleSender, "[BooksWithoutBorders] Found old config setting " + logger.log(Level.INFO, "[BooksWithoutBorders] Found old config setting " +
"\"Require_book_and_quill_to_create_book\""); "\"Require_book_and_quill_to_create_book\"\nUpdating to \"Price_to_create_book\" settings");
sendSuccessMessage(consoleSender, "Updating to \"Price_to_create_book\" settings");
if (config.getBoolean("Options.Require_book_and_quill_to_create_book")) { if (config.getBoolean("Options.Require_book_and_quill_to_create_book")) {
bookPriceType = Material.WRITABLE_BOOK; this.bookPriceType = Material.WRITABLE_BOOK;
bookPriceQuantity = 1; this.bookPriceQuantity = 1;
config.set("Options.Price_to_create_book.Item_type", bookPriceType.toString()); config.set("Options.Price_to_create_book.Item_type", this.bookPriceType.toString());
config.set("Options.Price_to_create_book.Required_quantity", bookPriceQuantity); config.set("Options.Price_to_create_book.Required_quantity", this.bookPriceQuantity);
} }
config.set("Options.Require_book_and_quill_to_create_book", null); config.set("Options.Require_book_and_quill_to_create_book", null);
} }
@@ -326,56 +349,59 @@ public class BooksWithoutBordersConfig {
* *
* @return <p>True if the config was loaded successfully</p> * @return <p>True if the config was loaded successfully</p>
*/ */
public static boolean loadConfig() { public boolean loadConfig() {
ConsoleCommandSender consoleSender = BooksWithoutBorders.getInstance().getServer().getConsoleSender(); Logger logger = BooksWithoutBorders.getInstance().getLogger();
BooksWithoutBorders.getInstance().reloadConfig(); BooksWithoutBorders.getInstance().reloadConfig();
Configuration config = BooksWithoutBorders.getInstance().getConfig(); Configuration config = BooksWithoutBorders.getInstance().getConfig();
try { try {
useYml = getBoolean(config, ConfigOption.USE_YAML); this.bookDuplicateLimit = config.getInt(ConfigOption.MAX_DUPLICATES.getConfigNode(),
bookDuplicateLimit = config.getInt(ConfigOption.MAX_DUPLICATES.getConfigNode(),
(Integer) ConfigOption.MAX_DUPLICATES.getDefaultValue()); (Integer) ConfigOption.MAX_DUPLICATES.getDefaultValue());
titleAuthorSeparator = getString(config, ConfigOption.TITLE_AUTHOR_SEPARATOR); this.titleAuthorSeparator = getString(config, ConfigOption.TITLE_AUTHOR_SEPARATOR);
loreSeparator = getString(config, ConfigOption.LORE_LINE_SEPARATOR); this.loreSeparator = getString(config, ConfigOption.LORE_LINE_SEPARATOR);
adminDecrypt = getBoolean(config, ConfigOption.ADMIN_AUTO_DECRYPT); this.adminDecrypt = getBoolean(config, ConfigOption.ADMIN_AUTO_DECRYPT);
authorOnlyCopy = getBoolean(config, ConfigOption.AUTHOR_ONLY_COPY); this.authorOnlyCopy = getBoolean(config, ConfigOption.AUTHOR_ONLY_COPY);
authorOnlyUnsign = getBoolean(config, ConfigOption.AUTHOR_ONLY_UNSIGN); this.authorOnlyUnsign = getBoolean(config, ConfigOption.AUTHOR_ONLY_UNSIGN);
authorOnlySave = getBoolean(config, ConfigOption.AUTHOR_ONLY_SAVE); this.authorOnlySave = getBoolean(config, ConfigOption.AUTHOR_ONLY_SAVE);
firstBooks = config.getStringList(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode()); this.firstBooks = config.getStringList(ConfigOption.BOOKS_FOR_NEW_PLAYERS.getConfigNode());
welcomeMessage = getString(config, ConfigOption.MESSAGE_FOR_NEW_PLAYERS); this.welcomeMessage = getString(config, ConfigOption.MESSAGE_FOR_NEW_PLAYERS);
formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING); this.formatBooks = getBoolean(config, ConfigOption.FORMAT_AFTER_SIGNING);
changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY); this.changeGenerationOnCopy = getBoolean(config, ConfigOption.CHANGE_GENERATION_ON_COPY);
enableBookshelfPeeking = getBoolean(config, ConfigOption.ENABLE_BOOKSHELF_PEEKING); this.enableBookshelfPeeking = getBoolean(config, ConfigOption.ENABLE_BOOKSHELF_PEEKING);
this.useRealEncryption = getBoolean(config, ConfigOption.USE_REAL_ENCRYPTION);
String language = config.getString("language", "en");
this.translator.loadLanguages(BooksWithoutBorders.getInstance().getDataFolder(), "en", language);
//Convert string into material //Convert string into material
this.economyManager = new EconomyManager();
String paymentMaterial = getString(config, ConfigOption.PRICE_ITEM_TYPE); String paymentMaterial = getString(config, ConfigOption.PRICE_ITEM_TYPE);
if (paymentMaterial.equalsIgnoreCase("Economy")) { if (paymentMaterial.equalsIgnoreCase("Economy")) {
if (EconomyHelper.setupEconomy()) { if (this.economyManager.getEconomy() == null) {
bookPriceType = Material.AIR; logger.log(Level.SEVERE, StaticMessage.EXCEPTION_VAULT_NOT_AVAILABLE.toString());
this.bookPriceType = null;
} else { } else {
sendErrorMessage(consoleSender, this.bookPriceType = Material.AIR;
"BooksWithoutBorders failed to hook into Vault! Book price not set!");
bookPriceType = null;
} }
} else if (!paymentMaterial.trim().isEmpty()) { } else if (!paymentMaterial.trim().isEmpty()) {
Material material = Material.matchMaterial(paymentMaterial); Material material = Material.matchMaterial(paymentMaterial);
if (material != null) { if (material != null) {
bookPriceType = material; this.bookPriceType = material;
} }
} }
bookPriceQuantity = config.getDouble(ConfigOption.PRICE_QUANTITY.getConfigNode(), this.bookPriceQuantity = config.getDouble(ConfigOption.PRICE_QUANTITY.getConfigNode(),
(Double) ConfigOption.PRICE_QUANTITY.getDefaultValue()); (Double) ConfigOption.PRICE_QUANTITY.getDefaultValue());
//Make sure titleAuthorSeparator is a valid value //Make sure titleAuthorSeparator is a valid value
titleAuthorSeparator = cleanString(titleAuthorSeparator); this.titleAuthorSeparator = cleanString(this.titleAuthorSeparator);
if (titleAuthorSeparator.length() != 1) { if (this.titleAuthorSeparator.length() != 1) {
sendErrorMessage(consoleSender, "Title-Author_Separator is set to an invalid value!"); logger.log(Level.SEVERE, "Title-Author_Separator is set to an invalid value!\n" +
sendErrorMessage(consoleSender, "Reverting to default value of \",\""); "Reverting to default value of \",\"");
titleAuthorSeparator = ","; this.titleAuthorSeparator = ",";
config.set("Options.Title-Author_Separator", titleAuthorSeparator); config.set("Options.Title-Author_Separator", this.titleAuthorSeparator);
} }
} catch (Exception e) { } catch (Exception exception) {
sendErrorMessage(consoleSender, "Warning! Config.yml failed to load!"); logger.log(Level.SEVERE, exception.getMessage());
sendErrorMessage(consoleSender, "Try Looking for settings that are missing values!"); logger.log(Level.SEVERE, "Warning! Config.yml failed to load!\n" +
"Try Looking for settings that are missing values!");
return false; return false;
} }

View File

@@ -0,0 +1,182 @@
package net.knarcraft.bookswithoutborders.config;
import org.jetbrains.annotations.NotNull;
/**
* A representation of a BwB command
*/
public enum BwBCommand {
/**
* The help command
*/
BOOKS_WITHOUT_BORDERS("booksWithoutBorders", false),
/**
* Clears the contents of a book
*/
CLEAR_BOOK("clearBook", true),
/**
* Copies the held book
*/
COPY_BOOK("copyBook", true),
/**
* Decrypts the held encrypted book
*/
DECRYPT_BOOK("decryptBook", true),
/**
* Deletes a book from a player's private collection
*/
DELETE_BOOK("deleteBook", true),
/**
* Deletes a book from the public collection
*/
DELETE_PUBLIC_BOOK("deletePublicBook", false),
/**
* Encrypts the held book
*/
ENCRYPT_BOOK("encryptBook", true),
/**
* Executes formatting codes in the held book
*/
FORMAT_BOOK("formatBook", true),
/**
* Gives a book from a player's private collection to another player
*/
GIVE_BOOK("giveBook", true),
/**
* Gives a book from the public collection to a player
*/
GIVE_PUBLIC_BOOK("givePublicBook", false),
/**
* Encrypts a book for specific group
*/
GROUP_ENCRYPT_BOOK("groupEncryptBook", true),
/**
* Loads a book from a player's private collection
*/
LOAD_BOOK("loadBook", true),
/**
* Loads a book from the public collection
*/
LOAD_PUBLIC_BOOK("loadPublicBook", true),
/**
* Reloads the plugin's configuration and the book lists
*/
RELOAD("reload", false),
/**
* Saves a book to a player's private collection
*/
SAVE_BOOK("saveBook", true),
/**
* Saves a book to the public collection
*/
SAVE_PUBLIC_BOOK("savePublicBook", true),
/**
* Sets the author of the held book
*/
SET_BOOK_AUTHOR("setBookAuthor", true),
/**
* Sets the generation of the held book
*/
SET_BOOK_GENERATION("setBookGeneration", true),
/**
* Sets the price of copying, loading and giving books
*/
SET_BOOK_PRICE("setBookPrice", false),
/**
* Sets the name/lore for the chiseled bookshelf in front of the player, displayed when peeking the bookshelf
*/
SET_BOOKSHELF_DATA("setBookshelfData", true),
/**
* Sets the lore of the held book/item
*/
SET_LORE("setLore", true),
/**
* Sets the book title of the held signed book, or the display name of the held item
*/
SET_TITLE("setTitle", true),
/**
* Un-signs the held signed book
*/
UNSIGN_BOOK("unsignBook", true),
/**
* Adds a title page, blank page or chapter page to the held book
*
* <p>If no input is given, and a signed book is provided, a title page will be added to the beginning of the book.
* If an index is given, and a signed book is provided, a title page will be added to the specified index.
* If no input is given, and an unsigned book is provided, an error will be displayed.
* If an index is given, but no text, and a signed book is provided, a blank page is added to the specified index.
* If both an index and text is given, and a signed or unsigned book is provided, a custom chapter page depending on
* the text will be added to the specified index.</p>
*/
ADD_TITLE_PAGE("addBookTitlePage", true),
/**
* Deletes a page from the held book
*/
DELETE_PAGE("deleteBookPage", true),
/**
* Migrates all books, fixing any problems with their names, and converts txt books to yml
*/
MIGRATE("migrateBooks", true),
;
private final @NotNull String commandName;
private final boolean requiresPlayer;
/**
* Instantiates a new command
*
* @param commandName <p>The name of the command</p>
* @param requiresPlayer <p>Whether the command requires to be run by a player</p>
*/
BwBCommand(@NotNull String commandName, boolean requiresPlayer) {
this.commandName = commandName;
this.requiresPlayer = requiresPlayer;
}
/**
* Gets whether this command can only be used by a player
*
* @return <p>True if this command must be run by a player</p>
*/
public boolean requiresPlayer() {
return this.requiresPlayer;
}
/**
* Return name instead of enum when displayed as a string
*
* @return <p>The command name</p>
*/
@Override
@NotNull
public String toString() {
return this.commandName;
}
}

View File

@@ -7,11 +7,6 @@ import org.jetbrains.annotations.NotNull;
*/ */
public enum ConfigOption { public enum ConfigOption {
/**
* Whether YAML should be used to store books instead of simple text files
*/
USE_YAML("Options.Save_Books_in_Yaml_Format", true),
/** /**
* The max duplicates of a book that can be saved * The max duplicates of a book that can be saved
*/ */
@@ -80,7 +75,12 @@ public enum ConfigOption {
/** /**
* Whether hitting a bookshelf should display information about the contained books * Whether hitting a bookshelf should display information about the contained books
*/ */
ENABLE_BOOKSHELF_PEEKING("Options.Enable_Book_Peeking", false), ENABLE_BOOKSHELF_PEEKING("Options.Enable_Book_Peeking", true),
/**
* Whether to use real AES encryption instead of storing garbled book text, while the full plaintext is stored in a file
*/
USE_REAL_ENCRYPTION("Options.Use_Real_Encryption", false),
; ;
private final String configNode; private final String configNode;

View File

@@ -0,0 +1,69 @@
package net.knarcraft.bookswithoutborders.config;
import org.jetbrains.annotations.NotNull;
/**
* A representation of a BwB permission
*/
public enum Permission {
/**
* The permission for bypassing paying the set book printing price
*/
BYPASS_BOOK_PRICE("bypassBookPrice"),
/**
* The admin permission, giving most/all other permissions
*/
ADMIN("admin"),
/**
* The permission for bypassing the setting making sure a book can only be copied by its author
*/
BYPASS_AUTHOR_ONLY_COPY("bypassAuthorOnlyCopy"),
/**
* Does nothing by itself, but its child nodes allow decrypting group encrypted books for the specified group
*/
DECRYPT("decrypt"),
/**
* The permission for formatting a book's text either through the `/formatBook` command or automatically
*/
FORMAT("format"),
;
private final @NotNull String node;
/**
* Instantiates a new permission
*
* @param node <p>The permission's permission node</p>
*/
Permission(@NotNull String node) {
this.node = node;
}
/**
* Gets the node of this permission
*
* @return <p>The permission node</p>
*/
@NotNull
public String getNode() {
return "bookswithoutborders." + this.node;
}
/**
* Return node instead of enum when displayed as a string
*
* @return <p>The permission node string</p>
*/
@Override
@NotNull
public String toString() {
return getNode();
}
}

View File

@@ -0,0 +1,33 @@
package net.knarcraft.bookswithoutborders.config;
import org.jetbrains.annotations.NotNull;
/**
* Messages shown in the console that cannot be altered by users
*/
public enum StaticMessage {
BOOK_SAVING_FAILED("Saving failed! Aborting..."),
BOOK_FOLDER_CREATE_FAILED("Unable to create necessary folders"),
COMMAND_NOT_REGISTERED("Command {command} has not been registered!"),
EXCEPTION_VAULT_NOT_AVAILABLE("Vault is unavailable, but book price is set to economy. Unsetting book cost!"),
EXCEPTION_VAULT_PRICE_NOT_CHANGED("BooksWithoutBorders failed to hook into Vault! Book price not set!");
private final @NotNull String messageString;
/**
* Instantiates a new static message
*
* @param messageString <p>The message string</p>
*/
StaticMessage(@NotNull String messageString) {
this.messageString = messageString;
}
@Override
@NotNull
public String toString() {
return this.messageString;
}
}

View File

@@ -0,0 +1,257 @@
package net.knarcraft.bookswithoutborders.config;
import net.knarcraft.knarlib.formatting.TranslatableMessage;
import org.jetbrains.annotations.NotNull;
/**
* An enum representing all translatable messages
*/
public enum Translatable implements TranslatableMessage {
/**
* The success message displayed when the copy command succeeds
*/
SUCCESS_COPY,
/**
* The success message displayed when the clear command succeeds
*/
SUCCESS_CLEARED,
/**
* The success message displayed when the decrypt command succeeds
*/
SUCCESS_DECRYPTED,
/**
* The success message displayed when a book is successfully decrypted without providing the key
*/
SUCCESS_AUTO_DECRYPTED,
/**
* The success message displayed when a book is successfully deleted
*/
SUCCESS_DELETED,
/**
* The success message displayed when a book is successfully formatted
*/
SUCCESS_FORMATTED,
/**
* The success message displayed when a book is successfully sent
*/
SUCCESS_GIVE_SENT,
/**
* The success message displayed when a book is successfully received
*/
SUCCESS_GIVE_RECEIVED,
/**
* The error to display when the console attempts to run a player-only command
*/
ERROR_PLAYER_ONLY,
/**
* The error displayed when running a relevant command without holding a written book
*/
ERROR_NOT_HOLDING_WRITTEN_BOOK,
/**
* The error displayed when running a relevant command without holding a writable book
*/
ERROR_NOT_HOLDING_WRITABLE_BOOK,
/**
* The error displayed when running a relevant command while holding one book in each hand
*/
ERROR_ONLY_ONE_BOOK,
/**
* The error displayed when running the copy command without specifying the amount of copies
*/
ERROR_COPY_COUNT_NOT_SPECIFIED,
/**
* The error displayed when supplying the copy command with a negative number
*/
ERROR_COPY_NEGATIVE_AMOUNT,
/**
* The error displayed when supplying the copy command with a non-number
*/
ERROR_COPY_INVALID_AMOUNT,
/**
* The error displayed when trying to copy a tattered book or a copy of a copy
*/
ERROR_BOOK_COPIED_TOO_FAR,
/**
* The error displayed when unable to get book metadata from the held book
*/
ERROR_METADATA_MISSING,
/**
* The error displayed when trying to receive a book with a full inventory
*/
ERROR_INVENTORY_FULL,
/**
* The error displayed when trying to do a Vault transaction while Vault is unavailable
*/
ERROR_VAULT_COST_BUT_UNAVAILABLE,
/**
* The error displayed when trying to decrypt a book without displaying the key
*/
ERROR_DECRYPT_MISSING_KEY,
/**
* The error displayed when failing to decrypt a book for any reason
*/
ERROR_DECRYPT_FAILED,
/**
* The error displayed when listing all encrypted files returns null
*/
ERROR_ENCRYPTED_DIRECTORY_EMPTY_OR_MISSING,
/**
* The error displayed when failing to match an encrypted book during admin decrypt
*/
ERROR_ENCRYPTED_BOOK_UNKNOWN,
/**
* The error displayed when trying to delete a book while the book folder is empty or missing
*/
ERROR_DELETE_EMPTY,
/**
* The error displayed when given the name of a book that does not exist
*/
ERROR_INCORRECT_FILE_NAME,
/**
* The error displayed when a file failed to be deleted, without throwing an exception
*/
ERROR_DELETE_FAILED_SILENT,
/**
* The error displayed when a file failed to be deleted, after throwing an exception
*/
ERROR_DELETE_FAILED_EXCEPTION,
/**
* The error displayed when trying to encrypt a book without supplying a key
*/
ERROR_ENCRYPT_NO_KEY,
/**
* The error displayed when exceeding the number of allowed arguments for a command
*/
ERROR_TOO_MANY_ARGUMENTS_COMMAND,
/**
* The error displayed when trying to encrypt an empty book
*/
ERROR_ENCRYPT_EMPTY,
/**
* The error displayed after using the give command without specifying a recipient
*/
ERROR_GIVE_NO_RECIPIENT,
/**
* The error displayed when using the give command with an invalid recipient
*/
ERROR_GIVE_RECIPIENT_UNKNOWN,
/**
* The error displayed when using the give command with a recipient with a full inventory
*/
ERROR_GIVE_RECIPIENT_FULL,
/**
* The error displayed when using the give command with an invalid number of copies (unlikely to ever happen)
*/
ERROR_GIVE_INVALID_COPIES_AMOUNT,
/**
* The error displayed when a book is not properly loaded when using the give command
*/
ERROR_GIVE_LOAD_FAILED,
/**
* The error displayed when running a command without holding any book in the main hand
*/
ERROR_NOT_HOLDING_ANY_BOOK,
/**
* The header displayed before printing all commands
*/
NEUTRAL_COMMANDS_HEADER,
/**
* The format used when displaying a book's economy price
*/
NEUTRAL_COMMANDS_BOOK_PRICE_ECO,
/**
* The format used when displaying a book's item price
*/
NEUTRAL_COMMANDS_BOOK_PRICE_ITEM,
/**
* The format used when displaying one command entry
*/
NEUTRAL_COMMANDS_COMMAND,
/**
* The string used when displaying that a command requires no permission
*/
NEUTRAL_COMMANDS_COMMAND_NO_PERMISSION_REQUIRED,
/**
* The format used when displaying a command's required permission
*/
NEUTRAL_COMMANDS_COMMAND_PERMISSION,
/**
* The translation of unknown author for a book
*/
NEUTRAL_UNKNOWN_AUTHOR,
/**
* The translation of the copy action
*/
ACTION_COPY,
/**
* The translation of the clear action
*/
ACTION_CLEAR,
/**
* The translation of the decrypt action
*/
ACTION_DECRYPT,
/**
* The translation of the encrypt action
*/
ACTION_ENCRYPT,
/**
* The translation of the format action
*/
ACTION_FORMAT,
;
@Override
public @NotNull TranslatableMessage[] getAllMessages() {
return Translatable.values();
}
}

View File

@@ -0,0 +1,15 @@
package net.knarcraft.bookswithoutborders.container;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* A request for migrating a book
*
* @param file <p>The file to migrate</p>
* @param player <p>The player that initiated the migration</p>
*/
public record MigrationRequest(@NotNull File file, @NotNull Player player) {
}

View File

@@ -29,33 +29,58 @@ import java.util.logging.Level;
* *
* @author EpicKnarvik97 * @author EpicKnarvik97
*/ */
public class AES { public class AES implements Encryptor {
//TODO: Generate salt for each installation, and figure out what to to with the iv parameter private final @NotNull IvParameterSpec ivParameterSpec;
private final IvParameterSpec ivParameterSpec;
private final byte[] passwordSalt; private final byte[] passwordSalt;
private final @NotNull String password;
/** /**
* Instantiates a new AES encryptor * Instantiates a new AES encryptor
* *
* @param initializationVector <p>The initialization vector to use for CBC</p> * @param aesConfiguration <p>The AES configuration to use</p>
* @param passwordSalt <p>The password salt to use</p>
*/ */
public AES(byte[] initializationVector, byte[] passwordSalt) { public AES(@NotNull AESConfiguration aesConfiguration) {
this.ivParameterSpec = new IvParameterSpec(initializationVector); this.ivParameterSpec = new IvParameterSpec(aesConfiguration.iv());
this.passwordSalt = passwordSalt; this.passwordSalt = aesConfiguration.salt();
this.password = aesConfiguration.key();
}
@Override
@Nullable
public String encryptText(@NotNull String input) {
return encryptDecryptText(input, true);
}
@Override
@Nullable
public String decryptText(@NotNull String input) {
return encryptDecryptText(input, false);
}
/**
* Generates a 16-byte initialization vector
*
* @return <p>An initialization vector</p>
*/
public static byte[] generateIV() {
byte[] initializationVector = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(initializationVector);
return initializationVector;
} }
/** /**
* Encrypts or decrypts the given text * Encrypts or decrypts the given text
* *
* <p>Note: The same IV and salt must be used during instantiation in order to decrypt an encrypted message.</p>
*
* @param input <p>The input to encrypt or decrypt</p> * @param input <p>The input to encrypt or decrypt</p>
* @param password <p>The password to use for key generation</p>
* @param encrypt <p>Whether to encrypt or decrypt the input</p> * @param encrypt <p>Whether to encrypt or decrypt the input</p>
* @return <p>The encrypted/decrypted input, or null if anything went wrong</p> * @return <p>The encrypted/decrypted input, or null if anything went wrong</p>
*/ */
@Nullable @Nullable
public String encryptDecryptText(@NotNull String input, @NotNull String password, boolean encrypt) { private String encryptDecryptText(@NotNull String input, boolean encrypt) {
//Make a key from the password //Make a key from the password
SecretKeySpec secretKeySpec = getKeyFromPassword(password); SecretKeySpec secretKeySpec = getKeyFromPassword(password);
//Get cipher instance //Get cipher instance
@@ -73,31 +98,22 @@ public class AES {
try { try {
aes.init(mode, secretKeySpec, ivParameterSpec); aes.init(mode, secretKeySpec, ivParameterSpec);
} catch (InvalidKeyException | InvalidAlgorithmParameterException exception) { } catch (InvalidKeyException | InvalidAlgorithmParameterException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES input given!"); BooksWithoutBorders.log(Level.SEVERE, "Invalid AES input given!");
return null; return null;
} }
//Perform encryption/decryption and output result //Perform encryption/decryption and output result
try { try {
byte[] output = aes.doFinal(getInputBytes(input, encrypt)); byte[] output = aes.doFinal(getInputBytes(input, encrypt));
return createResult(output, encrypt); return createResult(output, encrypt);
} catch (IllegalBlockSizeException | BadPaddingException exception) { } catch (IllegalBlockSizeException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES block size or padding"); BooksWithoutBorders.log(Level.SEVERE, "Invalid AES block size during finalization");
return null;
} catch (BadPaddingException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES padding during finalization");
return null; return null;
} }
} }
/**
* Generates a 16-byte initialization vector
*
* @return <p>An initialization vector</p>
*/
public static byte[] generateIV() {
byte[] initializationVector = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(initializationVector);
return initializationVector;
}
/** /**
* Transforms the input string into bytes * Transforms the input string into bytes
* *
@@ -139,8 +155,11 @@ public class AES {
Cipher aes; Cipher aes;
try { try {
aes = Cipher.getInstance("AES/CBC/PKCS5Padding"); aes = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException | NoSuchPaddingException exception) { } catch (NoSuchAlgorithmException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES algorithm or padding"); BooksWithoutBorders.log(Level.SEVERE, "Invalid AES algorithm during Cipher generation");
return null;
} catch (NoSuchPaddingException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Invalid AES padding during Cipher generation");
return null; return null;
} }
return aes; return aes;
@@ -159,14 +178,14 @@ public class AES {
try { try {
keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
} catch (NoSuchAlgorithmException exception) { } catch (NoSuchAlgorithmException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES algorithm"); BooksWithoutBorders.log(Level.SEVERE, "Invalid AES algorithm");
return null; return null;
} }
SecretKey tmp; SecretKey tmp;
try { try {
tmp = keyFactory.generateSecret(spec); tmp = keyFactory.generateSecret(spec);
} catch (InvalidKeySpecException exception) { } catch (InvalidKeySpecException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Invalid AES key specification"); BooksWithoutBorders.log(Level.SEVERE, "Invalid AES key specification");
return null; return null;
} }
return new SecretKeySpec(tmp.getEncoded(), "AES"); return new SecretKeySpec(tmp.getEncoded(), "AES");

View File

@@ -0,0 +1,24 @@
package net.knarcraft.bookswithoutborders.encryption;
import org.jetbrains.annotations.NotNull;
/**
* A configuration for AES encryption
*
* @param iv <p>The initialization vector</p>
* @param salt <p>The encryption salt</p>
* @param key <p>The encryption key</p>
*/
public record AESConfiguration(byte @NotNull [] iv, byte @NotNull [] salt, @NotNull String key) {
/**
* Generates a new AES configuration with randomized IV and salt
*
* @param key <p>The encryption key to use</p>
* @return <p>The new AES configuration</p>
*/
public static AESConfiguration getNewConfiguration(@NotNull String key) {
return new AESConfiguration(AES.generateIV(), AES.generateIV(), key);
}
}

View File

@@ -0,0 +1,69 @@
package net.knarcraft.bookswithoutborders.encryption;
import org.jetbrains.annotations.NotNull;
/**
* This enum represents the different available encryption styles
*/
public enum EncryptionStyle {
/**
* Possibly lossy encryption using DNA codons
*/
DNA("dna"),
/**
* A simple cipher using the key to substitute one character for another
*/
SUBSTITUTION("substitution"),
/**
* A military-grade encryption cypher
*/
AES("aes"),
/**
* An unbreakable encryption method assuming the key is completely random and never used more than once, ever
*/
ONE_TIME_PAD("onetimepad"),
/**
* Just a way of using magic text to make text illegible
*/
MAGIC("magic"),
;
private final String name;
/**
* Instantiates a new encryption style
*
* @param name <p>The human-readable encryption style name</p>
*/
EncryptionStyle(@NotNull String name) {
this.name = name;
}
/**
* Gets an encryption style given its name
*
* @param name <p>The name of the encryption style</p>
* @return <p>An encryption style or null if no match is found</p>
*/
@NotNull
public static EncryptionStyle getFromString(@NotNull String name) {
for (EncryptionStyle style : EncryptionStyle.values()) {
if (style.name.equalsIgnoreCase(name)) {
return style;
}
}
return SUBSTITUTION;
}
@Override
@NotNull
public String toString() {
return this.name;
}
}

View File

@@ -0,0 +1,29 @@
package net.knarcraft.bookswithoutborders.encryption;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* An interface describing a style of encryption
*/
public interface Encryptor {
/**
* Encrypts the given plaintext
*
* @param input <p>The input to encrypt</p>
* @return <p>The resulting cypher text, or null if unsuccessful</p>
*/
@Nullable
String encryptText(@NotNull String input);
/**
* Decrypts the given cypher text
*
* @param input <p>The cypher text to decrypt</p>
* @return <p>The resulting plaintext, or null if unsuccessful</p>
*/
@Nullable
String decryptText(@NotNull String input);
}

View File

@@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.encryption; package net.knarcraft.bookswithoutborders.encryption;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -12,11 +13,11 @@ import java.util.Random;
* <p>Not sure where this was gotten from, but it does exist at * <p>Not sure where this was gotten from, but it does exist at
* <a href="https://crypto.stackexchange.com/questions/11614/how-do-i-test-my-encryption-absolute-amateur">Stack Exchange</a>.</p> * <a href="https://crypto.stackexchange.com/questions/11614/how-do-i-test-my-encryption-absolute-amateur">Stack Exchange</a>.</p>
*/ */
public class GenenCrypt { public class GenenCrypt implements Encryptor {
private final Random ranGen; private final Random ranGen;
private final String[] bases; private final String[] bases;
private final String[] charList; private final String[] availableCharacters;
private final HashMap<String, String[]> codonTable; private final HashMap<String, String[]> codonTable;
private final HashMap<String, String> decryptTable; private final HashMap<String, String> decryptTable;
private final String key; private final String key;
@@ -62,32 +63,40 @@ public class GenenCrypt {
// 10 digits // 10 digits
// space, newline, and tab // space, newline, and tab
// the symbols . , ? " ! @ # $ % ^ & * ( ) - + = / _ \ : ; < > // the symbols . , ? " ! @ # $ % ^ & * ( ) - + = / _ \ : ; < >
charList = new String[]{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", availableCharacters = 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", " ", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", " ",
"\t", "\n", ".", ",", "?", "\"", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "/", "\t", "\n", ".", ",", "?", "\"", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "-", "+", "=", "/",
"_", "\\", ":", ";", "<", ">", "|"}; "_", "\\", ":", ";", "<", ">", "|"};
// define the codon table to encode text // define the codon table to encode text
codonTable = new HashMap<>(); codonTable = new HashMap<>();
for (int i = 0; i < charList.length; i++) { for (int i = 0; i < availableCharacters.length; i++) {
String[] tempArray = new String[]{shuffledCodonList.get(4 * i), shuffledCodonList.get(4 * i + 1), String[] tempArray = new String[]{shuffledCodonList.get(4 * i), shuffledCodonList.get(4 * i + 1),
shuffledCodonList.get(4 * i + 2), shuffledCodonList.get(4 * i + 3)}; shuffledCodonList.get(4 * i + 2), shuffledCodonList.get(4 * i + 3)};
//System.out.println(i); //System.out.println(i);
codonTable.put(charList[i], tempArray); codonTable.put(availableCharacters[i], tempArray);
} }
// define the decryption table // define the decryption table
decryptTable = new HashMap<>(); decryptTable = new HashMap<>();
for (int i = 0; i < codonTable.size(); i++) { for (int i = 0; i < codonTable.size(); i++) {
String s = charList[i]; String s = availableCharacters[i];
String[] sa = codonTable.get(s); String[] sa = codonTable.get(s);
decryptTable.put(sa[0], s); decryptTable.put(sa[0], s);
decryptTable.put(sa[1], s); decryptTable.put(sa[1], s);
decryptTable.put(sa[2], s); decryptTable.put(sa[2], s);
decryptTable.put(sa[3], s); decryptTable.put(sa[3], s);
} }
}
@Override
public @Nullable String encryptText(@NotNull String input) {
return encrypt(input);
}
@Override
public @Nullable String decryptText(@NotNull String input) {
return decrypt(input);
} }
/** /**
@@ -96,7 +105,7 @@ public class GenenCrypt {
public void printCodonTable() { public void printCodonTable() {
// print the codon table // print the codon table
for (int i = 0; i < codonTable.size(); i++) { for (int i = 0; i < codonTable.size(); i++) {
String s = charList[i]; String s = availableCharacters[i];
String[] sa = codonTable.get(s); String[] sa = codonTable.get(s);
switch (s) { switch (s) {
case "\t" -> case "\t" ->
@@ -117,7 +126,7 @@ public class GenenCrypt {
* @return <p>The encrypted input</p> * @return <p>The encrypted input</p>
*/ */
@NotNull @NotNull
public String encrypt(@NotNull String input) { private String encrypt(@NotNull String input) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
for (int i = 0; i < input.length(); i++) { for (int i = 0; i < input.length(); i++) {
// insert junk bases // insert junk bases
@@ -151,7 +160,7 @@ public class GenenCrypt {
* @return <p>The decrypted input</p> * @return <p>The decrypted input</p>
*/ */
@NotNull @NotNull
public String decrypt(@NotNull String input) { private String decrypt(@NotNull String input) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
int keyCount = 0; int keyCount = 0;
int junk = key.charAt(0); int junk = key.charAt(0);

View File

@@ -0,0 +1,22 @@
package net.knarcraft.bookswithoutborders.encryption;
import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* So-called "Magic" encryption which simply makes the contents unreadable
*/
public class Magic implements Encryptor {
@Override
public @Nullable String encryptText(@NotNull String input) {
return "§k" + BookFormatter.stripColor(input.replace("§", ""));
}
@Override
public @Nullable String decryptText(@NotNull String input) {
return null;
}
}

View File

@@ -0,0 +1,64 @@
package net.knarcraft.bookswithoutborders.encryption;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* A one-time pad implementation
*/
public class OneTimePad implements Encryptor {
private final @NotNull String key;
/**
* Instantiates a new one-time pad
*
* @param key <p>The key to use</p>
*/
public OneTimePad(@NotNull String key) {
this.key = key;
}
@Override
public @Nullable String encryptText(@NotNull String input) {
return oneTimePad(input);
}
@Override
public @Nullable String decryptText(@NotNull String input) {
return oneTimePad(input);
}
/**
* Encrypts/decrypts the input using a one-time pad
*
* <p>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.</p>
*
* @param input <p>The input to encrypt/decrypt</p>
* @return <p>The encrypted/decrypted output</p>
*/
@NotNull
public String oneTimePad(@NotNull String input) {
String longKey;
try {
final MessageDigest digest = MessageDigest.getInstance("SHA3-256");
final byte[] hashBytes = digest.digest(key.getBytes(StandardCharsets.UTF_8));
longKey = Base64.getEncoder().encodeToString(hashBytes);
} catch (NoSuchAlgorithmException exception) {
longKey = key;
}
StringBuilder output = new StringBuilder();
for (int i = 0; i < input.length(); i++) {
output.append((char) (input.charAt(i) ^ longKey.charAt(i % longKey.length())));
}
return output.toString();
}
}

View File

@@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.encryption; package net.knarcraft.bookswithoutborders.encryption;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.StringTokenizer; import java.util.StringTokenizer;
@@ -8,90 +9,89 @@ import java.util.StringTokenizer;
/** /**
* A simple substitution cipher * A simple substitution cipher
*/ */
public class SubstitutionCipher { public class SubstitutionCipher implements Encryptor {
public SubstitutionCipher() { private final @NotNull String key;
/**
* Instantiates a new substitution cipher
*
* @param key <p>The key to use</p>
*/
public SubstitutionCipher(@NotNull String key) {
this.key = key;
} }
// encrypts a string using a substitution cipher. @Override
// the substitution is made harder to crack by public @Nullable String encryptText(@NotNull String input) {
// using a string for the key, it is converted return encryptDecrypt(input, true);
// a series of offsets that each character in the }
// original message is offset by
@Override
public @Nullable String decryptText(@NotNull String input) {
return encryptDecrypt(input, false);
}
/**
* Encrypts or decrypts a string using a substitution cipher
*
* <p>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.</p>
*
* @param input <p>The input to encrypt/decrypt</p>
* @param encrypt <p>Whether to encrypt or decrypt the input</p>
* @return <p>The encryption output</p>
*/
@NotNull @NotNull
public String encrypt(@NotNull String in, @NotNull String key) { private String encryptDecrypt(@NotNull String input, boolean encrypt) {
StringBuilder output = new StringBuilder(); StringBuilder output = new StringBuilder();
if (!key.isEmpty()) { if (this.key.isBlank()) {
return output.toString();
}
// converts each number in the key to an integer and adds to an array
int[] offsetArray = getOffsetArray(this.key);
int offsetPosition = 0;
for (int i = 0; i < input.length(); i++) {
// encrypts the letter and adds to the output string
if (encrypt) {
output.append((char) (input.charAt(i) + offsetArray[offsetPosition]));
} else {
output.append((char) (input.charAt(i) - offsetArray[offsetPosition]));
}
// 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();
}
/**
* Tokenizes a key and generates an offset array for substitution
*
* @param key <p>The key to make an offset array for</p>
* @return <p>The offset array</p>
*/
private int[] getOffsetArray(@NotNull String key) {
StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key StringTokenizer tokenizer = new StringTokenizer(key, ", "); // tokenizes the key
// converts each number in the key to an integer and adds to an array // converts each number in the key to an integer and adds to an array
int[] offsetArray = new int[tokenizer.countTokens()]; int[] offsetArray = new int[tokenizer.countTokens()];
for (int i = 0; i < offsetArray.length; i++) { for (int i = 0; i < offsetArray.length; i++) {
String nt = tokenizer.nextToken(); String nextToken = tokenizer.nextToken();
try { try {
offsetArray[i] = Integer.parseInt(nt); offsetArray[i] = Integer.parseInt(nextToken);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
BigInteger big = new BigInteger(nt); BigInteger big = new BigInteger(nextToken);
offsetArray[i] = Math.abs(big.intValue()); offsetArray[i] = Math.abs(big.intValue());
} }
} }
return offsetArray;
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.
@SuppressWarnings("unused")
@NotNull
public String decrypt(@NotNull String in, @NotNull String key) {
StringBuilder output = new StringBuilder();
if (!key.isEmpty()) {
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.
@SuppressWarnings("unused")
@NotNull
public String oneTimePad(@NotNull String in, @NotNull 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();
} }
} }

View File

@@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.gui; package net.knarcraft.bookswithoutborders.gui;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFormatter; import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
@@ -27,7 +28,8 @@ public class AuthorBookIndex extends BookIndex {
public static void printBooks(@NotNull CommandSender sender, boolean listPublic, @NotNull String command, int page, public static void printBooks(@NotNull CommandSender sender, boolean listPublic, @NotNull String command, int page,
@NotNull String authorName) { @NotNull String authorName) {
List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, listPublic); List<String> availableBooks = BooksWithoutBorders.getAvailableBooks(sender, listPublic);
availableBooks.removeIf((bookPath) -> !BookFormatter.stripColor(bookPath.substring(0, bookPath.length() - 4).split(",")[1]).equalsIgnoreCase(authorName)); availableBooks.removeIf((bookPath) ->
!BookFormatter.stripColor(BookFileHelper.getBookAuthorFromPath(bookPath)).equalsIgnoreCase(authorName));
int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage); int totalPages = (int) Math.ceil((double) availableBooks.size() / booksPerPage);
if (page > totalPages) { if (page > totalPages) {
@@ -50,17 +52,23 @@ public class AuthorBookIndex extends BookIndex {
private static void showAuthorBooks(@NotNull CommandSender sender, @NotNull String command, int page, private static void showAuthorBooks(@NotNull CommandSender sender, @NotNull String command, int page,
int totalPages, @NotNull List<String> availableBooks, @NotNull String authorName) { int totalPages, @NotNull List<String> availableBooks, @NotNull String authorName) {
ComponentBuilder componentBuilder = new ComponentBuilder(); ComponentBuilder componentBuilder = new ComponentBuilder();
String navigationCommand = command + " author" + authorName;
componentBuilder.append("--- ");
if (command.toLowerCase().contains("public")) {
componentBuilder.append("Publicly saved books by: ").color(ChatColor.GREEN).append(authorName).color(ChatColor.AQUA);
} else {
componentBuilder.append("Your saved books by: ").color(ChatColor.GREEN).append(authorName).color(ChatColor.AQUA);
}
componentBuilder.append(" ---", ComponentBuilder.FormatRetention.NONE).append("\n");
// Display the list of books, with the next and previous buttons
displayPreviousButton(componentBuilder, command + " author" + authorName, page);
componentBuilder.append("\n");
displayBookList(componentBuilder, command, page, availableBooks); displayBookList(componentBuilder, command, page, availableBooks);
displayNextButton(componentBuilder, command + " author" + authorName, page, totalPages); displayPreviousButton(componentBuilder, navigationCommand, page);
componentBuilder.append(" | ", ComponentBuilder.FormatRetention.NONE);
// Display total pages and the manual change page command suggestion displayTotalPages(componentBuilder, navigationCommand, page, totalPages);
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE); componentBuilder.append(" | ", ComponentBuilder.FormatRetention.NONE);
displayTotalPages(componentBuilder, page, totalPages); displayNextButton(componentBuilder, navigationCommand, page, totalPages);
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE);
sender.spigot().sendMessage(componentBuilder.create()); sender.spigot().sendMessage(componentBuilder.create());
} }

View File

@@ -1,6 +1,6 @@
package net.knarcraft.bookswithoutborders.gui; package net.knarcraft.bookswithoutborders.gui;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
@@ -48,32 +48,43 @@ public abstract class BookIndex {
return true; return true;
} }
} }
return false;
// Parse book author from input
for (int authorIndex = 0; authorIndex < arguments.length; authorIndex++) {
String author = InputCleaningHelper.parseAuthorSpecifier(arguments[authorIndex]);
if (author == null) {
continue;
}
for (int pageIndex = authorIndex + 1; pageIndex < arguments.length; pageIndex++) {
int pageNumber = InputCleaningHelper.parsePageNumber(arguments[pageIndex]);
if (pageNumber <= 0) {
continue;
}
StringBuilder builder = new StringBuilder(author);
for (int authorPartCounter = authorIndex + 1; authorPartCounter < pageIndex; authorPartCounter++) {
builder.append(" ").append(arguments[authorPartCounter]);
}
AuthorBookIndex.printBooks(sender, selectPublic, command, pageNumber, builder.toString());
return true;
}
} }
/** return false;
* Displays the suggestion for manually going to any page
*
* @param componentBuilder <p>The component builder to append to</p>
* @param command <p>The command used for switching pages</p>
* @param page <p>The current page</p>
*/
protected static void displayPageCommand(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page) {
componentBuilder.append("[Page Command]", ComponentBuilder.FormatRetention.NONE).color(interactColor).event(new HoverEvent(
HoverEvent.Action.SHOW_TEXT, new Text("/" + command + " page" + page))).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " page" + page));
} }
/** /**
* Displays the current page and total amount of pages * Displays the current page and total amount of pages
* *
* @param componentBuilder <p>The component builder to append to</p> * @param componentBuilder <p>The component builder to append to</p>
* @param command <p>The command used for switching pages</p>
* @param page <p>The current page</p> * @param page <p>The current page</p>
* @param totalPages <p>The total amount of pages</p> * @param totalPages <p>The total amount of pages</p>
*/ */
protected static void displayTotalPages(@NotNull ComponentBuilder componentBuilder, int page, int totalPages) { protected static void displayTotalPages(@NotNull ComponentBuilder componentBuilder, @NotNull String command, int page, int totalPages) {
componentBuilder.append("Page " + page + " of " + totalPages, componentBuilder.append("Page " + page + " of " + totalPages,
ComponentBuilder.FormatRetention.NONE).color(inactiveColor); ComponentBuilder.FormatRetention.NONE).color(interactColor).event(new HoverEvent(
HoverEvent.Action.SHOW_TEXT, new Text("/" + command + " page" + page))).event(
new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/" + command + " page" + page));
} }
/** /**
@@ -114,10 +125,10 @@ public abstract class BookIndex {
String fullCommand = "/" + command + " page" + (page - 1); String fullCommand = "/" + command + " page" + (page - 1);
HoverEvent prevPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page - 1))); HoverEvent prevPagePreview = new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("To page " + (page - 1)));
ClickEvent prevPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand); ClickEvent prevPageClick = new ClickEvent(ClickEvent.Action.RUN_COMMAND, fullCommand);
componentBuilder.append("Previous [<]", ComponentBuilder.FormatRetention.NONE).color(interactColor) componentBuilder.append("[<] Previous", ComponentBuilder.FormatRetention.NONE).color(interactColor)
.event(prevPagePreview).event(prevPageClick); .event(prevPagePreview).event(prevPageClick);
} else { } else {
componentBuilder.append("Previous [<]", ComponentBuilder.FormatRetention.NONE).color(inactiveColor); componentBuilder.append("[<] Previous", ComponentBuilder.FormatRetention.NONE).color(inactiveColor);
} }
} }
@@ -150,14 +161,10 @@ public abstract class BookIndex {
*/ */
@NotNull @NotNull
protected static String getNiceName(@NotNull String bookPath) { protected static String getNiceName(@NotNull String bookPath) {
String separator = BooksWithoutBordersConfig.getTitleAuthorSeparator(); String title = BookFileHelper.getBookTitleFromPath(bookPath);
bookPath = ChatColor.translateAlternateColorCodes('&', bookPath.substring(0, bookPath.length() - 4)); String author = BookFileHelper.getBookAuthorFromPath(bookPath);
if (bookPath.contains(separator)) { return ChatColor.translateAlternateColorCodes('&',
String[] parts = bookPath.split(separator); title + ChatColor.RESET + " by " + author + ChatColor.RESET);
return parts[0].replace("_", " ") + ChatColor.RESET + " by " + parts[1] + ChatColor.RESET;
} else {
return bookPath + ChatColor.RESET + " by Unknown" + ChatColor.RESET;
}
} }
} }

View File

@@ -63,20 +63,21 @@ public class PagedBookIndex extends BookIndex {
@NotNull Map<Character, Integer> firstInstances) { @NotNull Map<Character, Integer> firstInstances) {
ComponentBuilder componentBuilder = new ComponentBuilder(); ComponentBuilder componentBuilder = new ComponentBuilder();
// Display the list of books, with the next and previous buttons componentBuilder.append("--- ");
displayPreviousButton(componentBuilder, command, page); if (command.toLowerCase().contains("public")) {
componentBuilder.append("\n"); componentBuilder.append("Publicly saved books").color(ChatColor.GREEN);
} else {
componentBuilder.append("Your saved books").color(ChatColor.GREEN);
}
componentBuilder.append(" ---", ComponentBuilder.FormatRetention.NONE).append("\n");
displayBookList(componentBuilder, command, page, availableBooks); displayBookList(componentBuilder, command, page, availableBooks);
displayPreviousButton(componentBuilder, command, page);
componentBuilder.append(" | ", ComponentBuilder.FormatRetention.NONE);
displayTotalPages(componentBuilder, command, page, totalPages);
componentBuilder.append(" | ", ComponentBuilder.FormatRetention.NONE);
displayNextButton(componentBuilder, command, page, totalPages); displayNextButton(componentBuilder, command, page, totalPages);
// Display total pages and the manual change page command suggestion
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE);
displayTotalPages(componentBuilder, page, totalPages);
componentBuilder.append(" ", ComponentBuilder.FormatRetention.NONE);
displayPageCommand(componentBuilder, command, page);
componentBuilder.append("\n"); componentBuilder.append("\n");
// Display the alphabet index as the header
displayAlphabetIndex(componentBuilder, command, firstInstances); displayAlphabetIndex(componentBuilder, command, firstInstances);
sender.spigot().sendMessage(componentBuilder.create()); sender.spigot().sendMessage(componentBuilder.create());
} }

View File

@@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -72,7 +71,7 @@ public class BookshelfHandler {
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(bookshelfFile); YamlConfiguration configuration = YamlConfiguration.loadConfiguration(bookshelfFile);
ConfigurationSection bookshelfSection = configuration.getConfigurationSection("bookshelves"); ConfigurationSection bookshelfSection = configuration.getConfigurationSection("bookshelves");
if (bookshelfSection == null) { if (bookshelfSection == null) {
BooksWithoutBorders.getInstance().getLogger().log(Level.INFO, BooksWithoutBorders.log(Level.INFO,
"BooksWithoutBorders found no bookshelves to load"); "BooksWithoutBorders found no bookshelves to load");
return; return;
} }
@@ -89,19 +88,10 @@ public class BookshelfHandler {
String loreKey = key + ".lore"; String loreKey = key + ".lore";
String title = bookshelfSection.getString(titleKey, null); String title = bookshelfSection.getString(titleKey, null);
List<String> loreStrings = new ArrayList<>(); List<String> lore = bookshelfSection.getStringList(loreKey);
List<?> lore = bookshelfSection.getList(loreKey);
if (lore == null) {
throw new IllegalArgumentException("Lore is missing from bookshelf data!");
}
lore.forEach((item) -> {
if (item instanceof String) {
loreStrings.add((String) item);
}
});
if (title != null) { if (title != null) {
registerBookshelf(new Bookshelf(bookshelfLocation, title, loreStrings)); registerBookshelf(new Bookshelf(bookshelfLocation, title, lore));
} }
} }
} }
@@ -118,7 +108,7 @@ public class BookshelfHandler {
} }
configuration.save(bookshelfFile); configuration.save(bookshelfFile);
} catch (IOException exception) { } catch (IOException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save bookshelves!"); BooksWithoutBorders.log(Level.SEVERE, "Unable to save bookshelves!");
} }
} }

View File

@@ -1,6 +1,7 @@
package net.knarcraft.bookswithoutborders.listener; package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.Permission;
import net.knarcraft.bookswithoutborders.utility.BookFormatter; import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -16,7 +17,8 @@ public class BookEventListener implements Listener {
@EventHandler @EventHandler
public void onBookSign(@NotNull PlayerEditBookEvent event) { public void onBookSign(@NotNull PlayerEditBookEvent event) {
if (event.isCancelled() || !event.isSigning() || !BooksWithoutBordersConfig.formatBooks()) { if (event.isCancelled() || !event.isSigning() || !BooksWithoutBorders.getConfiguration().formatBooks() ||
!event.getPlayer().hasPermission(Permission.FORMAT.toString())) {
return; return;
} }
event.setNewBookMeta(BookFormatter.formatPages(event.getNewBookMeta())); event.setNewBookMeta(BookFormatter.formatPages(event.getNewBookMeta()));

View File

@@ -1,7 +1,7 @@
package net.knarcraft.bookswithoutborders.listener; package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.container.Bookshelf; import net.knarcraft.bookswithoutborders.container.Bookshelf;
import net.knarcraft.bookswithoutborders.handler.BookshelfHandler; import net.knarcraft.bookswithoutborders.handler.BookshelfHandler;
import net.knarcraft.bookswithoutborders.utility.IntegerToRomanConverter; import net.knarcraft.bookswithoutborders.utility.IntegerToRomanConverter;
@@ -63,7 +63,7 @@ public class BookshelfListener implements Listener {
} }
// Check if bookshelf peeking is enabled, and the player can peek // Check if bookshelf peeking is enabled, and the player can peek
if (!BooksWithoutBordersConfig.getEnableBookshelfPeeking() || if (!BooksWithoutBorders.getConfiguration().getEnableBookshelfPeeking() ||
!event.getPlayer().hasPermission("bookswithoutborders.peekbookshelf")) { !event.getPlayer().hasPermission("bookswithoutborders.peekbookshelf")) {
return; return;
} }
@@ -155,7 +155,7 @@ public class BookshelfListener implements Listener {
title = bookMeta.getTitle(); title = bookMeta.getTitle();
} }
if (!bookMeta.hasAuthor() || bookMeta.getAuthor() == null) { if (!bookMeta.hasAuthor() || bookMeta.getAuthor() == null) {
author = "Unknown"; author = BooksWithoutBorders.getStringFormatter().getUnFormattedColoredMessage(Translatable.NEUTRAL_UNKNOWN_AUTHOR);
} else { } else {
author = bookMeta.getAuthor(); author = bookMeta.getAuthor();
} }

View File

@@ -2,23 +2,14 @@ package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper; import net.knarcraft.bookswithoutborders.utility.InputCleaningHelper;
import net.knarcraft.bookswithoutborders.utility.InventoryHelper;
import org.bukkit.Material;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerItemHeldEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.logging.Level; import java.util.logging.Level;
@@ -30,41 +21,17 @@ public class PlayerEventListener implements Listener {
private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance(); private final BooksWithoutBorders booksWithoutBorders = BooksWithoutBorders.getInstance();
@EventHandler
public void onHold(@NotNull PlayerItemHeldEvent event) {
if (event.isCancelled()) {
return;
}
Player player = event.getPlayer();
int selectedSlot = event.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 @EventHandler
public void onPlayerJoin(@NotNull PlayerJoinEvent event) { public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
//If a book directory exists with this player's name, move it to this player's UUID //If a book directory exists with this player's name, move it to this player's UUID
String bookFolder = BooksWithoutBordersConfig.getBookFolder(); String bookFolder = config.getBookFolder();
File file = new File(bookFolder + InputCleaningHelper.cleanString(player.getName())); File file = new File(bookFolder + InputCleaningHelper.cleanString(player.getName()));
if (file.exists()) { if (file.exists()) {
if (!file.renameTo(new File(bookFolder + player.getUniqueId()))) { if (!file.renameTo(new File(bookFolder + player.getUniqueId()))) {
BooksWithoutBorders.getInstance().getLogger().log(Level.WARNING, "Unable to migrate player book " + BooksWithoutBorders.log(Level.WARNING, "Unable to migrate player book " +
"directory for player " + player.getName()); "directory for player " + player.getName());
} }
} }
@@ -74,26 +41,10 @@ public class PlayerEventListener implements Listener {
boolean sendMessage = true; boolean sendMessage = true;
//Gives new players necessary books //Gives new players necessary books
for (String bookName : BooksWithoutBordersConfig.getFirstBooks()) { for (String bookName : config.getFirstBooks()) {
sendMessage = giveBookToNewPlayer(bookName, player, sendMessage); sendMessage = giveBookToNewPlayer(bookName, player, sendMessage);
} }
} }
//Updates any books in either hand
ItemStack mainHandItem = InventoryHelper.getHeldItem(player, true);
ItemStack offHandItem = InventoryHelper.getHeldItem(player, false);
if (mainHandItem.getType() == Material.WRITTEN_BOOK) {
ItemMeta itemMetadata = mainHandItem.getItemMeta();
if (itemMetadata != null) {
updateBookInHand(player, itemMetadata, true);
}
}
if (offHandItem.getType() == Material.WRITTEN_BOOK) {
ItemMeta itemMetadata = offHandItem.getItemMeta();
if (itemMetadata != null) {
updateBookInHand(player, itemMetadata, false);
}
}
} }
/** /**
@@ -114,7 +65,7 @@ public class PlayerEventListener implements Listener {
} }
//Send the player a welcome message if it exists //Send the player a welcome message if it exists
String welcomeMessage = BooksWithoutBordersConfig.getWelcomeMessage(); String welcomeMessage = BooksWithoutBorders.getConfiguration().getWelcomeMessage();
if (!welcomeMessage.trim().isEmpty() && newBook != null && sendMessage) { if (!welcomeMessage.trim().isEmpty() && newBook != null && sendMessage) {
sendMessage = false; sendMessage = false;
booksWithoutBorders.getServer().getScheduler().scheduleSyncDelayedTask(booksWithoutBorders, booksWithoutBorders.getServer().getScheduler().scheduleSyncDelayedTask(booksWithoutBorders,
@@ -124,69 +75,4 @@ public class PlayerEventListener implements Listener {
return sendMessage; return sendMessage;
} }
/**
* Updates a book in one of the player's hands
*
* @param player <p>The player to update</p>
* @param itemMetadata <p>Information about the held book</p>
* @param mainHand <p>Whether to update the book in the player's main hand</p>
*/
private void updateBookInHand(@NotNull Player player, @NotNull 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);
}
}
}
/**
* Updates old books to a newer format
*
* @param player <p>The player holding the book</p>
* @param oldBook <p>Metadata about the held book</p>
* @return <p>An updated book</p>
*/
@Nullable
public ItemStack updateBook(@NotNull Player player, @NotNull 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() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + oldBook.getAuthor();
}
String playerFolderPath = BookHelper.getBookDirectoryPathString(BookDirectory.PLAYER, player);
String publicFolderPath = BookHelper.getBookDirectoryPathString(BookDirectory.PUBLIC, player);
String[] possiblePaths = new String[]{
publicFolderPath + fileName + ".yml",
publicFolderPath + fileName + ".txt",
playerFolderPath + fileName + ".yml",
playerFolderPath + fileName + ".txt"
};
for (String path : possiblePaths) {
File file = new File(path);
if (file.isFile()) {
return BookLoader.loadBook(player, fileName, "true", "player");
}
}
return null;
}
return null;
}
} }

View File

@@ -2,7 +2,9 @@ package net.knarcraft.bookswithoutborders.listener;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle; import net.knarcraft.bookswithoutborders.config.Permission;
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
import net.knarcraft.bookswithoutborders.state.BookDirectory;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper; import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookFormatter; import net.knarcraft.bookswithoutborders.utility.BookFormatter;
import net.knarcraft.bookswithoutborders.utility.BookLoader; import net.knarcraft.bookswithoutborders.utility.BookLoader;
@@ -30,8 +32,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.BookFileHelper.isBookListIndex; import static net.knarcraft.bookswithoutborders.utility.BookFileHelper.isBookListIndex;
/** /**
@@ -39,8 +39,6 @@ import static net.knarcraft.bookswithoutborders.utility.BookFileHelper.isBookLis
*/ */
public class SignEventListener implements Listener { public class SignEventListener implements Listener {
private final String slash = getSlash();
@EventHandler @EventHandler
public void onSignChange(@NotNull SignChangeEvent event) { public void onSignChange(@NotNull SignChangeEvent event) {
if (event.isCancelled()) { if (event.isCancelled()) {
@@ -59,10 +57,11 @@ public class SignEventListener implements Listener {
event.setLine(0, ChatColor.DARK_GREEN + "[BwB]"); event.setLine(0, ChatColor.DARK_GREEN + "[BwB]");
//Check if the sign is of a valid type //Check if the sign is of a valid type
if (!((lines[1].equalsIgnoreCase("[Encrypt]") || lines[1].equalsIgnoreCase("[Decrypt]") || if ((!lines[1].equalsIgnoreCase("[Encrypt]") && !lines[1].equalsIgnoreCase("[Decrypt]") &&
lines[1].equalsIgnoreCase("[Give]")) && !lines[2].trim().isEmpty())) { !lines[1].equalsIgnoreCase("[Give]")) || lines[2].trim().isEmpty()) {
//Mark the second line as invalid //Mark the second line as invalid
event.setLine(1, ChatColor.DARK_RED + lines[1]); event.setLine(1, ChatColor.DARK_RED + lines[1]);
player.sendMessage("Invalid sign!");
return; return;
} }
@@ -83,11 +82,6 @@ public class SignEventListener implements Listener {
@EventHandler @EventHandler
public void onClick(@NotNull PlayerInteractEvent event) { public void onClick(@NotNull PlayerInteractEvent event) {
if (event.getClickedBlock() == null) {
return;
}
Material clickedBlockType = event.getClickedBlock().getType();
Player player = event.getPlayer(); Player player = event.getPlayer();
PlayerInventory playerInventory = player.getInventory(); PlayerInventory playerInventory = player.getInventory();
EquipmentSlot hand = event.getHand(); EquipmentSlot hand = event.getHand();
@@ -100,15 +94,18 @@ public class SignEventListener implements Listener {
} }
Material heldItemType = heldItem.getType(); Material heldItemType = heldItem.getType();
if (event.getAction() == Action.RIGHT_CLICK_BLOCK && (Tag.SIGNS.isTagged(clickedBlockType) || if (event.getClickedBlock() != null && (event.getAction() == Action.RIGHT_CLICK_BLOCK &&
Tag.WALL_SIGNS.isTagged(clickedBlockType))) { (Tag.SIGNS.isTagged(event.getClickedBlock().getType()) ||
event.setUseItemInHand(Event.Result.DENY); Tag.WALL_SIGNS.isTagged(event.getClickedBlock().getType())))) {
//The player right-clicked a sign //The player right-clicked a sign
Sign sign = (Sign) event.getClickedBlock().getState(); Sign sign = (Sign) event.getClickedBlock().getState();
if (!signLineEquals(sign, 0, "[BwB]", ChatColor.DARK_GREEN)) { if (!signLineEquals(sign, 0, "[BwB]", ChatColor.DARK_GREEN)) {
return; return;
} }
event.setUseItemInHand(Event.Result.DENY);
event.setCancelled(true);
if (signLineEquals(sign, 1, "[Encrypt]", ChatColor.DARK_BLUE)) { if (signLineEquals(sign, 1, "[Encrypt]", ChatColor.DARK_BLUE)) {
encryptHeldBookUsingSign(sign, heldItemType, player, hand); encryptHeldBookUsingSign(sign, heldItemType, player, hand);
} else if (signLineEquals(sign, 1, "[Decrypt]", ChatColor.DARK_BLUE)) { } else if (signLineEquals(sign, 1, "[Decrypt]", ChatColor.DARK_BLUE)) {
@@ -124,6 +121,7 @@ public class SignEventListener implements Listener {
} }
} else if (heldItemType == Material.WRITTEN_BOOK && (event.getAction() == Action.LEFT_CLICK_AIR } else if (heldItemType == Material.WRITTEN_BOOK && (event.getAction() == Action.LEFT_CLICK_AIR
|| event.getAction() == Action.LEFT_CLICK_BLOCK)) { || event.getAction() == Action.LEFT_CLICK_BLOCK)) {
BookMeta oldBook = (BookMeta) heldItem.getItemMeta(); BookMeta oldBook = (BookMeta) heldItem.getItemMeta();
if (oldBook == null) { if (oldBook == null) {
return; return;
@@ -151,7 +149,11 @@ public class SignEventListener implements Listener {
String lineText = BookFormatter.stripColor(sign.getSide(Side.FRONT).getLine(2)); String lineText = BookFormatter.stripColor(sign.getSide(Side.FRONT).getLine(2));
String key = EncryptionHelper.getNumberKeyFromStringKey(lineText); String key = EncryptionHelper.getNumberKeyFromStringKey(lineText);
ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false); ItemStack book = EncryptionHelper.loadEncryptedBook(player, key, false, false);
if (book == null) {
book = EncryptionHelper.loadEncryptedBookLegacy(player, key, false);
}
if (book != null) { if (book != null) {
player.getInventory().setItem(hand, book); player.getInventory().setItem(hand, book);
player.sendMessage(ChatColor.GREEN + "Book decrypted!"); player.sendMessage(ChatColor.GREEN + "Book decrypted!");
@@ -207,7 +209,7 @@ public class SignEventListener implements Listener {
} }
//Tests if a full file name has been supplied and points to an actual file //Tests if a full file name has been supplied and points to an actual file
String signFile = getBookFolder() + lines[2] + lines[3]; String signFile = BooksWithoutBorders.getConfiguration().getBookFolder() + lines[2] + lines[3];
if (BookFileHelper.bookFileExists(signFile)) { if (BookFileHelper.bookFileExists(signFile)) {
markGiveSignValidity(event, true); markGiveSignValidity(event, true);
return; return;
@@ -248,6 +250,8 @@ public class SignEventListener implements Listener {
*/ */
private void decryptBook(@NotNull BookMeta oldBook, @NotNull Player player, @NotNull ItemStack heldItem, private void decryptBook(@NotNull BookMeta oldBook, @NotNull Player player, @NotNull ItemStack heldItem,
@NotNull EquipmentSlot hand) { @NotNull EquipmentSlot hand) {
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
ItemStack newBook; ItemStack newBook;
//Check if the book is encrypted by Books Without Borders //Check if the book is encrypted by Books Without Borders
@@ -258,25 +262,32 @@ public class SignEventListener implements Listener {
String groupName = oldBook.getLore().get(0).substring(3).split(" encrypted")[0]; String groupName = oldBook.getLore().get(0).substring(3).split(" encrypted")[0];
//Permission check //Permission check
if (!player.hasPermission("bookswithoutborders.decrypt." + groupName) && if (!player.hasPermission(Permission.DECRYPT + "." + groupName) &&
!(BooksWithoutBordersConfig.getAdminDecrypt() && player.hasPermission("bookswithoutborders.admin"))) { !(config.getAdminDecrypt() && player.hasPermission(Permission.ADMIN.toString()))) {
BooksWithoutBorders.sendErrorMessage(player, "You are not allowed to decrypt that book");
return; return;
} }
String fileName = oldBook.getTitle() + BooksWithoutBordersConfig.getTitleAuthorSeparator() + oldBook.getAuthor(); String encryptedFolder = BooksWithoutBorders.getConfiguration().getEncryptedBookPath();
String fileName = oldBook.getTitle() + config.getTitleAuthorSeparator() + oldBook.getAuthor();
String encryptionFile = InputCleaningHelper.cleanString(groupName) + slash + fileName + ".yml"; File file = BookFileHelper.findBookFile(encryptedFolder + InputCleaningHelper.cleanString(groupName) +
config.getSlash(), oldBook);
File file = new File(getBookFolder() + "Encrypted" + slash + encryptionFile); if (file == null) {
if (!file.isFile()) { file = BookFileHelper.findBookFile(encryptedFolder, oldBook);
file = new File(getBookFolder() + fileName + ".txt"); if (file == null) {
if (!file.isFile()) { file = BookFileHelper.findBookFile(config.getBookFolder(), oldBook);
if (file == null) {
BooksWithoutBorders.sendErrorMessage(player, "Unable to find encrypted book");
return; return;
} }
} }
newBook = BookLoader.loadBook(player, fileName, "true", groupName, heldItem.getAmount()); }
newBook = BookLoader.loadBook(player, fileName, "true", BookDirectory.ENCRYPTED, groupName, heldItem.getAmount());
if (newBook == null) { if (newBook == null) {
BooksWithoutBorders.sendErrorMessage(player, "Unable to load the unencrypted book!");
return; return;
} }

View File

@@ -1,7 +1,8 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.manager;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.Economy;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.Server; import org.bukkit.Server;
@@ -13,19 +14,35 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicesManager; import org.bukkit.plugin.ServicesManager;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /**
* Helper class for economy related functions * A manager for managing Vault economy integration
*/ */
public final class EconomyHelper { public class EconomyManager {
private static Economy economy; private @Nullable Economy economy = null;
private EconomyHelper() { /**
* Instantiates the economy manager
*/
public EconomyManager() {
Server server = BooksWithoutBorders.getInstance().getServer();
Plugin plugin = server.getPluginManager().getPlugin("Vault");
ServicesManager servicesManager = server.getServicesManager();
if (plugin == null) {
return;
}
RegisteredServiceProvider<Economy> economyProvider = servicesManager.getRegistration(Economy.class);
if (economyProvider != null) {
this.economy = economyProvider.getProvider();
}
} }
/** /**
@@ -33,35 +50,11 @@ public final class EconomyHelper {
* *
* @return <p>An economy instance, or null if it's not initialized</p> * @return <p>An economy instance, or null if it's not initialized</p>
*/ */
@NotNull @Nullable
public static Economy getEconomy() { public Economy getEconomy() {
return economy; return economy;
} }
/**
* Tries to set up economy
*
* @return <p>True if economy is set up and enabled</p>
*/
public static boolean setupEconomy() {
if (economy != null) {
return true;
}
Server server = BooksWithoutBorders.getInstance().getServer();
Plugin plugin = server.getPluginManager().getPlugin("Vault");
ServicesManager servicesManager = server.getServicesManager();
if (plugin != null) {
RegisteredServiceProvider<Economy> economyProvider = servicesManager.getRegistration(Economy.class);
if (economyProvider != null) {
economy = economyProvider.getProvider();
}
}
return (economy != null);
}
/** /**
* Makes the player pay for printing a given number of books * Makes the player pay for printing a given number of books
* *
@@ -69,15 +62,16 @@ public final class EconomyHelper {
* @param numCopies <p>The number of copies the player is trying to print</p> * @param numCopies <p>The number of copies the player is trying to print</p>
* @return <p>True if the player cannot pay for the printing of the books</p> * @return <p>True if the player cannot pay for the printing of the books</p>
*/ */
public static boolean cannotPayForBookPrinting(@NotNull Player player, int numCopies) { public boolean cannotPayForBookPrinting(@NotNull Player player, int numCopies) {
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
//BookPriceQuantity: How many items are required to pay for each book //BookPriceQuantity: How many items are required to pay for each book
//BookPriceType: Which item is used to pay for the books. AIR = use economy //BookPriceType: Which item is used to pay for the books. AIR = use economy
Material bookCurrency = BooksWithoutBordersConfig.getBookPriceType(); Material bookCurrency = config.getBookPriceType();
double cost = BooksWithoutBordersConfig.getBookPriceQuantity() * numCopies; double cost = config.getBookPriceQuantity() * numCopies;
int itemCost = (int) cost; int itemCost = (int) cost;
if (bookCurrency == Material.AIR) { if (bookCurrency == Material.AIR) {
return !EconomyHelper.payForBookPrintingEconomy(player, cost, numCopies); return !payForBookPrintingEconomy(player, cost, numCopies);
} else { } else {
if (bookCurrency == Material.WRITABLE_BOOK) { if (bookCurrency == Material.WRITABLE_BOOK) {
//Writable books are treated as a special case to prevent WIP books from being used //Writable books are treated as a special case to prevent WIP books from being used
@@ -102,7 +96,7 @@ public final class EconomyHelper {
* @param itemCost <p>The number of writable books to pay</p> * @param itemCost <p>The number of writable books to pay</p>
* @return <p>True if the payment was successful</p> * @return <p>True if the payment was successful</p>
*/ */
private static boolean takeWritableBookPayment(@NotNull Player player, int itemCost) { private boolean takeWritableBookPayment(@NotNull Player player, int itemCost) {
List<ItemStack> books = getPlayersEmptyBooks(player); List<ItemStack> books = getPlayersEmptyBooks(player);
if (countItems(books) < itemCost) { if (countItems(books) < itemCost) {
BooksWithoutBorders.sendErrorMessage(player, itemCost + " empty " + Material.WRITABLE_BOOK + BooksWithoutBorders.sendErrorMessage(player, itemCost + " empty " + Material.WRITABLE_BOOK +
@@ -133,7 +127,7 @@ public final class EconomyHelper {
* @param items <p>The items to count</p> * @param items <p>The items to count</p>
* @return <p>The total number of items</p> * @return <p>The total number of items</p>
*/ */
private static int countItems(@NotNull List<ItemStack> items) { private int countItems(@NotNull List<ItemStack> items) {
int totalItems = 0; int totalItems = 0;
for (ItemStack itemStack : items) { for (ItemStack itemStack : items) {
totalItems += itemStack.getAmount(); totalItems += itemStack.getAmount();
@@ -148,7 +142,7 @@ public final class EconomyHelper {
* @return <p>The empty books in the player's inventory</p> * @return <p>The empty books in the player's inventory</p>
*/ */
@NotNull @NotNull
private static List<ItemStack> getPlayersEmptyBooks(@NotNull Player player) { private List<ItemStack> getPlayersEmptyBooks(@NotNull Player player) {
List<ItemStack> validBooks = new ArrayList<>(); List<ItemStack> validBooks = new ArrayList<>();
for (ItemStack itemStack : player.getInventory().getContents()) { for (ItemStack itemStack : player.getInventory().getContents()) {
if (itemStack == null || itemStack.getType() != Material.WRITABLE_BOOK) { if (itemStack == null || itemStack.getType() != Material.WRITABLE_BOOK) {
@@ -171,7 +165,12 @@ public final class EconomyHelper {
* @param numCopies <p>The number of books the player is printing</p> * @param numCopies <p>The number of books the player is printing</p>
* @return <p>True if the player had the money and it has been withdrawn</p> * @return <p>True if the player had the money and it has been withdrawn</p>
*/ */
private static boolean payForBookPrintingEconomy(@NotNull Player player, double cost, int numCopies) { private boolean payForBookPrintingEconomy(@NotNull Player player, double cost, int numCopies) {
if (economy == null) {
BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_VAULT_COST_BUT_UNAVAILABLE);
return false;
}
if ((economy.getBalance(player) - cost) >= 0) { if ((economy.getBalance(player) - cost) >= 0) {
economy.withdrawPlayer(player, cost); economy.withdrawPlayer(player, cost);
BooksWithoutBorders.sendSuccessMessage(player, economy.format(cost) + " withdrawn to create " + BooksWithoutBorders.sendSuccessMessage(player, economy.format(cost) + " withdrawn to create " +
@@ -191,12 +190,12 @@ public final class EconomyHelper {
* @param player <p>The player which needs to pay</p> * @param player <p>The player which needs to pay</p>
* @param itemCost <p>The number of items to pay</p> * @param itemCost <p>The number of items to pay</p>
*/ */
private static void payForBookPrintingItem(@NotNull Player player, int itemCost) { private void payForBookPrintingItem(@NotNull Player player, int itemCost) {
PlayerInventory playerInventory = player.getInventory(); PlayerInventory playerInventory = player.getInventory();
int clearedAmount = 0; int clearedAmount = 0;
while (clearedAmount < itemCost) { while (clearedAmount < itemCost) {
int firstItemIndex = playerInventory.first(BooksWithoutBordersConfig.getBookPriceType()); int firstItemIndex = playerInventory.first(BooksWithoutBorders.getConfiguration().getBookPriceType());
ItemStack firstItem = playerInventory.getItem(firstItemIndex); ItemStack firstItem = playerInventory.getItem(firstItemIndex);
if (Objects.requireNonNull(firstItem).getAmount() <= itemCost - clearedAmount) { if (Objects.requireNonNull(firstItem).getAmount() <= itemCost - clearedAmount) {

View File

@@ -1,35 +0,0 @@
package net.knarcraft.bookswithoutborders.state;
import org.jetbrains.annotations.NotNull;
/**
* This enum represents the different available encryption styles
*/
public enum EncryptionStyle {
DNA("dna"),
SUBSTITUTION("substitution");
private final String name;
EncryptionStyle(@NotNull String name) {
this.name = name;
}
/**
* Gets an encryption style given its name
*
* @param name <p>The name of the encryption style</p>
* @return <p>An encryption style or null if no match is found</p>
*/
@NotNull
public static EncryptionStyle getFromString(@NotNull String name) {
for (EncryptionStyle style : EncryptionStyle.values()) {
if (style.name.equalsIgnoreCase(name)) {
return style;
}
}
return SUBSTITUTION;
}
}

View File

@@ -0,0 +1,150 @@
package net.knarcraft.bookswithoutborders.thread;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.container.MigrationRequest;
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
import net.knarcraft.bookswithoutborders.utility.BookHelper;
import net.knarcraft.bookswithoutborders.utility.BookToFromTextHelper;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
/**
* A thread for doing book migrations without locking up the main thread
*/
public class MigrationQueueThread implements Runnable {
private Boolean success = null;
private int taskId;
@Override
public void run() {
long systemTime = System.nanoTime();
//Repeat for at most 0.025 seconds
while (System.nanoTime() - systemTime < 25000000) {
if (pollQueue()) {
break;
}
}
}
/**
* Sets the task id used for stopping this task
*
* @param taskId <p>The id of this task</p>
*/
public void setTaskId(int taskId) {
this.taskId = taskId;
}
/**
* Polls the migration queue for any waiting requests
*
* @return <p>True if the queue is empty and it's safe to quit</p>
*/
public boolean pollQueue() {
MigrationRequest migrationRequest = BooksWithoutBorders.getMigrationQueue().poll();
if (migrationRequest == null) {
return true;
}
if (success == null) {
success = true;
}
success = success & migrateFile(migrationRequest.file(), migrationRequest.player());
if (BooksWithoutBorders.getMigrationQueue().peek() == null) {
Player player = migrationRequest.player();
if (success) {
BooksWithoutBorders.sendSuccessMessage(player, "Successfully migrated all books");
} else {
BooksWithoutBorders.sendErrorMessage(player, "Failed to migrate all books");
}
BooksWithoutBorders.getInstance().getServer().getScheduler().cancelTask(this.taskId);
success = null;
return true;
}
return false;
}
/**
* Migrates a single book file
*
* @param file <p>The file to migrate</p>
* @param player <p>The player causing this code to be executed</p>
* @return <p>True if the migration completed successfully</p>
*/
private boolean migrateFile(@NotNull File file, @NotNull Player player) {
BookMeta bookMeta = (BookMeta) BooksWithoutBorders.getItemFactory().getItemMeta(Material.WRITTEN_BOOK);
if (bookMeta == null) {
return false;
}
BookMeta loadedBook;
String extension = BookFileHelper.getExtensionFromPath(file.getName());
if (extension.equalsIgnoreCase("yml")) {
loadedBook = BookToFromTextHelper.encryptedBookFromYml(file, bookMeta, "", true);
} else if (extension.equalsIgnoreCase("txt")) {
loadedBook = BookToFromTextHelper.bookFromFile(file, bookMeta);
} else {
BooksWithoutBorders.log(Level.WARNING, "File with unexpected extension " + extension + " encountered!");
return true;
}
if (loadedBook == null) {
BooksWithoutBorders.log(Level.SEVERE, "Unable to load book: " + file.getName());
return false;
}
// Attempt to retain UUID naming
boolean isPublic = true;
OfflinePlayer author = player;
try {
UUID authorId = UUID.fromString(file.getParentFile().getName());
author = Bukkit.getOfflinePlayer(authorId);
isPublic = false;
} catch (IllegalArgumentException ignored) {
}
try {
String newName = BookHelper.getBookFile(loadedBook, author, isPublic);
return saveBook(file.getParentFile(), newName, loadedBook, file);
} catch (IllegalArgumentException exception) {
BooksWithoutBorders.sendErrorMessage(player, "Failed to migrate book: " + file.getName() + " Cause:");
BooksWithoutBorders.sendErrorMessage(player, exception.getMessage());
return false;
}
}
/**
* Saves a migrated book
*
* @param parent <p>The parent folder the file belongs to</p>
* @param newName <p>The new name of the file</p>
* @param bookMeta <p>The metadata of the book to migrate</p>
* @param oldFile <p>The old file path, in case it should be deleted</p>
* @return <p>True if successfully saved</p>
*/
private boolean saveBook(@NotNull File parent, @NotNull String newName, @NotNull BookMeta bookMeta,
@NotNull File oldFile) {
try {
BookToFromTextHelper.bookToYml(parent.getAbsolutePath(), newName, bookMeta);
if (!oldFile.getAbsolutePath().equalsIgnoreCase(new File(parent, newName + ".yml").getAbsolutePath())) {
return oldFile.delete();
}
return true;
} catch (IOException exception) {
BooksWithoutBorders.log(Level.SEVERE, "Failed to save migrated book: " + newName);
return false;
}
}
}

View File

@@ -1,9 +1,12 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.state.BookDirectory; import net.knarcraft.bookswithoutborders.state.BookDirectory;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.meta.BookMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -16,8 +19,6 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
/** /**
* Helper class for dealing with files * Helper class for dealing with files
*/ */
@@ -33,7 +34,7 @@ public final class BookFileHelper {
* @return <p>True if the number is a book index</p> * @return <p>True if the number is a book index</p>
*/ */
public static boolean isBookListIndex(@NotNull String possibleIndex) { public static boolean isBookListIndex(@NotNull String possibleIndex) {
File bookDirectory = new File(getBookFolder().replaceAll("[\\\\/]$", "")); File bookDirectory = new File(BooksWithoutBorders.getConfiguration().getBookFolder().replaceAll("[\\\\/]$", ""));
try { try {
//Tests if a load list number has been supplied //Tests if a load list number has been supplied
@@ -146,18 +147,16 @@ public final class BookFileHelper {
for (File foundFile : existingFiles) { for (File foundFile : existingFiles) {
// Filter out invalid files // Filter out invalid files
if (!foundFile.isFile() || foundFile.getName().contains(" ") || foundFile.getName().contains("§")) { if (!foundFile.isFile() || foundFile.getName().contains("§")) {
continue; continue;
} }
String fileName = foundFile.getName(); String fileName = foundFile.getName();
String separator = BooksWithoutBordersConfig.getTitleAuthorSeparator(); String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
if (fileName.contains(separator)) { if (fileName.contains(separator)) {
//Convert the UUID into a username if necessary //Convert the UUID into a username if necessary
String[] data = fileName.split(separator); String userName = getBookAuthorFromPath(fileName);
String extension = data[1].substring(data[1].length() - 4); String title = getBookTitleFromPath(fileName);
String userName = data[1].substring(0, data[1].length() - 4); fileList.add(title + separator + BookHelper.authorFromUUID(userName));
data[1] = BookHelper.authorFromUUID(userName) + extension;
fileList.add(String.join(separator, data));
} else { } else {
fileList.add(fileName); fileList.add(fileName);
} }
@@ -187,4 +186,131 @@ public final class BookFileHelper {
return foundDuplicates; return foundDuplicates;
} }
/**
* Gets a book's title given the book's path
*
* @param path <p>The path of the book</p>
* @return <p>The book title</p>
*/
@NotNull
public static String getBookTitleFromPath(@NotNull String path) {
String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
String stripped = stripExtensionFromPath(path);
if (stripped.contains(separator)) {
return stripped.split(separator)[0];
} else {
return stripped;
}
}
/**
* Gets a book's author given the book's path
*
* @param path <p>The path of the book</p>
* @return <p>The author name</p>
*/
@NotNull
public static String getBookAuthorFromPath(@NotNull String path) {
String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
String stripped = stripExtensionFromPath(path);
if (stripped.contains(separator)) {
return stripped.split(separator)[1];
} else {
return BooksWithoutBorders.getStringFormatter().getUnFormattedColoredMessage(Translatable.NEUTRAL_UNKNOWN_AUTHOR);
}
}
/**
* Strips the extension from the given path
*
* @param path <p>The path to strip the extension from</p>
* @return <p>The input with the extension stripped</p>
*/
@NotNull
public static String getExtensionFromPath(@NotNull String path) {
int dotIndex = path.lastIndexOf(".");
if (dotIndex > 0) {
String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
if (path.lastIndexOf(separator) < dotIndex && (path.length() - dotIndex == 4)) {
return path.substring(dotIndex + 1);
}
}
return path;
}
/**
* Strips the extension from the given path
*
* @param path <p>The path to strip the extension from</p>
* @return <p>The input with the extension stripped</p>
*/
@NotNull
public static String stripExtensionFromPath(@NotNull String path) {
int dotIndex = path.lastIndexOf(".");
if (dotIndex > 0) {
String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
if (path.lastIndexOf(separator) < dotIndex && (path.length() - dotIndex == 4)) {
return path.substring(0, dotIndex);
}
}
return path;
}
/**
* Attempts to find the correct book file
*
* @param folder <p>The folder the book is in</p>
* @param bookMeta <p>The book meta of the book to find</p>
* @return <p>The book's file, or null if not found</p>
*/
@Nullable
public static File findBookFile(@NotNull String folder, @NotNull BookMeta bookMeta) {
String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
String fileName = bookMeta.getTitle() + separator + bookMeta.getAuthor();
return findBookFile(folder, fileName);
}
/**
* Attempts to find the correct book file
*
* @param folder <p>The folder the book is in</p>
* @param fileName <p>The name of the book to find</p>
* @return <p>The book's file, or null if not found</p>
*/
@Nullable
public static File findBookFile(@NotNull String folder, @NotNull String fileName) {
fileName = InputCleaningHelper.cleanString(fileName);
File file = new File(folder, fileName + ".yml");
if (file.exists()) {
return getBookFile(file.getAbsolutePath());
}
file = new File(folder, fileName.replace(" ", "_") + ".yml");
if (file.exists()) {
return getBookFile(file.getAbsolutePath());
}
file = new File(folder, fileName.replace(" ", "_") + ".txt");
if (file.exists()) {
return getBookFile(file.getAbsolutePath());
} else {
return null;
}
}
/**
* Replaces an author name with a player UUID if matched
*
* @param fileName <p>The filename to replace the author of</p>
* @return <p>The filename, or the filename with the author replaced with UUID</p>
*/
public static String replaceAuthorWithUUID(@NotNull String fileName) {
String userName = BookFormatter.stripColor(getBookAuthorFromPath(fileName));
Player player = Bukkit.getPlayerExact(userName);
if (player != null) {
return userName.replace(userName, player.getUniqueId().toString());
} else {
return userName;
}
}
} }

View File

@@ -95,8 +95,11 @@ public final class BookFormatter {
*/ */
public static void formatLastPageAddNewline(@NotNull List<String> rawPages, int fitsNewline) { public static void formatLastPageAddNewline(@NotNull List<String> rawPages, int fitsNewline) {
int pageIndex = rawPages.size() - 1; int pageIndex = rawPages.size() - 1;
if (rawPages.get(pageIndex).length() <= fitsNewline && !rawPages.get(pageIndex).isEmpty()) { String pageContents = rawPages.get(pageIndex);
rawPages.set(pageIndex, (rawPages.get(pageIndex)) + "\n"); if (pageContents == null) {
rawPages.set(pageIndex, "");
} else if (pageContents.length() <= fitsNewline && !pageContents.isEmpty()) {
rawPages.set(pageIndex, pageContents + "\n");
} }
} }

View File

@@ -2,21 +2,24 @@ package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.state.BookDirectory; import net.knarcraft.bookswithoutborders.state.BookDirectory;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
import org.bukkit.inventory.meta.WritableBookMeta;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/** /**
* Helper class for getting abstract book information * Helper class for getting abstract book information
@@ -27,6 +30,38 @@ public final class BookHelper {
} }
/**
* Un-signs a book
*
* @param oldMetadata <p>The signed book's metadata</p>
* @param amount <p>The amount of unsigned books to produce</p>
* @return <p>The unsigned books, or null if unable to generate metadata</p>
*/
@Nullable
public static ItemStack unsignBook(@NotNull BookMeta oldMetadata, int amount) {
WritableBookMeta newMetadata = (BookMeta) BooksWithoutBorders.getItemFactory().getItemMeta(Material.WRITABLE_BOOK);
if (newMetadata == null) {
BooksWithoutBorders.log(Level.SEVERE, "Unable to create writable book metadata");
return null;
}
//Create a new unsigned book with the same data
ItemStack newBook = new ItemStack(Material.WRITABLE_BOOK, amount);
if (oldMetadata.hasLore()) {
List<String> oldLore = oldMetadata.getLore();
if (oldLore != null) {
List<String> newLore = new ArrayList<>(oldLore);
newLore.remove(0);
newMetadata.setLore(newLore);
}
}
newMetadata.setPages(oldMetadata.getPages());
newBook.setItemMeta(newMetadata);
// Give the player the new book
return newBook;
}
/** /**
* Converts the author of a book from UUID if necessary * Converts the author of a book from UUID if necessary
* *
@@ -36,7 +71,7 @@ public final class BookHelper {
@NotNull @NotNull
public static String authorFromUUID(@NotNull String author) { public static String authorFromUUID(@NotNull String author) {
try { try {
UUID authorID = UUID.fromString(author); UUID authorID = UUID.fromString(BookFormatter.stripColor(author));
Player player = Bukkit.getPlayer(authorID); Player player = Bukkit.getPlayer(authorID);
if (player != null) { if (player != null) {
author = player.getName(); author = player.getName();
@@ -71,12 +106,13 @@ public final class BookHelper {
*/ */
@Nullable @Nullable
public static String getBookDirectoryPathString(@NotNull BookDirectory bookDirectory, @NotNull CommandSender sender) { public static String getBookDirectoryPathString(@NotNull BookDirectory bookDirectory, @NotNull CommandSender sender) {
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
String folder = null; String folder = null;
String bookFolder = BooksWithoutBordersConfig.getBookFolder(); String bookFolder = config.getBookFolder();
if (bookDirectory == BookDirectory.PUBLIC) { if (bookDirectory == BookDirectory.PUBLIC) {
folder = bookFolder; folder = bookFolder;
} else if (bookDirectory == BookDirectory.PLAYER && sender instanceof Player player) { } else if (bookDirectory == BookDirectory.PLAYER && sender instanceof Player player) {
folder = bookFolder + player.getUniqueId() + getSlash(); folder = bookFolder + player.getUniqueId() + config.getSlash();
} }
return folder; return folder;
} }
@@ -88,7 +124,7 @@ public final class BookHelper {
*/ */
public static void increaseGeneration(@NotNull ItemStack bookItem) { public static void increaseGeneration(@NotNull ItemStack bookItem) {
BookMeta bookMeta = (BookMeta) bookItem.getItemMeta(); BookMeta bookMeta = (BookMeta) bookItem.getItemMeta();
if (BooksWithoutBordersConfig.changeGenerationOnCopy() && bookMeta != null) { if (BooksWithoutBorders.getConfiguration().changeGenerationOnCopy() && bookMeta != null) {
bookMeta.setGeneration(BookHelper.getNextGeneration(bookMeta.getGeneration())); bookMeta.setGeneration(BookHelper.getNextGeneration(bookMeta.getGeneration()));
bookItem.setItemMeta(bookMeta); bookItem.setItemMeta(bookMeta);
} }
@@ -124,8 +160,8 @@ public final class BookHelper {
* @throws IllegalArgumentException <p>If the book title or author contains the title author separator</p> * @throws IllegalArgumentException <p>If the book title or author contains the title author separator</p>
*/ */
@NotNull @NotNull
public static String getBookFile(@NotNull BookMeta book, @NotNull Player player, boolean isPublic) throws IllegalArgumentException { public static String getBookFile(@NotNull BookMeta book, @NotNull OfflinePlayer player, boolean isPublic) throws IllegalArgumentException {
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator(); String separator = BooksWithoutBorders.getConfiguration().getTitleAuthorSeparator();
String bookName; String bookName;
if (book.hasTitle()) { if (book.hasTitle()) {
bookName = book.getTitle(); bookName = book.getTitle();
@@ -136,8 +172,9 @@ public final class BookHelper {
bookName = "Untitled"; bookName = "Untitled";
} }
String playerName = player.getName() == null ? player.getUniqueId().toString() : player.getName();
String authorName; String authorName;
if ((!book.hasAuthor() || isAuthor(player.getName(), book.getAuthor())) && !isPublic) { if ((!book.hasAuthor() || isAuthor(playerName, book.getAuthor())) && !isPublic) {
//Store as unique id to account for name changes //Store as unique id to account for name changes
authorName = player.getUniqueId().toString(); authorName = player.getUniqueId().toString();
} else if (!book.hasAuthor()) { } else if (!book.hasAuthor()) {
@@ -145,16 +182,18 @@ public final class BookHelper {
} else { } else {
authorName = book.getAuthor(); authorName = book.getAuthor();
if (authorName == null) { if (authorName == null) {
authorName = "Unknown"; authorName = BooksWithoutBorders.getStringFormatter().getUnFormattedColoredMessage(Translatable.NEUTRAL_UNKNOWN_AUTHOR);
} }
} }
if (bookName.contains(titleAuthorSeparator) || authorName.contains(titleAuthorSeparator)) { if (InputCleaningHelper.cleanString(bookName).contains(separator) ||
throw new IllegalArgumentException("The author or title contains the title author separator. Saving this " + InputCleaningHelper.cleanString(authorName).contains(separator)) {
throw new IllegalArgumentException("The author; " + authorName + " or title; " + bookName +
" contains the title author separator (" + separator + "). Saving this " +
"book would lead to unexpected problems."); "book would lead to unexpected problems.");
} }
return fixName(cleanString(bookName + titleAuthorSeparator + authorName), false); return InputCleaningHelper.cleanString(bookName + separator + authorName);
} }
/** /**

View File

@@ -3,7 +3,6 @@ package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.state.BookDirectory; import net.knarcraft.bookswithoutborders.state.BookDirectory;
import org.bukkit.Bukkit;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -15,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level;
/** /**
* A helper class for loading books from files * A helper class for loading books from files
@@ -57,54 +57,59 @@ public final class BookLoader {
BooksWithoutBorders.sendErrorMessage(sender, "Unrecognized book directory!"); BooksWithoutBorders.sendErrorMessage(sender, "Unrecognized book directory!");
return null; return null;
} }
return loadBook(sender, fileName, isSigned, bookDirectory, directory, numCopies);
}
/**
* Loads the given book
*
* @param sender <p>The command sender trying to load the book</p>
* @param fileName <p>The index or file name of the book to load</p>
* @param isSigned <p>Whether to load the book as signed, and not unsigned</p>
* @param bookDirectory <p>The type of directory to save in</p>
* @param directory <p>The directory to save the book in</p>
* @param numCopies <p>The number of copies to load</p>
* @return <p>The loaded book</p>
*/
@Nullable
public static ItemStack loadBook(@NotNull CommandSender sender, @NotNull String fileName, @NotNull String isSigned,
@NotNull BookDirectory bookDirectory, @NotNull String directory, int numCopies) {
//Find the filename if a book index is given //Find the filename if a book index is given
try { try {
int bookIndex = Integer.parseInt(fileName); int bookIndex = Integer.parseInt(fileName);
List<String> availableFiles = BooksWithoutBorders.getAvailableBooks(sender, bookDirectory == BookDirectory.PUBLIC); List<String> availableFiles = BooksWithoutBorders.getAvailableBooks(sender,
bookDirectory == BookDirectory.PUBLIC);
if (bookIndex <= availableFiles.size()) { if (bookIndex <= availableFiles.size()) {
fileName = availableFiles.get(Integer.parseInt(fileName) - 1); fileName = availableFiles.get(Integer.parseInt(fileName) - 1);
} }
} catch (NumberFormatException ignored) { } catch (NumberFormatException ignored) {
} }
BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
//Get the full path of the book to load //Get the full path of the book to load
File file = getFullPath(sender, fileName, bookDirectory, directory); File file = getFullPath(sender, fileName, bookDirectory, directory);
if (file == null) { if (file == null) {
//Try converting the username to UUID //Try converting the username to UUID
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator(); String replaced = BookFileHelper.replaceAuthorWithUUID(fileName);
String[] data = fileName.split(titleAuthorSeparator); file = getFullPath(sender, replaced, bookDirectory, directory);
String extension = data[1].substring(data[1].length() - 4);
String userName = data[1].substring(0, data[1].length() - 4);
Player player = Bukkit.getPlayer(userName);
if (player != null) {
data[1] = player.getUniqueId() + extension;
file = getFullPath(sender, String.join(titleAuthorSeparator, data), bookDirectory, directory);
if (file == null) { if (file == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!"); BooksWithoutBorders.sendErrorMessage(sender, "Incorrect file name!");
return null; return null;
} }
} else {
return null;
}
} }
//Make sure the player can pay for the book //Make sure the player can pay for the book
if (BooksWithoutBordersConfig.booksHavePrice() && if (config.booksHavePrice() &&
!sender.hasPermission("bookswithoutborders.bypassBookPrice") && !sender.hasPermission("bookswithoutborders.bypassBookPrice") &&
(bookDirectory == BookDirectory.PUBLIC || bookDirectory == BookDirectory.PLAYER) && (bookDirectory == BookDirectory.PUBLIC || bookDirectory == BookDirectory.PLAYER) &&
EconomyHelper.cannotPayForBookPrinting((Player) sender, numCopies)) { config.getEconomyManager().cannotPayForBookPrinting((Player) sender, numCopies)) {
return null; return null;
} }
//Generate a new empty book //Generate a new empty book
ItemStack book; ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
BookMeta bookMetadata = (BookMeta) BooksWithoutBorders.getItemFactory().getItemMeta(Material.WRITTEN_BOOK); BookMeta bookMetadata = (BookMeta) BooksWithoutBorders.getItemFactory().getItemMeta(Material.WRITTEN_BOOK);
if (isSigned.equalsIgnoreCase("true")) {
book = new ItemStack(Material.WRITTEN_BOOK);
} else {
book = new ItemStack(Material.WRITABLE_BOOK);
}
if (bookMetadata == null) { if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(sender, "Unable to create blank book metadata!"); BooksWithoutBorders.sendErrorMessage(sender, "Unable to create blank book metadata!");
@@ -134,6 +139,10 @@ public final class BookLoader {
BookHelper.increaseGeneration(book); BookHelper.increaseGeneration(book);
book.setAmount(numCopies); book.setAmount(numCopies);
if (!isSigned.equalsIgnoreCase("true") && book.getItemMeta() != null) {
return BookHelper.unsignBook((BookMeta) book.getItemMeta(), book.getAmount());
}
return book; return book;
} }
@@ -149,13 +158,18 @@ public final class BookLoader {
@Nullable @Nullable
private static File getFullPath(@NotNull CommandSender sender, @NotNull String fileName, private static File getFullPath(@NotNull CommandSender sender, @NotNull String fileName,
@NotNull BookDirectory bookDirectory, @NotNull String directory) { @NotNull BookDirectory bookDirectory, @NotNull String directory) {
File file; BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
String slash = BooksWithoutBordersConfig.getSlash(); File file = null;
String bookFolder = BooksWithoutBordersConfig.getBookFolder(); String slash = config.getSlash();
if (bookDirectory == BookDirectory.ENCRYPTED) { if (bookDirectory == BookDirectory.ENCRYPTED) {
file = BookFileHelper.getBookFile(bookFolder + "Encrypted" + slash + directory + slash + fileName); file = BookFileHelper.findBookFile(config.getEncryptedBookPath() + directory + slash, fileName);
} else { } else {
file = BookFileHelper.getBookFile(BookHelper.getBookDirectoryPathString(bookDirectory, sender) + fileName); String folder = BookHelper.getBookDirectoryPathString(bookDirectory, sender);
if (folder != null) {
file = BookFileHelper.findBookFile(folder, fileName);
} else {
BooksWithoutBorders.log(Level.WARNING, "Unknown directory " + bookDirectory);
}
} }
if (file == null || !file.isFile()) { if (file == null || !file.isFile()) {
return null; return null;

View File

@@ -1,7 +1,9 @@
package net.knarcraft.bookswithoutborders.utility; package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.Translatable;
import net.knarcraft.bookswithoutborders.encryption.AESConfiguration;
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
import net.knarcraft.knarlib.util.FileHelper; import net.knarcraft.knarlib.util.FileHelper;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
@@ -12,16 +14,12 @@ import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import static net.knarcraft.bookswithoutborders.utility.BookHelper.authorFromUUID; import static net.knarcraft.bookswithoutborders.utility.BookHelper.authorFromUUID;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/** /**
* Helper class for converting books to and from text/yml files * Helper class for converting books to and from text/yml files
@@ -40,7 +38,55 @@ public final class BookToFromTextHelper {
* @throws IOException <p>If unable to save the book</p> * @throws IOException <p>If unable to save the book</p>
*/ */
public static void bookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException { public static void bookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException {
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File(path, "blank")); FileConfiguration bookYml = getBookConfiguration(bookMetadata);
bookYml.save(new File(path, fileName + ".yml"));
}
/**
* Saves an encrypted book's contents to a .yml file
*
* @param path <p>The path of the folder to save to. Must end with a slash</p>
* @param fileName <p>The name of the file to load to</p>
* @param bookMetadata <p>Metadata about the book to save</p>
* @throws IOException <p>If unable to save the book</p>
*/
public static void encryptedBookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata,
@NotNull EncryptionStyle encryptionStyle, @NotNull String encryptionKey,
@Nullable AESConfiguration aesConfiguration) throws IOException {
FileConfiguration bookYml = getBookConfiguration(bookMetadata);
bookYml.set("Encryption.Style", encryptionStyle.toString());
bookYml.set("Encryption.Key", encryptionKey);
if (encryptionStyle == EncryptionStyle.AES) {
if (aesConfiguration == null) {
throw new IOException("Attempted to save AES encrypted book without supplying a configuration!");
}
bookYml.set("Encryption.AES.IV", EncryptionHelper.bytesToHex(aesConfiguration.iv()));
bookYml.set("Encryption.AES.Salt", EncryptionHelper.bytesToHex(aesConfiguration.salt()));
}
List<String> encryptedPages = EncryptionHelper.encryptDecryptBookPages(bookMetadata, encryptionStyle,
aesConfiguration, encryptionKey, true);
if (encryptedPages == null || encryptedPages.isEmpty()) {
throw new IOException("Book encryption failed!");
}
bookYml.set("Encryption.Data", encryptedPages);
// Make sure the plaintext cannot simply be seen in the file
if (BooksWithoutBorders.getConfiguration().useRealEncryption()) {
bookYml.set("Pages", null);
}
bookYml.save(path + fileName + ".yml");
}
/**
* Gets a file configuration containing a book's information
*
* @param bookMetadata <p>Metadata about the book to save</p>
*/
private static FileConfiguration getBookConfiguration(@NotNull BookMeta bookMetadata) {
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(new File("", "blank"));
if (bookMetadata.hasTitle()) { if (bookMetadata.hasTitle()) {
bookYml.set("Title", bookMetadata.getTitle()); bookYml.set("Title", bookMetadata.getTitle());
@@ -60,7 +106,7 @@ public final class BookToFromTextHelper {
bookYml.set("Lore", bookMetadata.getLore()); bookYml.set("Lore", bookMetadata.getLore());
} }
bookYml.save(path + InputCleaningHelper.cleanString(fileName) + ".yml"); return bookYml;
} }
/** /**
@@ -82,30 +128,67 @@ public final class BookToFromTextHelper {
} }
/** /**
* Saves a book's contents to a text file * Loads a book from a .yml file
* *
* @param folderPath <p>The folder path to save to. Must end with a slash</p> * @param file <p>The path of the file to load</p>
* @param fileName <p>The name of the file to save to</p> * @param bookMetadata <p>Metadata which will be altered with the book's contents</p>
* @param bookMetadata <p>Metadata about the book to save</p> * @param userKey <p>The user-supplied decryption key</p>
* @throws IOException <p>If unable to save the book</p> * @param forceDecrypt <p>Whether to use the saved key for decryption, ignoring the supplied key</p>
* @return <p>Metadata for the loaded book</p>
*/ */
public static void bookToTXT(@NotNull String folderPath, @NotNull String fileName, @NotNull BookMeta bookMetadata) throws IOException { @Nullable
FileWriter fileWriter = new FileWriter(folderPath + InputCleaningHelper.cleanString(fileName) + ".txt", StandardCharsets.UTF_8); public static BookMeta encryptedBookFromYml(@NotNull File file, @NotNull BookMeta bookMetadata, @NotNull String userKey, boolean forceDecrypt) {
PrintWriter printWriter = new PrintWriter(fileWriter); BookMeta meta;
List<String> pages = bookMetadata.getPages();
BookMeta.Generation generation = bookMetadata.getGeneration(); try {
if (generation == null) { meta = bookFromYml(file, bookMetadata);
generation = BookMeta.Generation.ORIGINAL; if (meta == null) {
return null;
}
} catch (IllegalArgumentException exception) {
return null;
} }
String generationString = ":" + generation.name();
//Save each page of the book as a text line // If the plaintext is stored in the file, don't bother with real decryption
printWriter.println("[Book]" + generationString); if (!meta.getPages().isEmpty()) {
for (String page : pages) { return meta;
printWriter.println(page);
} }
printWriter.close();
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file);
userKey = EncryptionHelper.sha256(userKey);
String realKey = bookYml.getString("Encryption.Key", "");
if (forceDecrypt) {
userKey = realKey;
}
if (!userKey.equals(realKey)) {
BooksWithoutBorders.log(Level.INFO, "Supplied key: " + userKey + " does not match real key: " + realKey);
return null;
}
List<String> data = bookYml.getStringList("Encryption.Data");
if (data.isEmpty()) {
return null;
}
EncryptionStyle encryptionStyle = EncryptionStyle.getFromString(bookYml.getString("Encryption.Style",
EncryptionStyle.SUBSTITUTION.toString()));
AESConfiguration aesConfiguration = null;
if (encryptionStyle == EncryptionStyle.AES) {
byte[] iv = EncryptionHelper.hexStringToByteArray(bookYml.getString("Encryption.AES.IV", ""));
byte[] salt = EncryptionHelper.hexStringToByteArray(bookYml.getString("Encryption.AES.Salt", ""));
aesConfiguration = new AESConfiguration(iv, salt, userKey);
}
meta.setPages(data);
List<String> decryptedPages = EncryptionHelper.encryptDecryptBookPages(meta, encryptionStyle,
aesConfiguration, userKey, false);
if (decryptedPages != null && !decryptedPages.isEmpty()) {
meta.setPages(decryptedPages);
} else {
return null;
}
return meta;
} }
/** /**
@@ -122,7 +205,8 @@ public final class BookToFromTextHelper {
bookMetadata.setGeneration(BookMeta.Generation.valueOf(bookYml.getString("Generation", "ORIGINAL"))); bookMetadata.setGeneration(BookMeta.Generation.valueOf(bookYml.getString("Generation", "ORIGINAL")));
bookMetadata.setTitle(bookYml.getString("Title", "Untitled")); bookMetadata.setTitle(bookYml.getString("Title", "Untitled"));
bookMetadata.setAuthor(authorFromUUID(bookYml.getString("Author", "Unknown"))); bookMetadata.setAuthor(authorFromUUID(bookYml.getString("Author",
BooksWithoutBorders.getStringFormatter().getUnFormattedColoredMessage(Translatable.NEUTRAL_UNKNOWN_AUTHOR))));
bookMetadata.setPages(bookYml.getStringList("Pages")); bookMetadata.setPages(bookYml.getStringList("Pages"));
bookMetadata.setLore(bookYml.getStringList("Lore")); bookMetadata.setLore(bookYml.getStringList("Lore"));
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -141,35 +225,20 @@ public final class BookToFromTextHelper {
*/ */
@Nullable @Nullable
private static BookMeta bookFromTXT(@NotNull String fileName, @NotNull File file, @NotNull BookMeta bookMetadata) { private static BookMeta bookFromTXT(@NotNull String fileName, @NotNull File file, @NotNull BookMeta bookMetadata) {
String author;
String title;
String titleAuthorSeparator = BooksWithoutBordersConfig.getTitleAuthorSeparator();
//Remove .txt extension
fileName = fileName.substring(0, fileName.length() - 4);
//Get title and author from the file name //Get title and author from the file name
if (fileName.contains(titleAuthorSeparator)) { String title = BookFileHelper.getBookTitleFromPath(fileName);
String[] titleAuthor = fileName.split(titleAuthorSeparator); String author = BookFileHelper.getBookAuthorFromPath(fileName);
title = titleAuthor[0];
author = titleAuthor[1];
} else {
author = "Unknown";
title = fileName;
}
//Replace underscores with spaces
title = fixName(title, true);
//Read the .txt file //Read the .txt file
List<String> rawPages; List<String> rawPages;
try { try {
rawPages = readTextFile(file); rawPages = readTextFile(file);
if (rawPages == null) { if (rawPages == null) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Text file's first line was null"); BooksWithoutBorders.log(Level.SEVERE, "Text file's first line was null");
return null; return null;
} }
} catch (IOException exception) { } catch (IOException exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to read text file"); BooksWithoutBorders.log(Level.SEVERE, "Unable to read text file");
return null; return null;
} }
@@ -184,7 +253,7 @@ public final class BookToFromTextHelper {
//Update the metadata of the book with its new values //Update the metadata of the book with its new values
bookMetadata.setAuthor(authorFromUUID(author)); bookMetadata.setAuthor(authorFromUUID(author));
bookMetadata.setTitle(title); bookMetadata.setTitle(title.substring(0, 32));
bookMetadata.setPages(pages); bookMetadata.setPages(pages);
return bookMetadata; return bookMetadata;

View File

@@ -2,12 +2,16 @@ package net.knarcraft.bookswithoutborders.utility;
import net.knarcraft.bookswithoutborders.BooksWithoutBorders; import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig; import net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig;
import net.knarcraft.bookswithoutborders.encryption.AES;
import net.knarcraft.bookswithoutborders.encryption.AESConfiguration;
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
import net.knarcraft.bookswithoutborders.encryption.Encryptor;
import net.knarcraft.bookswithoutborders.encryption.GenenCrypt; import net.knarcraft.bookswithoutborders.encryption.GenenCrypt;
import net.knarcraft.bookswithoutborders.encryption.Magic;
import net.knarcraft.bookswithoutborders.encryption.OneTimePad;
import net.knarcraft.bookswithoutborders.encryption.SubstitutionCipher; import net.knarcraft.bookswithoutborders.encryption.SubstitutionCipher;
import net.knarcraft.bookswithoutborders.state.EncryptionStyle;
import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta;
@@ -16,20 +20,24 @@ import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getBookFolder;
import static net.knarcraft.bookswithoutborders.config.BooksWithoutBordersConfig.getSlash;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString; import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.cleanString;
import static net.knarcraft.bookswithoutborders.utility.InputCleaningHelper.fixName;
/** /**
* Helper class for book encryption * Helper class for book encryption
*/ */
public final class EncryptionHelper { public final class EncryptionHelper {
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
private EncryptionHelper() { private EncryptionHelper() {
} }
@@ -37,49 +45,79 @@ public final class EncryptionHelper {
* Transforms a string key/password into its numerical values * Transforms a string key/password into its numerical values
* *
* @param key <p>The key to transform</p> * @param key <p>The key to transform</p>
* @return <p>The numbers representing the key's characters</p> * @return <p>A comma-separated string of the numbers representing the key's characters</p>
*/ */
@NotNull @NotNull
public static String getNumberKeyFromStringKey(@NotNull String key) { public static String getNumberKeyFromStringKey(@NotNull String key) {
StringBuilder integerKey = new StringBuilder(); StringBuilder integerKey = new StringBuilder(String.valueOf(Character.codePointAt(key, 0)));
for (int x = 0; x < key.length(); x++) { for (int x = 1; x < key.length(); x++) {
integerKey.append(Character.getNumericValue(Character.codePointAt(key, x))); integerKey.append(", ").append(Character.codePointAt(key, x));
} }
return integerKey.toString(); return integerKey.toString();
} }
/**
* Performs sha256 hashing on the input string
*
* @param input <p>The input to hash</p>
* @return <p>The hashed input</p>
*/
@NotNull
public static String sha256(@NotNull String input) {
String hashed;
try {
final MessageDigest digest = MessageDigest.getInstance("SHA3-256");
final byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
hashed = Base64.getEncoder().encodeToString(hashBytes);
} catch (NoSuchAlgorithmException exception) {
hashed = input;
}
return hashed;
}
/** /**
* Encrypts the pages of a book * Encrypts the pages of a book
* *
* @param book <p>The book to encrypt</p> * @param book <p>The book to encrypt</p>
* @param style <p>The encryption style to use</p> * @param style <p>The encryption style to use</p>
* @param integerKey <p>The encryption key to use</p> * @param aesConfiguration <p>The AES configuration to use, if encrypting using AES</p>
* @param player <p>The player trying to encrypt a book</p> * @param key <p>The encryption key to use</p>
* @param encrypt <p>Whether to perform an encryption or a decryption</p>
* @return <p>The pages of the book in encrypted form</p> * @return <p>The pages of the book in encrypted form</p>
*/ */
@Nullable @Nullable
public static List<String> encryptBookPages(@NotNull BookMeta book, @NotNull EncryptionStyle style, public static List<String> encryptDecryptBookPages(@NotNull BookMeta book, @NotNull EncryptionStyle style,
@NotNull String integerKey, @NotNull Player player) { @Nullable AESConfiguration aesConfiguration, @NotNull String key,
List<String> encryptedPages = new ArrayList<>(); boolean encrypt) {
//Scramble the book's contents Encryptor encryptor = switch (style) {
if (style == EncryptionStyle.DNA) { case DNA -> new GenenCrypt(EncryptionHelper.getNumberKeyFromStringKey(key));
//Encrypt the pages using gene-based encryption case SUBSTITUTION -> new SubstitutionCipher(EncryptionHelper.getNumberKeyFromStringKey(key));
GenenCrypt gc = new GenenCrypt(integerKey); case AES -> {
for (int x = 0; x < book.getPages().size(); x++) { if (aesConfiguration == null) {
encryptedPages.add(gc.encrypt(book.getPage(x + 1))); throw new IllegalArgumentException("Attempted to perform AES encryption without a valid AES configuration");
}
return encryptedPages;
} else if (style == EncryptionStyle.SUBSTITUTION) {
//Encrypt the pages using a substitution cipher
SubstitutionCipher sc = new SubstitutionCipher();
for (int x = 0; x < book.getPages().size(); x++) {
encryptedPages.add(sc.encrypt(book.getPage(x + 1), integerKey));
}
return encryptedPages;
} else { } else {
BooksWithoutBorders.sendErrorMessage(player, "Invalid encryption style encountered!"); yield new AES(aesConfiguration);
}
}
case ONE_TIME_PAD -> new OneTimePad(key);
case MAGIC -> new Magic();
};
List<String> encryptedPages = new ArrayList<>();
for (int x = 0; x < book.getPages().size(); x++) {
String text = book.getPage(x + 1);
String output;
if (encrypt) {
output = encryptor.encryptText(text);
} else {
output = encryptor.decryptText(text);
}
if (output == null || output.isEmpty()) {
return null; return null;
} }
encryptedPages.add(output);
}
return encryptedPages;
} }
/** /**
@@ -110,9 +148,6 @@ public final class EncryptionHelper {
@Nullable @Nullable
public static ItemStack encryptBook(Player player, boolean mainHand, @NotNull String key, public static ItemStack encryptBook(Player player, boolean mainHand, @NotNull String key,
@NotNull EncryptionStyle style, @NotNull String groupName) { @NotNull EncryptionStyle style, @NotNull String groupName) {
//converts user supplied key into integer form
String integerKey = EncryptionHelper.getNumberKeyFromStringKey(key);
BookMeta book = InventoryHelper.getHeldBookMetadata(player, mainHand); BookMeta book = InventoryHelper.getHeldBookMetadata(player, mainHand);
if (book == null) { if (book == null) {
BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata from the held book!"); BooksWithoutBorders.sendErrorMessage(player, "Unable to get metadata from the held book!");
@@ -124,14 +159,18 @@ public final class EncryptionHelper {
return null; return null;
} }
String hashedKey = sha256(key);
AESConfiguration configuration = AESConfiguration.getNewConfiguration(hashedKey);
//Save the book's un-encrypted contents to a file //Save the book's un-encrypted contents to a file
BookMeta newMetadata = saveBookPlaintext(groupName, player, book, integerKey); BookMeta newMetadata = saveBookPlaintext(groupName, player, book, style, hashedKey, configuration);
if (newMetadata == null) { if (newMetadata == null) {
return null; return null;
} }
//Get the encrypted pages //Get the encrypted pages
List<String> encryptedPages = EncryptionHelper.encryptBookPages(book, style, integerKey, player); List<String> encryptedPages = EncryptionHelper.encryptDecryptBookPages(book, style, configuration, hashedKey,
true);
if (encryptedPages == null) { if (encryptedPages == null) {
return null; return null;
} }
@@ -178,16 +217,19 @@ public final class EncryptionHelper {
* @param groupName <p>The group who's allowed to decrypt the book, or ""</p> * @param groupName <p>The group who's allowed to decrypt the book, or ""</p>
* @param player <p>The player trying to encrypt the book</p> * @param player <p>The player trying to encrypt the book</p>
* @param book <p>The book to encrypt</p> * @param book <p>The book to encrypt</p>
* @param integerKey <p>The key used to encrypt the book</p> * @param encryptionStyle <p>The encryption style used for the book</p>
* @param key <p>The key used to encrypt the book</p>
* @param aesConfiguration <p>The AES configuration to use, if encrypting using AES</p>
* @return <p>The new metadata for the book, or null if it could not be saved</p> * @return <p>The new metadata for the book, or null if it could not be saved</p>
*/ */
@Nullable @Nullable
private static BookMeta saveBookPlaintext(@NotNull String groupName, @NotNull Player player, private static BookMeta saveBookPlaintext(@NotNull String groupName, @NotNull Player player,
@NotNull BookMeta book, @NotNull String integerKey) { @NotNull BookMeta book, @NotNull EncryptionStyle encryptionStyle,
@NotNull String key, @NotNull AESConfiguration aesConfiguration) {
BookMeta newMetadata = book; BookMeta newMetadata = book;
boolean wasSaved; boolean wasSaved;
if (groupName.trim().isEmpty()) { if (groupName.trim().isEmpty()) {
wasSaved = saveEncryptedBook(player, book, integerKey); wasSaved = saveEncryptedBook(player, book, encryptionStyle, key, aesConfiguration);
} else { } else {
newMetadata = saveEncryptedBookForGroup(player, book, groupName); newMetadata = saveEncryptedBookForGroup(player, book, groupName);
wasSaved = newMetadata != null; wasSaved = newMetadata != null;
@@ -205,20 +247,88 @@ public final class EncryptionHelper {
* @param player <p>The player trying to load the book</p> * @param player <p>The player trying to load the book</p>
* @param key <p>The encryption key/password for decryption</p> * @param key <p>The encryption key/password for decryption</p>
* @param deleteEncryptedFile <p>Whether to delete the plaintext file after decryption is finished</p> * @param deleteEncryptedFile <p>Whether to delete the plaintext file after decryption is finished</p>
* @param forceDecrypt <p>Whether to force decryption using the stored key</p>
* @return <p>The loaded book, or null if no book could be loaded</p> * @return <p>The loaded book, or null if no book could be loaded</p>
*/ */
@Nullable @Nullable
public static ItemStack loadEncryptedBook(@NotNull Player player, @NotNull String key, boolean deleteEncryptedFile) { public static ItemStack loadEncryptedBook(@NotNull Player player, @NotNull String key, boolean deleteEncryptedFile,
boolean forceDecrypt) {
ItemStack heldBook = InventoryHelper.getHeldBook(player, true); ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta(); BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta();
String path = getBookFolder() + "Encrypted" + getSlash(); String path = BooksWithoutBorders.getConfiguration().getEncryptedBookPath();
if (bookMetadata == null) { if (bookMetadata == null) {
return null; return null;
} }
String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetadata, player, true); String fileName = BookHelper.getBookFile(bookMetadata, player, true);
fileName = fixName(cleanString(fileName), false); fileName = cleanString(fileName);
File file = new File(path + fileName + ".yml");
if (!file.isFile()) {
file = new File(path + fileName + ".txt");
if (!file.isFile()) {
BooksWithoutBorders.sendErrorMessage(player, "Book not found!");
return null;
}
} else {
try {
bookMetadata = BookToFromTextHelper.encryptedBookFromYml(file, bookMetadata, key, forceDecrypt);
if (bookMetadata == null) {
throw new IllegalArgumentException();
}
} catch (Exception e) {
BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!");
return null;
}
}
if (deleteEncryptedFile) {
Logger logger = BooksWithoutBorders.getInstance().getLogger();
try {
if (!file.delete()) {
logger.log(Level.SEVERE, "Book encryption data failed to delete upon decryption!\n" +
"File location:" + file.getPath());
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Book encryption data failed to delete upon decryption!\nFile location:" + file.getPath());
}
}
ItemStack newBook = new ItemStack(Material.WRITTEN_BOOK);
newBook.setItemMeta(bookMetadata);
newBook.setAmount(InventoryHelper.getHeldBook(player, true).getAmount());
return newBook;
}
/**
* Loads an encrypted book
*
* @param player <p>The player trying to load the book</p>
* @param key <p>The encryption key/password for decryption</p>
* @param deleteEncryptedFile <p>Whether to delete the plaintext file after decryption is finished</p>
* @return <p>The loaded book, or null if no book could be loaded</p>
*/
@Nullable
public static ItemStack loadEncryptedBookLegacy(@NotNull Player player, @NotNull String key, boolean deleteEncryptedFile) {
BooksWithoutBorders.sendErrorMessage(player, "Attempting legacy decryption");
ItemStack heldBook = InventoryHelper.getHeldBook(player, true);
BookMeta bookMetadata = (BookMeta) heldBook.getItemMeta();
String path = BooksWithoutBorders.getConfiguration().getEncryptedBookPath();
if (bookMetadata == null) {
return null;
}
StringBuilder integerKey = new StringBuilder();
for (int x = 0; x < key.length(); x++) {
integerKey.append(Character.getNumericValue(Character.codePointAt(key, x)));
}
String fileName = "[" + integerKey + "]" + BookHelper.getBookFile(bookMetadata, player, true);
fileName = cleanString(fileName).replace(" ", "_");
File file = new File(path + fileName + ".yml"); File file = new File(path + fileName + ".yml");
if (!file.isFile()) { if (!file.isFile()) {
@@ -231,6 +341,10 @@ public final class EncryptionHelper {
} else { } else {
try { try {
bookMetadata = BookToFromTextHelper.bookFromFile(file, bookMetadata); bookMetadata = BookToFromTextHelper.bookFromFile(file, bookMetadata);
if (bookMetadata == null) {
BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!");
return null;
}
} catch (Exception e) { } catch (Exception e) {
BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!"); BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!");
return null; return null;
@@ -238,24 +352,55 @@ public final class EncryptionHelper {
} }
if (deleteEncryptedFile) { if (deleteEncryptedFile) {
ConsoleCommandSender consoleSender = BooksWithoutBorders.getConsoleSender(); Logger logger = BooksWithoutBorders.getInstance().getLogger();
try { try {
if (!file.delete()) { if (!file.delete()) {
BooksWithoutBorders.sendErrorMessage(consoleSender, "Book encryption data failed to delete upon decryption!"); logger.log(Level.SEVERE, "Book encryption data failed to delete upon decryption!\n" +
BooksWithoutBorders.sendErrorMessage(consoleSender, "File location:" + file.getPath()); "File location:" + file.getPath());
} }
} catch (Exception e) { } catch (Exception e) {
BooksWithoutBorders.sendErrorMessage(consoleSender, "Book encryption data failed to delete upon decryption!"); logger.log(Level.SEVERE, "Book encryption data failed to delete upon decryption!\nFile location:" + file.getPath());
BooksWithoutBorders.sendErrorMessage(consoleSender, "File location:" + file.getPath());
} }
} }
ItemStack newBook = new ItemStack(Material.WRITTEN_BOOK);//Book(book.getAuthor(), book.getTitle(), pages, 1, 387); ItemStack newBook = new ItemStack(Material.WRITTEN_BOOK);
newBook.setItemMeta(bookMetadata); newBook.setItemMeta(bookMetadata);
newBook.setAmount(InventoryHelper.getHeldBook(player, true).getAmount()); newBook.setAmount(InventoryHelper.getHeldBook(player, true).getAmount());
return newBook; return newBook;
} }
/**
* Converts a byte array to a hexadecimal string
*
* @param bytes <p>The bytes to convert</p>
* @return <p>The resulting hexadecimal string</p>
*/
public static String bytesToHex(byte[] bytes) {
byte[] hexChars = new byte[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars, StandardCharsets.UTF_8);
}
/**
* Converts a string of hexadecimals to a byte array
*
* @param input <p>The hexadecimal input to parse</p>
* @return <p>The resulting byte array</p>
*/
public static byte[] hexStringToByteArray(@NotNull String input) {
int len = input.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(input.charAt(i), 16) << 4) +
Character.digit(input.charAt(i + 1), 16));
}
return data;
}
/** /**
* Saves an encrypted book to be decryptable for the given group * Saves an encrypted book to be decryptable for the given group
* *
@@ -267,17 +412,18 @@ public final class EncryptionHelper {
@Nullable @Nullable
private static BookMeta saveEncryptedBookForGroup(@NotNull Player player, @NotNull BookMeta bookMetadata, private static BookMeta saveEncryptedBookForGroup(@NotNull Player player, @NotNull BookMeta bookMetadata,
@NotNull String groupName) { @NotNull String groupName) {
String path = getBookFolder() + "Encrypted" + getSlash() + cleanString(groupName) + getSlash(); BooksWithoutBordersConfig config = BooksWithoutBorders.getConfiguration();
String path = config.getEncryptedBookPath() + cleanString(groupName) + config.getSlash();
File dirTest = new File(path); File dirTest = new File(path);
//Creates group dir //Creates group dir
if (!dirTest.exists()) { if (!dirTest.exists()) {
try { try {
if (!dirTest.mkdir()) { if (!dirTest.mkdir()) {
BooksWithoutBorders.sendErrorMessage(BooksWithoutBorders.getConsoleSender(), "Unable to create encryption group folder!"); BooksWithoutBorders.log(Level.SEVERE, "Unable to create encryption group folder!");
return null; return null;
} }
} catch (Exception exception) { } catch (Exception exception) {
BooksWithoutBorders.getInstance().getLogger().log(Level.SEVERE, "Unable to save group encrypted book"); BooksWithoutBorders.log(Level.SEVERE, "Unable to save group encrypted book");
return null; return null;
} }
} }
@@ -295,8 +441,7 @@ public final class EncryptionHelper {
bookMetadata.setLore(newLore); bookMetadata.setLore(newLore);
//Save file //Save file
File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") : File file = new File(path + fileName + ".yml");
new File(path + fileName + ".txt");
if (!file.isFile()) { if (!file.isFile()) {
try { try {
BookToFromTextHelper.bookToYml(path, fileName, bookMetadata); BookToFromTextHelper.bookToYml(path, fileName, bookMetadata);
@@ -314,25 +459,28 @@ public final class EncryptionHelper {
* *
* @param player <p>The player encrypting the book</p> * @param player <p>The player encrypting the book</p>
* @param bookMetaData <p>Metadata for the book to encrypt</p> * @param bookMetaData <p>Metadata for the book to encrypt</p>
* @param encryptionStyle <p>The style of encryption used</p>
* @param key <p>The key to use for encryption</p> * @param key <p>The key to use for encryption</p>
* @param aesConfiguration <p>The AES configuration to use if encrypting with AES</p>
* @return <p>The new encrypted metadata for the book, or null if encryption failed</p> * @return <p>The new encrypted metadata for the book, or null if encryption failed</p>
*/ */
@NotNull @NotNull
private static Boolean saveEncryptedBook(@NotNull Player player, @NotNull BookMeta bookMetaData, @NotNull String key) { private static Boolean saveEncryptedBook(@NotNull Player player, @NotNull BookMeta bookMetaData,
String path = getBookFolder() + "Encrypted" + getSlash(); @NotNull EncryptionStyle encryptionStyle, @NotNull String key,
@Nullable AESConfiguration aesConfiguration) {
String path = BooksWithoutBorders.getConfiguration().getEncryptedBookPath();
String fileName = "[" + key + "]" + BookHelper.getBookFile(bookMetaData, player, true); String fileName = BookHelper.getBookFile(bookMetaData, player, true);
fileName = fixName(cleanString(fileName), false); fileName = cleanString(fileName);
//cancels saving if file is already encrypted //cancels saving if file is already encrypted
File file = (BooksWithoutBordersConfig.getUseYml()) ? new File(path + fileName + ".yml") : File file = new File(path + fileName + ".yml");
new File(path + fileName + ".txt");
if (file.isFile()) { if (file.isFile()) {
return true; return true;
} }
try { try {
BookToFromTextHelper.bookToYml(path, fileName, bookMetaData); BookToFromTextHelper.encryptedBookToYml(path, fileName, bookMetaData, encryptionStyle, key, aesConfiguration);
} catch (IOException exception) { } catch (IOException exception) {
BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!"); BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!");
return false; return false;

View File

@@ -50,23 +50,6 @@ public final class InputCleaningHelper {
return fileName; return fileName;
} }
/**
* Changes spaces to underscores or underscores to spaces, depending on context
*
* @param fileName <p>The file name to fix</p>
* @param isLoading <p>Whether loading from a file as opposed to saving to a file</p>
* @return <p>The fixed name</p>
*/
@NotNull
public static String fixName(@NotNull String fileName, @NotNull Boolean isLoading) {
if (isLoading) {
fileName = fileName.replace("_", " ");
} else {
fileName = fileName.replace(" ", "_");
}
return fileName;
}
/** /**
* Parses a page number for a string like "page1" * Parses a page number for a string like "page1"
* *
@@ -95,7 +78,7 @@ public final class InputCleaningHelper {
*/ */
@Nullable @Nullable
public static String parseAuthorSpecifier(@NotNull String input) { public static String parseAuthorSpecifier(@NotNull String input) {
Pattern pattern = Pattern.compile("author([0-9a-zA-Z_]+)"); Pattern pattern = Pattern.compile("author([\\p{L}0-9_.,!#%&'`^@$+]+)");
Matcher matcher = pattern.matcher(input); Matcher matcher = pattern.matcher(input);
if (matcher.matches()) { if (matcher.matches()) {
return matcher.group(1); return matcher.group(1);
@@ -104,4 +87,40 @@ public final class InputCleaningHelper {
} }
} }
/**
* Checks whether the given input is a boolean
*
* @param input <p>The input to validate</p>
* @return <p>True if the given input is a boolean</p>
*/
public static boolean isBoolean(@NotNull String input) {
return input.matches("(true|false)");
}
/**
* Checks whether the given input is an integer
*
* @param input <p>The input to validate</p>
* @return <p>True if the given input is an integer</p>
*/
public static boolean isInt(@NotNull String input) {
return input.matches("[0-9]+");
}
/**
* Merges all arguments to a string with spaces
*
* @param arguments <p>The arguments to merge</p>
* @param stripLastN <p>How many of the last arguments to ignore</p>
* @return <p>The merged arguments</p>
*/
@NotNull
public static String mergeArguments(String[] arguments, int stripLastN) {
StringBuilder builder = new StringBuilder(arguments[0]);
for (int i = 1; i < arguments.length - stripLastN; i++) {
builder.append(" ").append(arguments[i]);
}
return builder.toString();
}
} }

View File

@@ -18,6 +18,25 @@ public final class InventoryHelper {
private InventoryHelper() { private InventoryHelper() {
} }
/**
* Gets the book from a player's main hand
*
* @param player <p>The player holding the book</p>
* @return <p>The held book, or null if not holding one book in the main hand</p>
*/
public static ItemStack getHeldBook(@NotNull Player player) {
@NotNull ItemSlot heldSigned = InventoryHelper.getHeldSlotBook(player, true, true,
true, true);
@NotNull ItemSlot heldUnSigned = InventoryHelper.getHeldSlotBook(player, true, true,
true, false);
if (heldSigned == ItemSlot.MAIN_HAND || heldUnSigned == ItemSlot.MAIN_HAND) {
boolean holdingSigned = heldSigned == ItemSlot.MAIN_HAND;
return InventoryHelper.getHeldBook(player, holdingSigned);
} else {
return null;
}
}
/** /**
* Gets the book the holder is playing * Gets the book the holder is playing
* *

View File

@@ -59,4 +59,27 @@ public final class TabCompletionTypeHelper {
return booleansAndNumbers; return booleansAndNumbers;
} }
/**
* Gets tab-completions with only remaining text, from a list of full strings
*
* @param arguments <p>The arguments given by the user</p>
* @param filtered <p>Tab-completions filtered by user input</p>
* @return <p>The cleaned tab-completions</p>
*/
public static @NotNull List<String> getCleanedTabCompletions(@NotNull String[] arguments,
@NotNull List<String> filtered) {
List<String> cleaned = new ArrayList<>();
for (String name : filtered) {
String[] parts = name.split(" ");
if (parts[arguments.length - 2].equalsIgnoreCase(arguments[arguments.length - 2])) {
StringBuilder builder = new StringBuilder(parts[arguments.length - 1]);
for (int i = arguments.length; i < parts.length; i++) {
builder.append(" ").append(parts[i]);
}
cleaned.add(builder.toString());
}
}
return cleaned;
}
} }

View File

@@ -1,9 +1,10 @@
Options: Options:
# Whether to use YAML for saved books instead of just storing them as text # The language to use. Only "en" is built-in, but custom languages can be added
Save_Books_in_Yaml_Format: true Language: "en"
# The maximum number of duplicates of a saved book allowed # The maximum number of duplicates of a saved book allowed
Max_Number_of_Duplicates: 5 Max_Number_of_Duplicates: 5
# The separator used to separate the book title and the book author # The separator used to separate the book title and the book author. While this is a ',' by default for backwards
# compatibility, a rarely used character like '¤' is better to prevent restrictions on book titles.
Title-Author_Separator: "," Title-Author_Separator: ","
# The separator used to denote a new line in the book/item lore # The separator used to denote a new line in the book/item lore
Lore_line_separator: "~" Lore_line_separator: "~"
@@ -31,4 +32,8 @@ Options:
# vanilla behavior where a copy of a copy cannot be copied further. # vanilla behavior where a copy of a copy cannot be copied further.
Change_Generation_On_Copy: false Change_Generation_On_Copy: false
# Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents. # Whether to enable hitting a chiseled bookshelf while sneaking to see the shelf's contents.
Enable_Book_Peeking: false Enable_Book_Peeking: true
# Whether to use true AES encryption when encrypting and decrypting books. While the hashed password used for
# encryption is still stored in the book file, the real contents of the book are not. Admin decrypt can be used to
# peek at books, if an admin gets a hold of one, but only the encrypted AES cypher text is stored in the book.
Use_Real_Encryption: false

View File

@@ -101,6 +101,18 @@ commands:
description: Sets custom data for a chiseled bookshelf used when peeking at the bookshelf description: Sets custom data for a chiseled bookshelf used when peeking at the bookshelf
usage: /<command> <delete/name/lore> <text> [more text] usage: /<command> <delete/name/lore> <text> [more text]
permission: bookswithoutborders.editbookshelf permission: bookswithoutborders.editbookshelf
addBookTitlePage:
description: Adds a blank page, title page or chapter page depending on input and whether the book is signed
usage: /<command> [page index] [title~description]
permission: bookswithoutborders.addtitlepage
deleteBookPage:
description: Deletes one page from a book
usage: /<command> <page>
permission: bookswithoutborders.deletepage
migrateBooks:
description: Migrates all txt books to yml, and fixes any incorrect filenames
usage: /<command>
permission: bookswithoutborders.admin
permissions: permissions:
bookswithoutborders.*: bookswithoutborders.*:
description: Grants all permissions description: Grants all permissions
@@ -147,6 +159,8 @@ permissions:
bookswithoutborders.setlore: true bookswithoutborders.setlore: true
bookswithoutborders.format: true bookswithoutborders.format: true
bookswithoutborders.setgeneration: true bookswithoutborders.setgeneration: true
bookswithoutborders.addtitlepage: true
bookswithoutborders.deletepage: true
bookswithoutborders.format: bookswithoutborders.format:
description: Allows a player to format a book description: Allows a player to format a book
bookswithoutborders.save: bookswithoutborders.save:
@@ -201,3 +215,7 @@ permissions:
description: Allows player to left-click a bookshelf to see the contents of the shelf description: Allows player to left-click a bookshelf to see the contents of the shelf
bookswithoutborders.editbookshelf: bookswithoutborders.editbookshelf:
description: Allows player to set name/lore for bookshelves, used for peeking description: Allows player to set name/lore for bookshelves, used for peeking
bookswithoutborders.addtitlepage:
description: Allows player to add a blank title page to a book
bookswithoutborders.deletepage:
description: Allows player to delete a page from a book

View File

@@ -0,0 +1,58 @@
en:
PREFIX: "[BwB]"
SUCCESS_COPY: "Book copied!"
SUCCESS_CLEARED: "Book cleared!"
SUCCESS_DECRYPTED: "Book decrypted!"
SUCCESS_AUTO_DECRYPTED: "Book auto-decrypted!"
SUCCESS_DELETED: "\"{file}\" deleted successfully"
SUCCESS_FORMATTED: "Book formatted!"
SUCCESS_GIVE_SENT: "Book sent!"
SUCCESS_GIVE_RECEIVED: "Book received!"
ERROR_PLAYER_ONLY: "This command can only be used by a player!"
ERROR_NOT_HOLDING_WRITTEN_BOOK: "You must be holding a written book to {action} it!"
ERROR_NOT_HOLDING_WRITABLE_BOOK: "You must be holding a writable book to {action} it!"
ERROR_NOT_HOLDING_ANY_BOOK: "You must be holding a book to perform this command"
ERROR_ONLY_ONE_BOOK: "You cannot {action} two books at once!"
ACTION_COPY: "copy"
ACTION_CLEAR: "clear"
ACTION_DECRYPT: "decrypt"
ACTION_ENCRYPT: "encrypt"
ACTION_FORMAT: "format"
ERROR_COPY_COUNT_NOT_SPECIFIED: "You must specify the number of copies to be made!"
ERROR_COPY_NEGATIVE_AMOUNT: "Number of copies must be larger than 0!"
ERROR_COPY_INVALID_AMOUNT: |
Book not copied!
Number specified was invalid!
ERROR_BOOK_COPIED_TOO_FAR: "You cannot copy this book any further. You must have the original or a direct copy."
ERROR_INVENTORY_FULL: "You need an available slot in your inventory."
ERROR_METADATA_MISSING: "Unable to get metadata for the held book!"
ERROR_VAULT_COST_BUT_UNAVAILABLE: "&4The cost was set to economy, but Vault is unavailable!"
ERROR_DECRYPT_MISSING_KEY: "No decryption password given!"
ERROR_DECRYPT_FAILED: "Failed to decrypt book!"
ERROR_ENCRYPTED_DIRECTORY_EMPTY_OR_MISSING: "Could not find any encrypted files!"
ERROR_ENCRYPTED_BOOK_UNKNOWN: "No matching encrypted book found!"
ERROR_DELETE_EMPTY: "No files available to delete!"
ERROR_INCORRECT_FILE_NAME: "Incorrect file name!"
ERROR_DELETE_FAILED_SILENT: "Deletion failed without an exception!"
ERROR_DELETE_FAILED_EXCEPTION: "Deletion failed!"
ERROR_ENCRYPT_NO_KEY: "You must specify a key to encrypt a book!"
ERROR_ENCRYPT_EMPTY: "Book must have contents to encrypt!"
ERROR_TOO_MANY_ARGUMENTS_COMMAND: "You have given too many arguments for this command!"
ERROR_GIVE_NO_RECIPIENT: "You have not specified the recipient of the book!"
ERROR_GIVE_RECIPIENT_UNKNOWN: "Player not found!"
ERROR_GIVE_RECIPIENT_FULL: "Receiving player must have space in their inventory to receive books!"
ERROR_GIVE_INVALID_COPIES_AMOUNT: "Invalid number of book copies specified!"
ERROR_GIVE_LOAD_FAILED: "Book failed to load!"
NEUTRAL_COMMANDS_HEADER: |
&e[] denote optional parameters
<> denote required parameters
{} denote required permission
In some cases, commands with required parameters can be called with no parameters
{bookPrice}&eCommands:
{commands}
NEUTRAL_COMMANDS_BOOK_PRICE_ECO: "\n&c[{price} is required to create a book]"
NEUTRAL_COMMANDS_BOOK_PRICE_ITEM: "&c[{quantity} {type} (s) are required to create a book]\n"
NEUTRAL_COMMANDS_COMMAND: "\n \n&e{usage}: &a{description}"
NEUTRAL_COMMANDS_COMMAND_NO_PERMISSION_REQUIRED: "None"
NEUTRAL_COMMANDS_COMMAND_PERMISSION: " &7{{permission}}"
NEUTRAL_UNKNOWN_AUTHOR: "Unknown"

View File

@@ -4,6 +4,7 @@ import net.knarcraft.bookswithoutborders.encryption.GenenCrypt;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class GenenCryptTest { public class GenenCryptTest {
@@ -11,8 +12,11 @@ public class GenenCryptTest {
public void encryptDecryptTest() { public void encryptDecryptTest() {
GenenCrypt gc = new GenenCrypt("Another Key"); GenenCrypt gc = new GenenCrypt("Another Key");
gc.printCodonTable(); gc.printCodonTable();
String encrypted = gc.encrypt("Hello World!"); String encrypted = gc.encryptText("Hello World!");
assertEquals("HELLO WORLD!", gc.decrypt(encrypted));
assertNotNull(encrypted);
assertEquals("HELLO WORLD!", gc.decryptText(encrypted));
} }
} }

View File

@@ -3,22 +3,31 @@ package net.knarcraft.bookswithoutborders.encryption;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
public class AESTest { public class AESTest {
@Test @Test
public void encryptDecryptTest() { public void encryptDecryptTest() {
String plainText = "A lot of text"; String plainText = "Flåklypa";
String password = "abc123"; String password = "TqOZdpY9RjjjVE9JjCWVecUYObv5MYidByrpI3cxjoY=";
AES aes = new AES(AES.generateIV(), AES.generateIV()); System.out.println("Plaintext: " + plainText);
System.out.println("Encryption password: " + password);
String encrypted = aes.encryptDecryptText(plainText, password, true); AESConfiguration configuration = new AESConfiguration(new byte[]{-85, 103, -82, 71, 119, 28, 73, -75, -81, 102, -127, -125, -8, -75, 81, -111},
assertNotSame(encrypted, plainText); new byte[]{(byte) 104, -42, 63, 31, -120, -2, 14, -119, 35, 122, 109, -64, 122, 117, 33, -85}, password);
assertNotNull(encrypted); AES aes = new AES(configuration);
String decrypted = aes.encryptDecryptText(encrypted, password, false);
String cypherText = aes.encryptText(plainText);
System.out.println("Cypher text: " + cypherText);
assertNotNull(cypherText);
assertNotEquals(plainText, cypherText);
String decrypted = aes.decryptText(cypherText);
System.out.println("Decrypted: " + decrypted);
assertEquals(plainText, decrypted); assertEquals(plainText, decrypted);
} }

View File

@@ -0,0 +1,28 @@
package net.knarcraft.bookswithoutborders.encryption;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
public class GenenCryptTest {
@Test
public void encryptDecryptTest() {
String encryptionKey = EncryptionHelper.getNumberKeyFromStringKey("My secret password!");
String plaintext = "Very secret &4colored&r message.";
GenenCrypt genenCrypt = new GenenCrypt(encryptionKey);
String cypherText = genenCrypt.encryptText(plaintext);
assertNotNull(cypherText);
assertNotSame(cypherText, plaintext);
String decrypted = genenCrypt.decryptText(cypherText);
assertEquals(plaintext.toUpperCase(), decrypted);
}
}

View File

@@ -0,0 +1,26 @@
package net.knarcraft.bookswithoutborders.encryption;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
public class OneTimePadTest {
@Test
public void oneTimePadTest() {
String plaintext = "Very secret text that should be kept secret";
String key = "Very secret key!";
OneTimePad oneTimePad = new OneTimePad(key);
String cypherText = oneTimePad.encryptText(plaintext);
assertNotNull(cypherText);
assertNotSame(plaintext, cypherText);
String decrypted = oneTimePad.decryptText(cypherText);
assertEquals(plaintext, decrypted);
}
}

View File

@@ -0,0 +1,26 @@
package net.knarcraft.bookswithoutborders.encryption;
import net.knarcraft.bookswithoutborders.utility.EncryptionHelper;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
public class SubstitutionCipherTest {
@Test
public void encryptDecryptTest() {
String plaintext = "Very secret text that should be kept secret";
String integerKey = EncryptionHelper.getNumberKeyFromStringKey("Very secret key!");
SubstitutionCipher substitutionCipher = new SubstitutionCipher(integerKey);
String cypherText = substitutionCipher.encryptText(plaintext);
assertNotNull(cypherText);
assertNotSame(plaintext, cypherText);
String decrypted = substitutionCipher.decryptText(cypherText);
assertEquals(plaintext, decrypted);
}
}

View File

@@ -10,7 +10,7 @@ public class EncryptionHelperTest {
@Test @Test
public void getNumberKeyFromStringKey() { public void getNumberKeyFromStringKey() {
String numberKey = EncryptionHelper.getNumberKeyFromStringKey("hello"); String numberKey = EncryptionHelper.getNumberKeyFromStringKey("hello");
assertEquals("1714212124", numberKey); assertEquals("104, 101, 108, 108, 111", numberKey);
} }
} }