├── .classpath ├── .gitignore ├── .travis.yml ├── README.md ├── build.xml ├── pom.xml └── src ├── main ├── java │ └── me │ │ └── delta2force │ │ └── redditbrowser │ │ ├── Client.java │ │ ├── GithubAPIResponse.java │ │ ├── RedditBrowserPlugin.java │ │ ├── generator │ │ └── RedditGenerator.java │ │ ├── interaction │ │ └── InteractiveEnum.java │ │ ├── inventory │ │ └── RedditInventory.java │ │ ├── listeners │ │ └── EventListener.java │ │ ├── renderer │ │ ├── RedditRenderer.java │ │ └── TiledRenderer.java │ │ ├── repository │ │ ├── URLOptimizer.java │ │ └── URLToImageRepository.java │ │ └── room │ │ ├── RedditQueue.java │ │ ├── Room.java │ │ ├── RoomDimensions.java │ │ └── Screen.java └── resources │ ├── config.yml │ └── plugin.yml └── test └── java └── me └── delta2force └── redditbrowser └── repository └── URLOptimizerTest.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .settings 3 | .target 4 | target 5 | .project 6 | .vscode 7 | \.idea/ 8 | 9 | *.iml 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: [ openjdk8 ] 3 | sudo: false 4 | script: mvn install -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RedditBrowser - Browse Reddit in Minecraft [![Build Status](https://travis-ci.org/Delta2Force/redditbrowser.svg?branch=master)](https://travis-ci.org/Delta2Force/redditbrowser) 2 | (For some reason, the branch I made for more interaction is still registered in travis and will always fail since it doesn't exist anymore.) 3 | 4 | ## Installation 5 | Download the latest version from the releases tab and put it in your plugins tab. 6 | 7 | ## Usage 8 | When the plugin is first launched, a config file for it will be created. In this file you will have to enter the data from a Reddit application. Google „Reddit application page“ and create a script application. The username and password have to be from your account. Don’t worry, nothing gets sent to me. You can look inside of the code if you are worried. If you still don’t trust me, just create a throwaway. The client id is the text under the application name and the secret is labeled „secret“. I’m not sure if this works without this, but I advise to do it. After this, you don’t have to reload the server. Just do /reddit and it should work. 9 | 10 | Use `/reddit subreddit` in chat e.g. `/reddit awww`. 11 | 12 | WIP: If you want to join the lobby of another player, type `/reddit-join playername`. 13 | 14 | When you are in the room, you can upvote the post by clicking the button left or downvote by clicking the button right. 15 | You can leave a comment by writing a book using your book and quill and putting it in the comment chest. 16 | 17 | You can inspect a comment thread by right clicking on a comment. You will then see the comments under that comment. If you right click on those, you'll see the comments of those, and so on. 18 | 19 | You can see the post at its highest resolution by right clicking the image. 20 | 21 | 22 | ## Room configuration 23 | You can configure the following properties in the config file: 24 | 25 | `screenWidth`: the width of the screen in blocks 26 | 27 | `screenHeight`: the height of the screen in blocks 28 | 29 | `roomDepth`: how deep the room should be in blocks 30 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | me.delta2force 8 | redditbrowser 9 | 1.8 10 | jar 11 | 12 | RedditBrowser 13 | 14 | Browse reddit in minecraft 15 | Browse Reddit in Minecraft. 16 | 17 | 1.8 18 | UTF-8 19 | 20 | 21 | 22 | clean package 23 | 24 | 25 | org.apache.maven.plugins 26 | maven-compiler-plugin 27 | 3.7.0 28 | 29 | ${java.version} 30 | ${java.version} 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-shade-plugin 36 | 3.1.0 37 | 38 | 39 | package 40 | 41 | shade 42 | 43 | 44 | false 45 | 46 | 47 | 48 | 49 | 50 | org.apache.maven.plugins 51 | maven-surefire-plugin 52 | 2.22.1 53 | 54 | 55 | 56 | 57 | src/main/resources 58 | true 59 | 60 | 61 | 62 | 63 | 64 | 65 | spigotmc-repo 66 | https://hub.spigotmc.org/nexus/content/groups/public/ 67 | 68 | 69 | sonatype 70 | https://oss.sonatype.org/content/groups/public/ 71 | 72 | 73 | spring 74 | http://repo.spring.io/libs-release/ 75 | 76 | 77 | 78 | 79 | 80 | org.spigotmc 81 | spigot-api 82 | 1.14.4-R0.1-SNAPSHOT 83 | provided 84 | 85 | 86 | net.dean.jraw 87 | JRAW 88 | 1.1.0 89 | 90 | 91 | org.junit.jupiter 92 | junit-jupiter-engine 93 | 5.5.1 94 | test 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/Client.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser; 2 | 3 | import org.bukkit.configuration.file.FileConfiguration; 4 | 5 | public class Client { 6 | 7 | private String username; 8 | private String password; 9 | private String clientId; 10 | private String clientSecret; 11 | private int screenWidth; 12 | private int screenHeight; 13 | private int roomDepth; 14 | 15 | public Client(RedditBrowserPlugin reddit) { 16 | FileConfiguration config = reddit.getConfig(); 17 | this.username = config.getString("username"); 18 | this.password = config.getString("password"); 19 | this.clientId = config.getString("clientId"); 20 | this.clientSecret = config.getString("clientSecret"); 21 | this.screenWidth = config.getInt("screenWidth"); 22 | this.screenHeight = config.getInt("screenHeight"); 23 | this.roomDepth = config.getInt("roomDepth"); 24 | } 25 | 26 | public String getUsername() { 27 | return username; 28 | } 29 | 30 | public void setUsername(String username) { 31 | this.username = username; 32 | } 33 | 34 | public String getPassword() { 35 | return password; 36 | } 37 | 38 | public void setPassword(String password) { 39 | this.password = password; 40 | } 41 | 42 | public String getClientId() { 43 | return clientId; 44 | } 45 | 46 | public void setClientId(String clientId) { 47 | this.clientId = clientId; 48 | } 49 | 50 | public String getClientSecret() { 51 | return clientSecret; 52 | } 53 | 54 | public void setClientSecret(String clientSecret) { 55 | this.clientSecret = clientSecret; 56 | } 57 | 58 | public int getScreenWidth() { 59 | return screenWidth; 60 | } 61 | 62 | public void setScreenWidth(int screenWidth) { 63 | this.screenWidth = screenWidth; 64 | } 65 | 66 | public int getScreenHeight() { 67 | return screenHeight; 68 | } 69 | 70 | public void setScreenHeight(int screenHeight) { 71 | this.screenHeight = screenHeight; 72 | } 73 | 74 | public int getRoomDepth() { 75 | return roomDepth; 76 | } 77 | 78 | public void setRoomDepth(int roomDepth) { 79 | this.roomDepth = roomDepth; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/GithubAPIResponse.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser; 2 | 3 | public class GithubAPIResponse { 4 | public String html_url; 5 | public String tag_name; 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/RedditBrowserPlugin.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser; 2 | 3 | import com.google.gson.Gson; 4 | import me.delta2force.redditbrowser.generator.RedditGenerator; 5 | import me.delta2force.redditbrowser.inventory.RedditInventory; 6 | import me.delta2force.redditbrowser.listeners.EventListener; 7 | import me.delta2force.redditbrowser.room.Room; 8 | import me.delta2force.redditbrowser.room.RoomDimensions; 9 | import net.dean.jraw.RedditClient; 10 | import net.dean.jraw.http.OkHttpNetworkAdapter; 11 | import net.dean.jraw.http.UserAgent; 12 | import net.dean.jraw.models.Comment; 13 | import net.dean.jraw.models.KarmaBySubreddit; 14 | import net.dean.jraw.oauth.Credentials; 15 | import net.dean.jraw.oauth.OAuthHelper; 16 | import net.dean.jraw.tree.CommentNode; 17 | import org.apache.commons.lang.StringUtils; 18 | import org.bukkit.*; 19 | import org.bukkit.command.Command; 20 | import org.bukkit.command.CommandSender; 21 | import org.bukkit.entity.Player; 22 | import org.bukkit.inventory.ItemStack; 23 | import org.bukkit.plugin.java.JavaPlugin; 24 | import org.bukkit.scheduler.BukkitTask; 25 | import org.jetbrains.annotations.NotNull; 26 | 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.io.InputStreamReader; 30 | import java.net.MalformedURLException; 31 | import java.net.URL; 32 | import java.util.*; 33 | import java.util.concurrent.ConcurrentHashMap; 34 | 35 | import static me.delta2force.redditbrowser.room.Room.COMMENT_DISPLAY_NAME; 36 | 37 | public class RedditBrowserPlugin extends JavaPlugin { 38 | public static final String SUBMISSION_ID = "submissionId"; 39 | public static final String INTERACTIVE_ENUM = "interactiveEnum"; 40 | public static final String BUTTON_ACTIVATED = "buttonActivated"; 41 | public static final String ROOM_ID = "redditRoomId"; 42 | 43 | private Map beforeTPLocation = new HashMap<>(); 44 | private Map beforeTPExperience = new HashMap<>(); 45 | public Map> commentCache = new HashMap<>(); 46 | 47 | public RedditClient redditClient; 48 | public EventListener listener; 49 | public final Map roomMap = new ConcurrentHashMap<>(); 50 | 51 | @Override 52 | public void onEnable() { 53 | // Save the default config 54 | saveDefaultConfig(); 55 | listener = new EventListener(this); 56 | Bukkit.getServer().getPluginManager().registerEvents(listener, this); 57 | try { 58 | checkLatestVersion(); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | 64 | public void checkLatestVersion() throws MalformedURLException, IOException { 65 | Gson g = new Gson(); 66 | InputStream stream = new URL("https://api.github.com/repos/Delta2Force/redditbrowser/releases/latest").openStream(); 67 | InputStreamReader reader = new InputStreamReader(stream); 68 | GithubAPIResponse gar = g.fromJson(reader, GithubAPIResponse.class); 69 | reader.close(); 70 | stream.close(); 71 | 72 | if (!this.getDescription().getVersion().contains(gar.tag_name)) { 73 | this.getServer().broadcastMessage(ChatColor.LIGHT_PURPLE + "Your version of RedditBrowser is outdated! The newest version is: " + gar.tag_name + ". You can download it from: " + gar.html_url); 74 | } 75 | } 76 | 77 | @Override 78 | public void onDisable() { 79 | beforeTPLocation.clear(); 80 | listener = null; 81 | roomMap.values().forEach(room -> { 82 | room.destroy(); 83 | roomMap.remove(room.getRoomId()); 84 | }); 85 | } 86 | 87 | public void attemptConnect() { 88 | Client client = getClient(); 89 | Credentials oauthCreds = Credentials.script(client.getUsername(), client.getPassword(), client.getClientId(), client.getClientSecret()); 90 | UserAgent userAgent = new UserAgent("bot", "reddit.minecraft.browser", this.getDescription().getVersion(), client.getUsername()); 91 | redditClient = OAuthHelper.automatic(new OkHttpNetworkAdapter(userAgent), oauthCreds); 92 | redditClient.setAutoRenew(true); 93 | } 94 | 95 | private Client getClient() { 96 | return new Client(this); 97 | } 98 | 99 | @Override 100 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 101 | if (sender instanceof Player) { 102 | Player player = (Player) sender; 103 | if (command.getName().equals("reddit")) { 104 | if (args != null && args.length > 0 && StringUtils.isNotBlank(args[0])) { 105 | final Room playerRoom = roomMap.getOrDefault(player.getUniqueId(), null); 106 | if (playerRoom == null) { 107 | if (redditClient == null) { 108 | attemptConnect(); 109 | } 110 | storePreLocation(player); 111 | setupReddit(player); 112 | buildRoom(player, args[0]); 113 | } else { 114 | playerRoom.updateSubreddit(args[0]); 115 | } 116 | } else { 117 | player.sendMessage("Please provide a subreddit."); 118 | } 119 | } else if ("reddit-join".equalsIgnoreCase(command.getName())) { 120 | if (args != null && args.length > 0 && StringUtils.isNotBlank(args[0])) { 121 | String possiblePlayerName = args[0]; 122 | Player targetPlayer = this.getServer().getPlayer(possiblePlayerName); 123 | if (targetPlayer != null) { 124 | if (!Objects.equals(targetPlayer.getUniqueId(), player.getUniqueId())) { 125 | if (roomMap.containsKey(targetPlayer.getUniqueId())) { 126 | storePreLocation(player); 127 | setupReddit(player); 128 | final Room room = roomMap.get(targetPlayer.getUniqueId()); 129 | room.addPlayer(player); 130 | } else { 131 | sender.sendMessage("Player " + args[0] + " is not owner of a reddit room!"); 132 | } 133 | } else { 134 | sender.sendMessage("You cannot join yourself"); 135 | } 136 | } else { 137 | sender.sendMessage("Player " + args[0] + " does not exist!"); 138 | } 139 | } 140 | } 141 | } 142 | return true; 143 | } 144 | 145 | private void storePreLocation(Player player) { 146 | beforeTPLocation.put(player.getUniqueId(), player.getLocation()); 147 | beforeTPExperience.put(player.getUniqueId(), player.getTotalExperience()); 148 | } 149 | 150 | public void setupReddit(Player player) { 151 | player.sendMessage(ChatColor.YELLOW + "Please wait while I setup Reddit..."); 152 | setKarma(player); 153 | 154 | Bukkit.getScheduler().runTaskAsynchronously(this, new Runnable() { 155 | @Override 156 | public void run() { 157 | setKarma(player); 158 | } 159 | }); 160 | } 161 | 162 | public static String colorCode(String color) { 163 | return (char) (0xfeff00a7) + color; 164 | } 165 | 166 | public void setKarma(Player p) { 167 | p.setTotalExperience(0); 168 | int karma = 0; 169 | for (KarmaBySubreddit kbs : redditClient.me().karma()) { 170 | karma += kbs.getLinkKarma(); 171 | karma += kbs.getCommentKarma(); 172 | } 173 | p.setLevel(karma); 174 | } 175 | 176 | private World createWorld() { 177 | WorldCreator wc = new WorldCreator("reddit"); 178 | wc.generator(new RedditGenerator()); 179 | wc.generateStructures(false); 180 | World world = Bukkit.createWorld(wc); 181 | world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); 182 | world.setGameRule(GameRule.DO_WEATHER_CYCLE, false); 183 | world.setGameRule(GameRule.DO_MOB_SPAWNING, false); 184 | world.setTime(1400); 185 | return world; 186 | } 187 | 188 | public void buildRoom(Player player, String subReddit) { 189 | Client client = getClient(); 190 | World world = createWorld(); 191 | RoomDimensions roomDimensions = createRoomDimensions(client); 192 | Random r = new Random(); 193 | Location location = new Location(world, r.nextInt(2000000) - 1000000, 255, r.nextInt(2000000) - 1000000); 194 | 195 | Bukkit.getScheduler().runTaskAsynchronously(this, task -> { 196 | final Room room = new Room( 197 | this, 198 | location, 199 | subReddit, 200 | roomDimensions, 201 | player); 202 | roomMap.put(room.getRoomId(), room); 203 | room.build(Collections.singletonList(player)); 204 | }); 205 | } 206 | 207 | 208 | @NotNull 209 | private RoomDimensions createRoomDimensions(Client client) { 210 | int screenWidth = client.getScreenWidth(); 211 | if (screenWidth < 1) { 212 | screenWidth = 1; 213 | } 214 | int screenHeight = client.getScreenHeight(); 215 | if (screenHeight < 1) { 216 | screenHeight = 1; 217 | } 218 | int roomDepth = client.getRoomDepth(); 219 | if (roomDepth < 5) { 220 | roomDepth = 5; 221 | } 222 | int roomHeight = screenHeight > 1 ? screenHeight + 3 : screenHeight + 3; 223 | int roomWidth = screenWidth >= 3 ? screenWidth + 3 : screenWidth + 2; 224 | if (roomWidth < 5) { 225 | roomWidth = 5; 226 | } 227 | return new RoomDimensions( 228 | roomWidth, 229 | roomHeight, 230 | roomDepth, 231 | screenWidth, 232 | screenHeight 233 | ); 234 | } 235 | 236 | public void kickOut(Player player) { 237 | player.sendMessage(ChatColor.GREEN + "Goodbye reddit!"); 238 | final UUID uniqueId = player.getUniqueId(); 239 | player.teleport(beforeTPLocation.get(uniqueId)); 240 | removeCommentsFromPlayerInventory(player); 241 | player.setTotalExperience(beforeTPExperience.get(uniqueId)); 242 | 243 | beforeTPLocation.remove(uniqueId); 244 | beforeTPExperience.remove(uniqueId); 245 | 246 | if (roomMap.containsKey(uniqueId)) { 247 | final Room room = roomMap.get(uniqueId); 248 | room.destroy(); 249 | roomMap.remove(uniqueId); 250 | } 251 | } 252 | 253 | public static void removeCommentsFromPlayerInventory(Player player) { 254 | for (ItemStack item : player.getInventory().getContents()) 255 | { 256 | try { 257 | if (Material.WRITABLE_BOOK.equals(item.getType()) || 258 | Material.WRITTEN_BOOK.equals(item.getType())) { 259 | if(Objects.equals(COMMENT_DISPLAY_NAME, item.getItemMeta().getDisplayName())) { 260 | item.setAmount(0); 261 | } 262 | } 263 | } catch (Exception e) {} 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/generator/RedditGenerator.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.generator; 2 | 3 | import org.bukkit.Location; 4 | import org.bukkit.World; 5 | import org.bukkit.block.Biome; 6 | import org.bukkit.generator.BlockPopulator; 7 | import org.bukkit.generator.ChunkGenerator; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Random; 12 | 13 | public class RedditGenerator extends ChunkGenerator { 14 | 15 | @Override 16 | public List getDefaultPopulators(World world) { 17 | return Collections.emptyList(); 18 | } 19 | 20 | @Override 21 | public ChunkData generateChunkData(World world, Random random, int chunkX, int chunkZ, BiomeGrid biome) { 22 | ChunkData chunkData = super.createChunkData(world); 23 | 24 | // Set biome. 25 | for (int x = 0; x < 16; x++) { 26 | for (int z = 0; z < 16; z++) { 27 | biome.setBiome(x, z, Biome.PLAINS); 28 | } 29 | } 30 | 31 | // Return the new chunk data. 32 | return chunkData; 33 | } 34 | 35 | @Override 36 | public boolean canSpawn(World world, int x, int z) { 37 | return true; 38 | } 39 | 40 | @Override 41 | public Location getFixedSpawnLocation(World world, Random random) { 42 | return new Location(world, 0, 100, 0); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/interaction/InteractiveEnum.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.interaction; 2 | 3 | public enum InteractiveEnum { 4 | UPVOTE, DOWNVOTE, COMMENT_CHEST, PREVIOUS_ROOM, NEXT_ROOM, LOAD_COMMENTS, REFRESH, LEAVE, WRITE_COMMENT, SHOW_URL; 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/inventory/RedditInventory.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.inventory; 2 | 3 | import org.bukkit.entity.Player; 4 | import org.bukkit.inventory.ItemStack; 5 | import org.bukkit.inventory.PlayerInventory; 6 | 7 | public class RedditInventory { 8 | final ItemStack[] contents; 9 | final ItemStack[] armorContents; 10 | final ItemStack[] storageContents; 11 | final ItemStack[] extraContents; 12 | final ItemStack itemInOffHand; 13 | 14 | public RedditInventory(PlayerInventory inv) { 15 | contents = inv.getContents(); 16 | armorContents = inv.getArmorContents(); 17 | storageContents = inv.getStorageContents(); 18 | extraContents = inv.getExtraContents(); 19 | itemInOffHand = inv.getItemInOffHand(); 20 | } 21 | 22 | public void apply(Player p) { 23 | p.getInventory().setArmorContents(armorContents); 24 | p.getInventory().setContents(contents); 25 | p.getInventory().setStorageContents(storageContents); 26 | p.getInventory().setExtraContents(extraContents); 27 | p.getInventory().setItemInOffHand(itemInOffHand); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/listeners/EventListener.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.listeners; 2 | 3 | import me.delta2force.redditbrowser.RedditBrowserPlugin; 4 | import me.delta2force.redditbrowser.interaction.InteractiveEnum; 5 | import me.delta2force.redditbrowser.renderer.RedditRenderer; 6 | import me.delta2force.redditbrowser.room.Room; 7 | import net.dean.jraw.models.Comment; 8 | import net.dean.jraw.tree.CommentNode; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.UUID; 14 | 15 | import org.bukkit.*; 16 | import org.bukkit.block.Chest; 17 | import org.bukkit.entity.ItemFrame; 18 | import org.bukkit.entity.Player; 19 | import org.bukkit.event.EventHandler; 20 | import org.bukkit.event.Listener; 21 | import org.bukkit.event.block.Action; 22 | import org.bukkit.event.block.BlockBreakEvent; 23 | import org.bukkit.event.block.BlockPlaceEvent; 24 | import org.bukkit.event.hanging.HangingBreakEvent; 25 | import org.bukkit.event.inventory.InventoryClickEvent; 26 | import org.bukkit.event.inventory.InventoryCloseEvent; 27 | import org.bukkit.event.inventory.InventoryType; 28 | import org.bukkit.event.player.PlayerInteractAtEntityEvent; 29 | import org.bukkit.event.player.PlayerInteractEvent; 30 | import org.bukkit.event.player.PlayerQuitEvent; 31 | import org.bukkit.inventory.Inventory; 32 | import org.bukkit.inventory.ItemStack; 33 | import org.bukkit.inventory.meta.BookMeta; 34 | import org.bukkit.inventory.meta.MapMeta; 35 | import org.bukkit.map.MapRenderer; 36 | 37 | import org.bukkit.metadata.FixedMetadataValue; 38 | import org.bukkit.metadata.MetadataValue; 39 | 40 | import static me.delta2force.redditbrowser.room.Room.createWritableBookStack; 41 | 42 | public class EventListener implements Listener { 43 | 44 | private static final float VOLUME = 75f; 45 | private RedditBrowserPlugin reddit; 46 | 47 | public EventListener(RedditBrowserPlugin reddit) { 48 | this.reddit = reddit; 49 | } 50 | 51 | 52 | @EventHandler 53 | public void interact(PlayerInteractEvent event) { 54 | if (event.getClickedBlock() != null 55 | && Action.RIGHT_CLICK_BLOCK.equals(event.getAction()) 56 | && event.getClickedBlock().hasMetadata(RedditBrowserPlugin.INTERACTIVE_ENUM)) { 57 | List metadata = event.getClickedBlock().getMetadata(RedditBrowserPlugin.INTERACTIVE_ENUM); 58 | if (metadataContains(metadata, InteractiveEnum.UPVOTE)) { 59 | String submissionID = event.getClickedBlock().getMetadata(RedditBrowserPlugin.SUBMISSION_ID).get(0).asString(); 60 | Bukkit.getScheduler().runTaskAsynchronously(reddit, new Runnable() { 61 | @Override 62 | public void run() { 63 | event.getPlayer().playSound(event.getClickedBlock().getLocation(), Sound.ENTITY_VILLAGER_YES, VOLUME, 1); 64 | reddit.redditClient.submission(submissionID).upvote(); 65 | int karma = reddit.redditClient.submission(submissionID).inspect().getScore(); 66 | event.getPlayer().sendMessage(ChatColor.GREEN + "You have upvoted the post! It now has " + karma + " karma."); 67 | } 68 | }); 69 | } else if (metadataContains(metadata, InteractiveEnum.DOWNVOTE)) { 70 | String submissionID = event.getClickedBlock().getMetadata(RedditBrowserPlugin.SUBMISSION_ID).get(0).asString(); 71 | Bukkit.getScheduler().runTaskAsynchronously(reddit, new Runnable() { 72 | @Override 73 | public void run() { 74 | event.getPlayer().playSound(event.getClickedBlock().getLocation(), Sound.ENTITY_VILLAGER_NO, VOLUME, 1); 75 | reddit.redditClient.submission(submissionID).downvote(); 76 | int karma = reddit.redditClient.submission(submissionID).inspect().getScore(); 77 | event.getPlayer().sendMessage(ChatColor.RED + "You have downvoted the post! It now has " + karma + " karma."); 78 | } 79 | }); 80 | } else if (metadataContains(metadata, InteractiveEnum.NEXT_ROOM)) { 81 | if(!event.getClickedBlock().getMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED).get(0).asBoolean()) { 82 | event.getClickedBlock().setMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED, new FixedMetadataValue(reddit, true)); 83 | UUID roomId = (UUID) event.getClickedBlock().getMetadata(RedditBrowserPlugin.ROOM_ID).get(0).value(); 84 | if (reddit.roomMap.containsKey(roomId)) { 85 | final Room room = reddit.roomMap.get(roomId); 86 | room.nextPost(); 87 | } else { 88 | event.getPlayer().sendMessage(ChatColor.RED + "Room not found!"); 89 | } 90 | } 91 | } else if (metadataContains(metadata, InteractiveEnum.PREVIOUS_ROOM)) { 92 | if(!event.getClickedBlock().getMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED).get(0).asBoolean()) { 93 | UUID roomId = (UUID) event.getClickedBlock().getMetadata(RedditBrowserPlugin.ROOM_ID).get(0).value(); 94 | event.getClickedBlock().setMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED, new FixedMetadataValue(reddit, true)); 95 | if (reddit.roomMap.containsKey(roomId)) { 96 | final Room room = reddit.roomMap.get(roomId); 97 | room.previousPost(); 98 | } else { 99 | event.getPlayer().sendMessage(ChatColor.RED + "Room not found!"); 100 | } 101 | } 102 | } else if (metadataContains(metadata, InteractiveEnum.LOAD_COMMENTS)) { 103 | if(!event.getClickedBlock().getMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED).get(0).asBoolean()) { 104 | UUID roomId = (UUID) event.getClickedBlock().getMetadata(RedditBrowserPlugin.ROOM_ID).get(0).value(); 105 | event.getClickedBlock().setMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED, new FixedMetadataValue(reddit, true)); 106 | if (reddit.roomMap.containsKey(roomId)) { 107 | final Room room = reddit.roomMap.get(roomId); 108 | room.displayComments(); 109 | } else { 110 | event.getPlayer().sendMessage(ChatColor.RED + "Room not found!"); 111 | } 112 | } 113 | } else if (metadataContains(metadata, InteractiveEnum.REFRESH)) { 114 | if(!event.getClickedBlock().getMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED).get(0).asBoolean()) { 115 | UUID roomId = (UUID) event.getClickedBlock().getMetadata(RedditBrowserPlugin.ROOM_ID).get(0).value(); 116 | event.getClickedBlock().setMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED, new FixedMetadataValue(reddit, true)); 117 | if (reddit.roomMap.containsKey(roomId)) { 118 | final Room room = reddit.roomMap.get(roomId); 119 | room.refresh(); 120 | } else { 121 | event.getPlayer().sendMessage(ChatColor.RED + "Room not found!"); 122 | } 123 | } 124 | } else if (metadataContains(metadata, InteractiveEnum.LEAVE)) { 125 | if(!event.getClickedBlock().getMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED).get(0).asBoolean()) { 126 | event.getClickedBlock().setMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED, new FixedMetadataValue(reddit, true)); 127 | reddit.kickOut(event.getPlayer()); 128 | } 129 | } else if (metadataContains(metadata, InteractiveEnum.WRITE_COMMENT)) { 130 | if(!event.getClickedBlock().getMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED).get(0).asBoolean()) { 131 | event.getClickedBlock().setMetadata(RedditBrowserPlugin.BUTTON_ACTIVATED, new FixedMetadataValue(reddit, true)); 132 | final ItemStack writableBookStack = createWritableBookStack(); 133 | event.getPlayer().getInventory().setItemInMainHand(writableBookStack); 134 | } 135 | } else if (metadataContains(metadata, InteractiveEnum.SHOW_URL)) { 136 | UUID roomId = (UUID) event.getClickedBlock().getMetadata(RedditBrowserPlugin.ROOM_ID).get(0).value(); 137 | if (reddit.roomMap.containsKey(roomId)) { 138 | final Room room = reddit.roomMap.get(roomId); 139 | room.showURLtoPlayers(event.getPlayer()); 140 | } else { 141 | event.getPlayer().sendMessage(ChatColor.RED + "Room not found!"); 142 | } 143 | event.setCancelled(true); 144 | } 145 | } 146 | } 147 | 148 | @EventHandler 149 | public void interactInventory(InventoryClickEvent event) { 150 | Player p = (Player) event.getWhoClicked(); 151 | 152 | if(event.getCurrentItem() != null && 153 | event.getCurrentItem().getType().equals(Material.WRITTEN_BOOK) 154 | && event.getInventory().getType().equals(InventoryType.CHEST) && event.isRightClick()) { 155 | if(!event.getView().getTitle().startsWith("Comment ")) { 156 | if(reddit.roomMap.values() 157 | .stream() 158 | .noneMatch(o->o.hasPlayer(p.getUniqueId()))){ 159 | return ; 160 | } 161 | } 162 | String commentID = event.getCurrentItem().getItemMeta().getLore().get(0); 163 | List> replies = reddit.commentCache.get(commentID).getReplies(); 164 | Inventory commentInventory = reddit.getServer().createInventory(event.getClickedInventory().getHolder(), InventoryType.CHEST, "Comment " + commentID); 165 | int in = 0; 166 | for (CommentNode cn : replies) { 167 | Comment c = cn.getSubject(); 168 | if (in < 26) { 169 | ItemStack book = new ItemStack(Material.WRITTEN_BOOK); 170 | BookMeta bookmeta = (BookMeta) book.getItemMeta(); 171 | bookmeta.setTitle("Comment"); 172 | bookmeta.setDisplayName(Room.COMMENT_DISPLAY_NAME); 173 | bookmeta.setAuthor("u/" + c.getAuthor()); 174 | if (c.getBody().length() > 255) { 175 | double f = Math.ceil(((float) c.getBody().length()) / 255f); 176 | for (int i = 0; i < f; i++) { 177 | if (c.getBody().length() < (i + 1) * 255) { 178 | bookmeta.addPage(c.getBody().substring(i * 255)); 179 | } else { 180 | bookmeta.addPage(c.getBody().substring(i * 255, (i + 1) * 255)); 181 | } 182 | } 183 | } else { 184 | bookmeta.addPage(c.getBody()); 185 | } 186 | bookmeta.setLore(Arrays.asList(c.getId(), c.getBody())); 187 | book.setItemMeta(bookmeta); 188 | reddit.commentCache.put(c.getId(), cn); 189 | commentInventory.addItem(book); 190 | } else { 191 | break; 192 | } 193 | in++; 194 | } 195 | p.openInventory(commentInventory); 196 | } 197 | } 198 | 199 | 200 | @EventHandler 201 | public void closeInventory(InventoryCloseEvent event) { 202 | // redditClient.setKarma((Player) event.getPlayer()); Overloading the server 203 | 204 | if (event.getView().getTitle().startsWith("Comment ")) { 205 | String commentID = event.getView().getTitle().split(" ")[1]; 206 | for (ItemStack is : event.getInventory().getContents()) { 207 | if (is != null) { 208 | if (is.getType() == Material.WRITTEN_BOOK) { 209 | BookMeta bm = (BookMeta) is.getItemMeta(); 210 | if (bm.getAuthor().equals(event.getPlayer().getName())) { 211 | String comment = ""; 212 | for (String page : bm.getPages()) { 213 | comment += page + " "; 214 | } 215 | reddit.commentCache.get(commentID).getSubject().toReference(reddit.redditClient).reply(comment); 216 | event.getPlayer().sendMessage(ChatColor.GREEN + "You have left a comment on a comment!"); 217 | event.getPlayer().getInventory().addItem(createWritableBookStack()); 218 | event.getInventory().remove(is); 219 | } 220 | } 221 | } 222 | } 223 | } else if (event.getInventory().getHolder() instanceof Chest) { 224 | Chest chest = (Chest) event.getInventory().getHolder(); 225 | if (chest.hasMetadata(RedditBrowserPlugin.INTERACTIVE_ENUM)) { 226 | List metadata = chest.getMetadata(RedditBrowserPlugin.INTERACTIVE_ENUM); 227 | if (metadataContains(metadata, InteractiveEnum.COMMENT_CHEST)) { 228 | String submissionID = chest.getMetadata(RedditBrowserPlugin.SUBMISSION_ID).get(0).asString(); 229 | for (ItemStack is : event.getInventory().getContents()) { 230 | if (is != null) { 231 | if (is.getType() == Material.WRITTEN_BOOK) { 232 | BookMeta bm = (BookMeta) is.getItemMeta(); 233 | if (bm.getAuthor().equals(event.getPlayer().getName())) { 234 | String comment = ""; 235 | for (String page : bm.getPages()) { 236 | comment += page + " "; 237 | } 238 | reddit.redditClient.submission(submissionID).reply(comment); 239 | event.getPlayer().sendMessage(ChatColor.GREEN + "You have left a comment!"); 240 | event.getPlayer().getInventory().addItem(createWritableBookStack()); 241 | event.getInventory().remove(is); 242 | } 243 | } 244 | } 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | @EventHandler 252 | public void onLeave(PlayerQuitEvent event) { 253 | if(reddit.roomMap.values().stream() 254 | .anyMatch(o->o.hasPlayer(event.getPlayer().getUniqueId()))) { 255 | Player player = event.getPlayer(); 256 | reddit.kickOut(player); 257 | } 258 | } 259 | 260 | @EventHandler 261 | public void onBlockBreak(BlockBreakEvent blockBreakEvent) { 262 | final Location location = blockBreakEvent.getBlock().getLocation(); 263 | if(reddit.roomMap.values().stream().anyMatch(o->o.isInside(location))) { 264 | blockBreakEvent.setCancelled(true); 265 | } 266 | } 267 | 268 | @EventHandler 269 | public void onBlockPlace(BlockPlaceEvent blockPlaceEvent) { 270 | final Location location = blockPlaceEvent.getBlock().getLocation(); 271 | if(reddit.roomMap.values().stream().anyMatch(o->o.isInside(location))) { 272 | blockPlaceEvent.setCancelled(true); 273 | } 274 | } 275 | 276 | @EventHandler 277 | public void onBlockPlace(HangingBreakEvent hangingBreakEvent) { 278 | final Location location = hangingBreakEvent.getEntity().getLocation(); 279 | if(reddit.roomMap.values().stream().anyMatch(o->o.isInside(location))) { 280 | hangingBreakEvent.setCancelled(true); 281 | } 282 | } 283 | 284 | private static boolean metadataContains(List values, Object value) { 285 | if (values != null && !values.isEmpty()) { 286 | return values.stream().anyMatch(o -> Objects.equals(o.value(), value)); 287 | } 288 | return false; 289 | } 290 | 291 | } 292 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/renderer/RedditRenderer.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.renderer; 2 | 3 | import java.awt.image.BufferedImage; 4 | import java.io.IOException; 5 | import java.net.MalformedURLException; 6 | import java.net.URL; 7 | 8 | import javax.imageio.ImageIO; 9 | 10 | import org.bukkit.entity.Player; 11 | import org.bukkit.map.MapCanvas; 12 | import org.bukkit.map.MapRenderer; 13 | import org.bukkit.map.MapView; 14 | 15 | import me.delta2force.redditbrowser.RedditBrowserPlugin; 16 | 17 | public class RedditRenderer extends MapRenderer{ 18 | public BufferedImage image; 19 | public String url; 20 | private boolean drawn; 21 | 22 | public void setImage(String url, BufferedImage image) { 23 | this.url = url; 24 | this.drawn = false; 25 | this.image = image; 26 | } 27 | 28 | @Override 29 | public void render(MapView mv, MapCanvas mc, Player p) { 30 | if(image != null) { 31 | if(!drawn) { //Don't redraw every time because the server calls this too much 32 | //this will need to track for which player it was drawn I guess 33 | mc.drawImage(0, 0, image); 34 | drawn = true; 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/renderer/TiledRenderer.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.renderer; 2 | 3 | import me.delta2force.redditbrowser.RedditBrowserPlugin; 4 | import org.bukkit.map.MapRenderer; 5 | 6 | import java.awt.*; 7 | import java.awt.image.BufferedImage; 8 | 9 | public class TiledRenderer { 10 | private static final int IMAGE_SIZE = 128; 11 | private final RedditRenderer[][] tiles; 12 | private String url; 13 | private final RedditBrowserPlugin redditBrowserPlugin; 14 | private final int tileWidth; 15 | private final int tileHeight; 16 | 17 | public TiledRenderer( 18 | RedditBrowserPlugin redditBrowserPlugin, 19 | int tileWidth, 20 | int tileHeight) { 21 | this.tileWidth = tileWidth; 22 | this.tileHeight = tileHeight; 23 | 24 | this.redditBrowserPlugin = redditBrowserPlugin; 25 | tiles = new RedditRenderer[tileHeight][tileWidth]; 26 | for (int row = 0; row < tileHeight; row++) { 27 | for (int col = 0; col < tileWidth; col++) { 28 | tiles[row][col] = new RedditRenderer(); 29 | } 30 | } 31 | } 32 | 33 | public void updateImage(String originalURL, BufferedImage originalImage) { 34 | url = originalURL; 35 | if (originalImage != null) { 36 | BufferedImage image = new BufferedImage(IMAGE_SIZE * tileWidth, IMAGE_SIZE * tileHeight, BufferedImage.TYPE_INT_ARGB); 37 | image.createGraphics().drawImage(originalImage, 0, 0, IMAGE_SIZE * tileWidth, IMAGE_SIZE * tileHeight, null); 38 | BufferedImage[][] bufferedImages = splitImage(image, tileHeight, tileWidth); 39 | for (int row = 0; row < tileHeight; row++) { 40 | for (int col = 0; col < tileWidth; col++) { 41 | tiles[row][col].setImage(originalURL, bufferedImages[row][col]); 42 | } 43 | } 44 | } else { 45 | BufferedImage fallbackImage = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, BufferedImage.TYPE_BYTE_GRAY); 46 | for (int row = 0; row < tileHeight; row++) { 47 | for (int col = 0; col < tileWidth; col++) { 48 | tiles[row][col].setImage(originalURL, fallbackImage); 49 | } 50 | } 51 | } 52 | } 53 | 54 | private BufferedImage[][] splitImage(BufferedImage image, int rows, int cols) { 55 | BufferedImage[][] imgs = new BufferedImage[rows][cols]; //Image array to hold image chunks 56 | for (int col = 0; col < cols; col++) { 57 | for (int row = 0; row < rows; row++) { 58 | imgs[row][col] = new BufferedImage(IMAGE_SIZE, IMAGE_SIZE, image.getType()); 59 | 60 | Graphics2D gr = imgs[row][col].createGraphics(); 61 | gr.drawImage(image, 0, 0, 62 | IMAGE_SIZE, 63 | IMAGE_SIZE, 64 | IMAGE_SIZE * col, 65 | IMAGE_SIZE * row, 66 | IMAGE_SIZE * (col + 1), 67 | IMAGE_SIZE * (row + 1), 68 | null); 69 | gr.dispose(); 70 | } 71 | } 72 | return imgs; 73 | } 74 | 75 | public MapRenderer getRenderer(int row, int col) { 76 | return tiles[row][col]; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/repository/URLOptimizer.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.repository; 2 | 3 | import org.w3c.dom.Document; 4 | import org.w3c.dom.NodeList; 5 | 6 | import javax.xml.parsers.DocumentBuilder; 7 | import javax.xml.parsers.DocumentBuilderFactory; 8 | import java.io.InputStream; 9 | import java.net.URL; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Convert the URL to an image 15 | * 16 | * Converts youtube videos to the thumbnail 17 | * Converts indirect imgur links to direct links to the image 18 | * 19 | */ 20 | 21 | class URLOptimizer { 22 | private static final Pattern IMGUR_PATTERN = Pattern.compile("https://imgur\\.com/(.*)"); 23 | private static final Pattern VIMEO = Pattern.compile("https://vimeo.com/(\\d*).*"); 24 | private static final Pattern YOUTUBE_PATTERN = Pattern.compile("^.*((youtu.be\\/)|(v\\/)|(\\/u\\/\\w\\/)|(embed\\/)|(watch\\?))\\??v?=?([^#\\&\\?]*).*"); 25 | 26 | static String optimizeURL(String url) { 27 | String optimizedURL = replaceImgUr(url); 28 | optimizedURL = replaceYoutubeVideoWithThumbNail(optimizedURL); 29 | optimizedURL = replaceVimeoVideoWithThumbnail(optimizedURL); 30 | 31 | return optimizedURL; 32 | } 33 | 34 | static String replaceImgUr(String url) { 35 | Matcher matcher = IMGUR_PATTERN.matcher(url); 36 | if(matcher.matches()) { 37 | String group = matcher.group(1); 38 | return "https://i.imgur.com/" + group+ ".jpg"; 39 | } 40 | return url; 41 | } 42 | 43 | static String replaceYoutubeVideoWithThumbNail(String url) { 44 | Matcher matcher = YOUTUBE_PATTERN.matcher(url); 45 | if(matcher.matches()) { 46 | String group = matcher.group(7); 47 | return "https://img.youtube.com/vi/" + group + "/hqdefault.jpg"; 48 | } 49 | return url; 50 | } 51 | 52 | static String replaceVimeoVideoWithThumbnail(String url) { 53 | Matcher matcher = VIMEO.matcher(url); 54 | if(matcher.matches()) { 55 | String videoId = matcher.group(1); 56 | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory 57 | .newInstance(); 58 | DocumentBuilder documentBuilder = null; 59 | try { 60 | documentBuilder = documentBuilderFactory.newDocumentBuilder(); 61 | URL xmlURL = new URL("https://vimeo.com/api/v2/video/" + videoId + ".xml"); 62 | InputStream xml = xmlURL.openStream(); 63 | 64 | Document document = documentBuilder.parse(xml); 65 | 66 | final NodeList thumbnail_large = document.getElementsByTagName("thumbnail_large"); 67 | if(thumbnail_large.getLength() > 0 ) { 68 | return thumbnail_large.item(0).getTextContent(); 69 | } 70 | } catch (Exception e) { 71 | //Not intested in failures 72 | } 73 | } 74 | return url; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/repository/URLToImageRepository.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.repository; 2 | 3 | import net.dean.jraw.models.Submission; 4 | import org.bukkit.Bukkit; 5 | 6 | import javax.imageio.ImageIO; 7 | import java.awt.image.BufferedImage; 8 | import java.io.IOException; 9 | import java.net.URL; 10 | import java.util.logging.Level; 11 | 12 | import static me.delta2force.redditbrowser.repository.URLOptimizer.optimizeURL; 13 | 14 | public class URLToImageRepository { 15 | 16 | public static BufferedImage findImage(Submission submission) { 17 | final String url = findImageOrThumbnailUrl(submission); 18 | BufferedImage bufferedImage = null; 19 | try { 20 | bufferedImage = ImageIO.read(new URL(url)); 21 | } catch (IOException e) { 22 | Bukkit.getLogger().log(Level.INFO, "Could not retrieve '" + url + "' as an image. This could be a gif which is not supported or the url isn't a direct link to the image"); 23 | } 24 | return bufferedImage; 25 | } 26 | 27 | private static String findImageOrThumbnailUrl(Submission submission) { 28 | if ("image".equalsIgnoreCase(submission.getPostHint()) 29 | && !submission.getUrl().endsWith(".gif")) { 30 | return submission.getUrl(); 31 | } else if (submission.getPreview() != null 32 | && submission.getPreview().getImages() != null 33 | && !submission.getPreview().getImages().isEmpty() 34 | && submission.getPreview().getImages().get(0).getSource() != null 35 | && submission.getPreview().getImages().get(0).getSource().getUrl() != null) { 36 | return submission.getPreview().getImages().get(0).getSource().getUrl(); 37 | } else if (submission.getThumbnail() != null) { 38 | return submission.getThumbnail(); 39 | } else { 40 | return optimizeURL(submission.getUrl()); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/room/RedditQueue.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.room; 2 | 3 | import net.dean.jraw.RedditClient; 4 | import net.dean.jraw.models.Listing; 5 | import net.dean.jraw.models.Submission; 6 | import net.dean.jraw.models.SubredditSort; 7 | import net.dean.jraw.pagination.DefaultPaginator; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.stream.Collectors; 12 | 13 | public class RedditQueue { 14 | public static final int PAGING_SIZE = 25; 15 | private RedditClient redditClient; 16 | private final String subReddit; 17 | private final List history; 18 | private DefaultPaginator defaultPaginator; 19 | private int index = -1; 20 | 21 | public RedditQueue( 22 | RedditClient redditClient, 23 | String subReddit) { 24 | this.redditClient = redditClient; 25 | this.subReddit = subReddit; 26 | this.history = new ArrayList<>(); 27 | createRedditPaginator(); 28 | } 29 | 30 | /** 31 | * Using a the paginator instead of the Stream because the stream does weird things if the subreddit doesn't exist 32 | */ 33 | private void createRedditPaginator() { 34 | if("FRONTPAGE".equalsIgnoreCase(subReddit)) { 35 | defaultPaginator = redditClient 36 | .frontPage() 37 | .limit(PAGING_SIZE) 38 | .build(); 39 | } else { 40 | defaultPaginator = redditClient 41 | .subreddit(subReddit) 42 | .posts() 43 | .sorting(SubredditSort.HOT) 44 | .limit(PAGING_SIZE) 45 | .build(); 46 | } 47 | 48 | } 49 | 50 | 51 | Submission next() { 52 | if (index + 1 >= history.size()) { 53 | if(loadNextPage()) { 54 | return next(); 55 | } else { 56 | return null; 57 | } 58 | } else { 59 | final String submissionId = history.get(index + 1); 60 | final Submission submission = redditClient.submission(submissionId).inspect(); 61 | index++; 62 | return submission; 63 | } 64 | } 65 | 66 | private boolean loadNextPage() { 67 | final Listing next = defaultPaginator.next(); 68 | if (next != null) { 69 | final List children = next.getChildren(); 70 | if (children != null && !children.isEmpty()) { 71 | history.addAll(children.stream() 72 | .map(Submission::getId) 73 | .collect(Collectors.toList())); 74 | return true; 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | Submission previous() { 81 | if (hasPrevious()) { 82 | final String submissionId = history.get(index - 1); 83 | final Submission submission = redditClient.submission(submissionId).inspect(); 84 | index--; 85 | return submission; 86 | } 87 | return null; 88 | } 89 | 90 | boolean hasPrevious() { 91 | return index > 0; 92 | } 93 | 94 | void reset() { 95 | index = -1; 96 | history.clear(); 97 | createRedditPaginator(); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/room/Room.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.room; 2 | 3 | import me.delta2force.redditbrowser.RedditBrowserPlugin; 4 | import me.delta2force.redditbrowser.interaction.InteractiveEnum; 5 | import net.dean.jraw.models.Comment; 6 | import net.dean.jraw.models.Submission; 7 | import net.dean.jraw.tree.CommentNode; 8 | import net.dean.jraw.tree.RootCommentNode; 9 | import org.bukkit.*; 10 | import org.bukkit.block.Block; 11 | import org.bukkit.block.BlockFace; 12 | import org.bukkit.block.Chest; 13 | import org.bukkit.block.data.Directional; 14 | import org.bukkit.entity.*; 15 | import org.bukkit.inventory.ItemStack; 16 | import org.bukkit.inventory.meta.BookMeta; 17 | import org.bukkit.inventory.meta.ItemMeta; 18 | import org.bukkit.metadata.FixedMetadataValue; 19 | 20 | import java.util.*; 21 | import java.util.logging.Level; 22 | import java.util.stream.Collectors; 23 | 24 | import static java.lang.Math.ceil; 25 | import static me.delta2force.redditbrowser.RedditBrowserPlugin.*; 26 | 27 | public class Room { 28 | private static final String PREVIOUS_HOLOGRAM_NAME = colorCode("9") + "Previous"; 29 | private static final Material ROOM_MATERIAL = Material.WHITE_WOOL; 30 | private static final String COMMENTS_HOLOGRAM = colorCode("9") + "Comments"; 31 | private static final String WRITE_COMMENT_HOLOGRAM = colorCode("9") + "Write comment"; 32 | public static final String COMMENT_DISPLAY_NAME = "Comment"; 33 | public static final String NEWLINE = "\n"; 34 | private final RedditBrowserPlugin redditBrowserPlugin; 35 | private final Player owner; 36 | private final Location location; 37 | private final RoomDimensions roomDimensions; 38 | private RedditQueue redditQueue; 39 | private String subreddit; 40 | private Submission currentSubmission; 41 | private final Screen screen; 42 | 43 | public Room( 44 | RedditBrowserPlugin redditBrowserPlugin, 45 | Location location, 46 | String subreddit, 47 | RoomDimensions roomDimensions, 48 | Player owner) { 49 | this.redditBrowserPlugin = redditBrowserPlugin; 50 | this.owner = owner; 51 | this.location = location; 52 | this.roomDimensions = roomDimensions; 53 | final Location screenLocation = location.clone().add( 54 | -roomDimensions.getRoomWidth() + 2, 55 | -2, 56 | -roomDimensions.getRoomDepth()); 57 | this.screen = new Screen( 58 | this, 59 | screenLocation, 60 | roomDimensions.getScreenWidth(), 61 | roomDimensions.getScreenHeight()); 62 | setSubReddit(subreddit); 63 | } 64 | 65 | public void build(Collection startingPlayers) { 66 | Bukkit.getScheduler().runTaskAsynchronously(redditBrowserPlugin, () -> { 67 | final Submission submission = redditQueue.next(); 68 | if (submission != null) { 69 | currentSubmission = submission; 70 | Bukkit.getScheduler().runTask(redditBrowserPlugin, () -> { 71 | createRoom(submission); 72 | 73 | Bukkit.getScheduler().runTaskLater(redditBrowserPlugin, () -> { 74 | setupPlayers(submission, startingPlayers); 75 | }, 10); 76 | }); 77 | } else { 78 | Bukkit.getScheduler().runTask(redditBrowserPlugin, () -> { 79 | createRoom(null); 80 | Bukkit.getScheduler().runTaskLater(redditBrowserPlugin, () -> { 81 | setupPlayers(submission, startingPlayers); 82 | startingPlayers.forEach(player -> player.sendMessage(ChatColor.RED + "No posts found.")); 83 | 84 | }, 10); 85 | }); 86 | } 87 | }); 88 | } 89 | 90 | public void addPlayer(Player player) { 91 | setupPlayers(currentSubmission, Arrays.asList(player)); 92 | } 93 | 94 | public void refresh() { 95 | redditQueue.reset(); 96 | screen.clean(); 97 | build(getPlayers()); 98 | } 99 | 100 | public void updateSubreddit(String subreddit) { 101 | setSubReddit(subreddit); 102 | build(getPlayers()); 103 | } 104 | 105 | private void setSubReddit(String subreddit) { 106 | this.subreddit = subreddit; 107 | redditQueue = new RedditQueue(redditBrowserPlugin.redditClient, subreddit); 108 | } 109 | 110 | private void setupPlayers(Submission submission, Collection startingPlayers) { 111 | Location loc = location.clone().add(-roomDimensions.getRoomWidth() / 2, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() / 2); 112 | loc.setPitch(0); 113 | loc.setYaw(180); 114 | loc.getChunk().load(); 115 | location.clone().add(-roomDimensions.getRoomWidth(), -roomDimensions.getRoomHeight(), -roomDimensions.getRoomDepth()).getChunk().load(); 116 | startingPlayers.forEach(player -> { 117 | // If it's pewds' subreddit, display LWIAY title 118 | if (subreddit.equalsIgnoreCase("pewdiepiesubmissions")) { 119 | player.sendTitle(colorCode("4") + "L" + colorCode("a") + "W" + colorCode("1") + "I" + colorCode("d") + "A" + colorCode("e") + "Y", "", 10, 70, 20); 120 | } 121 | player.teleport(loc); 122 | player.setGameMode(GameMode.SURVIVAL); 123 | cleanupInventory(); 124 | if (submission != null) { 125 | updateTitleForPlayer(submission, player); 126 | } 127 | }); 128 | } 129 | 130 | public static ItemStack createWritableBookStack() { 131 | final ItemStack itemStack = new ItemStack(Material.WRITABLE_BOOK); 132 | final ItemMeta itemMeta = itemStack.getItemMeta(); 133 | itemMeta.setDisplayName(COMMENT_DISPLAY_NAME); 134 | itemStack.setItemMeta(itemMeta); 135 | return itemStack; 136 | } 137 | 138 | private void updateTitleForRoom(Submission submission) { 139 | getPlayers().forEach(player -> updateTitleForPlayer(submission, player)); 140 | } 141 | 142 | private void updateTitleForPlayer(Submission submission, Player player) { 143 | player.sendMessage( 144 | ChatColor.RESET + NEWLINE + NEWLINE + 145 | ChatColor.DARK_BLUE + 146 | "[" + getPostType(submission) + "] " + 147 | ChatColor.WHITE + submission.getTitle() 148 | + NEWLINE + ChatColor.GREEN + 149 | ChatColor.ITALIC + " by /u/" + submission.getAuthor() + ChatColor.RESET 150 | + NEWLINE + ChatColor.GOLD + "Karma : " + ChatColor.BOLD + submission.getScore() + NEWLINE + ChatColor.RESET); 151 | } 152 | 153 | private static String getPostType(Submission submission) { 154 | if (submission.isSelfPost()) { 155 | return "self"; 156 | } 157 | return submission.getPostHint(); 158 | } 159 | 160 | public void nextPost() { 161 | Bukkit.getScheduler().runTaskAsynchronously(redditBrowserPlugin, new Runnable() { 162 | @Override 163 | public void run() { 164 | final Submission submission = redditQueue.next(); 165 | if (submission != null) { 166 | currentSubmission = submission; 167 | 168 | Bukkit.getScheduler().runTask(redditBrowserPlugin, new Runnable() { 169 | @Override 170 | public void run() { 171 | updateRoom(submission); 172 | updateTitleForRoom(submission); 173 | cleanupInventory(); 174 | } 175 | }); 176 | } else { 177 | getPlayers().forEach(player -> player.sendMessage(ChatColor.RED + "No more posts found.")); 178 | } 179 | } 180 | }); 181 | } 182 | 183 | private void cleanupInventory() { 184 | getPlayers().forEach(RedditBrowserPlugin::removeCommentsFromPlayerInventory); 185 | } 186 | 187 | public void previousPost() { 188 | Bukkit.getScheduler().runTaskAsynchronously(redditBrowserPlugin, () -> { 189 | final Submission submission = redditQueue.previous(); 190 | currentSubmission = submission; 191 | if (submission != null) { 192 | Bukkit.getScheduler().runTask(redditBrowserPlugin, () -> { 193 | updateRoom(submission); 194 | updateTitleForRoom(submission); 195 | cleanupInventory(); 196 | }); 197 | } else { 198 | getPlayers().forEach(player -> player.sendMessage(ChatColor.RED + "No previous posts found.")); 199 | } 200 | }); 201 | } 202 | 203 | private void updateRoom(Submission submission) { 204 | removeHologramByType(location, EntityType.ARMOR_STAND); 205 | emptyCommentsChest(); 206 | buildLeaveButton(); 207 | removeNewCommentsButton(); 208 | screen.buildScreen(submission); 209 | if (submission != null) { 210 | buildNavigationButton(); 211 | buildVoteButtons(submission); 212 | buildCommentsButton(); 213 | buildRefreshButton(); 214 | buildLeaveButton(); 215 | buildSubredditHologram(submission); 216 | realignItemFrames(); 217 | } 218 | } 219 | 220 | private void realignItemFrames() { 221 | location.getWorld() 222 | .getNearbyEntities(location, 223 | -roomDimensions.getRoomWidth(), 224 | -roomDimensions.getRoomHeight(), 225 | -roomDimensions.getRoomDepth(), 226 | o -> Objects.equals(EntityType.ITEM_FRAME, o.getType())) 227 | .forEach(o -> ((ItemFrame) o).setRotation(Rotation.NONE)); 228 | } 229 | 230 | private void buildSubredditHologram(Submission submission) { 231 | final int height = roomDimensions.getRoomHeight() > 4 ? -(roomDimensions.getRoomHeight() / 2) : -1; 232 | spawnHologram(location.clone() 233 | .add(-(roomDimensions.getRoomWidth() / 2), 234 | height, 235 | -(roomDimensions.getRoomDepth() / 2)) 236 | .clone().add(.5, -2, .5), colorCode("a") + "r/" + submission.getSubreddit()); 237 | } 238 | 239 | private void buildRefreshButton() { 240 | Block refreshButton = location.getWorld().getBlockAt(location.clone().add(-(roomDimensions.getRoomWidth() / 2), -roomDimensions.getRoomHeight() + 2, -1)); 241 | refreshButton.setType(Material.OAK_BUTTON); 242 | refreshButton.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.REFRESH)); 243 | refreshButton.setMetadata(ROOM_ID, new FixedMetadataValue(redditBrowserPlugin, owner.getUniqueId())); 244 | refreshButton.setMetadata(BUTTON_ACTIVATED, new FixedMetadataValue(redditBrowserPlugin, false)); 245 | Directional refreshButtonDirectional = (Directional) refreshButton.getBlockData(); 246 | refreshButtonDirectional.setFacing(BlockFace.NORTH); 247 | refreshButton.setBlockData(refreshButtonDirectional); 248 | spawnHologram(refreshButton.getLocation().clone().add(.5, -2, .5), colorCode("a") + "Refresh"); 249 | } 250 | 251 | private void buildLeaveButton() { 252 | Block leaveButton = location.getWorld().getBlockAt(location.clone().add(-(roomDimensions.getRoomWidth() / 2) - 1, -roomDimensions.getRoomHeight() + 2, -1)); 253 | leaveButton.setType(Material.OAK_BUTTON); 254 | leaveButton.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.LEAVE)); 255 | leaveButton.setMetadata(ROOM_ID, new FixedMetadataValue(redditBrowserPlugin, owner.getUniqueId())); 256 | leaveButton.setMetadata(BUTTON_ACTIVATED, new FixedMetadataValue(redditBrowserPlugin, false)); 257 | Directional leaveButtonDirectional = (Directional) leaveButton.getBlockData(); 258 | leaveButtonDirectional.setFacing(BlockFace.NORTH); 259 | leaveButton.setBlockData(leaveButtonDirectional); 260 | spawnHologram(leaveButton.getLocation().clone().add(.5, -2, .5), colorCode("c") + "Leave"); 261 | } 262 | 263 | private void emptyCommentsChest() { 264 | Location chestLocation = location.clone().add(-roomDimensions.getRoomWidth() / 2, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() + 1); 265 | final Block block = chestLocation.getBlock(); 266 | if (Material.CHEST.equals(block.getType())) { 267 | Chest chest = (Chest) block.getState(); 268 | chest.getBlockInventory().clear(); 269 | } 270 | } 271 | 272 | private void buildCommentsButton() { 273 | Location chestLocation = location.clone().add(-roomDimensions.getRoomWidth() / 2, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() + 1); 274 | final Block block = chestLocation.getBlock(); 275 | block.setType(ROOM_MATERIAL); 276 | Block commentButton = location.getWorld().getBlockAt(chestLocation.clone().add(0, 0, 1)); 277 | commentButton.setType(Material.OAK_BUTTON); 278 | commentButton.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.LOAD_COMMENTS)); 279 | commentButton.setMetadata(ROOM_ID, new FixedMetadataValue(redditBrowserPlugin, owner.getUniqueId())); 280 | commentButton.setMetadata(BUTTON_ACTIVATED, new FixedMetadataValue(redditBrowserPlugin, false)); 281 | 282 | Directional commentsButtonDirection = (Directional) commentButton.getBlockData(); 283 | commentsButtonDirection.setFacing(BlockFace.SOUTH); 284 | commentButton.setBlockData(commentsButtonDirection); 285 | 286 | spawnHologram(commentButton.getLocation().clone().add(.5, -2, .5), COMMENTS_HOLOGRAM); 287 | } 288 | 289 | private void buildEmptyRoom() { 290 | emptyCommentsChest(); 291 | cube(ROOM_MATERIAL, location, location.clone().add(-roomDimensions.getRoomWidth(), -roomDimensions.getRoomHeight(), -roomDimensions.getRoomDepth())); 292 | cube(Material.AIR, location.clone().add(-1, -1, -1), location.clone().add(-roomDimensions.getRoomWidth() + 1, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() + 1)); 293 | } 294 | 295 | private void createRoom( 296 | Submission submission) { 297 | buildEmptyRoom(); 298 | updateRoom(submission); 299 | } 300 | 301 | public void displayComments() { 302 | Bukkit.getScheduler().runTaskAsynchronously(redditBrowserPlugin, () -> { 303 | final RootCommentNode comments = redditBrowserPlugin.redditClient 304 | .submission(currentSubmission.getId()) 305 | .comments(); 306 | Bukkit.getScheduler().runTask( 307 | redditBrowserPlugin, 308 | () -> buildCommentsChest(location, currentSubmission.getId(), comments, roomDimensions)); 309 | }); 310 | } 311 | 312 | private void buildCommentsChest( 313 | Location location, 314 | String submissionId, 315 | RootCommentNode comments, 316 | RoomDimensions roomDimensions) { 317 | if (comments != null) { 318 | buildNewCommentButton(); 319 | Location chestLocation = location.clone().add(-roomDimensions.getRoomWidth() / 2, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() + 1); 320 | final Location buttonLocation = chestLocation.clone().add(0, 0, 1); 321 | buttonLocation.getBlock().setType(Material.AIR); 322 | Block b = chestLocation.getBlock(); 323 | 324 | b.setType(Material.CHEST); 325 | Directional chestDirection = (Directional) b.getBlockData(); 326 | chestDirection.setFacing(BlockFace.SOUTH); 327 | b.setBlockData(chestDirection); 328 | b.setMetadata(SUBMISSION_ID, new FixedMetadataValue(redditBrowserPlugin, submissionId)); 329 | b.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.COMMENT_CHEST)); 330 | Chest chest = (Chest) b.getState(); 331 | 332 | chest.setCustomName(UUID.randomUUID().toString()); 333 | 334 | int in = 0; 335 | for (CommentNode cn : comments.getReplies()) { 336 | Comment c = cn.getSubject(); 337 | if (in < 26) { 338 | ItemStack book = new ItemStack(Material.WRITTEN_BOOK); 339 | BookMeta bookmeta = (BookMeta) book.getItemMeta(); 340 | bookmeta.setTitle("Comment"); 341 | bookmeta.setAuthor("u/" + c.getAuthor()); 342 | if (c.getBody().length() > 255) { 343 | double f = ceil(((float) c.getBody().length()) / 255f); 344 | for (int i = 0; i < f; i++) { 345 | if (c.getBody().length() < (i + 1) * 255) { 346 | bookmeta.addPage(c.getBody().substring(i * 255)); 347 | } else { 348 | bookmeta.addPage(c.getBody().substring(i * 255, (i + 1) * 255)); 349 | } 350 | } 351 | } else { 352 | bookmeta.addPage(c.getBody()); 353 | } 354 | bookmeta.setLore(Arrays.asList(c.getId(), c.getBody())); 355 | bookmeta.setDisplayName(COMMENT_DISPLAY_NAME); 356 | book.setItemMeta(bookmeta); 357 | redditBrowserPlugin.commentCache.put(c.getId(), cn); 358 | chest.getInventory().addItem(book); 359 | } else { 360 | break; 361 | } 362 | in++; 363 | } 364 | } 365 | } 366 | 367 | private void buildNewCommentButton() { 368 | Location buttonLocation = location.clone().add(-roomDimensions.getRoomWidth() / 2 - 1, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() + 1); 369 | final Block writeCommentsButton = buttonLocation.getBlock(); 370 | writeCommentsButton.setType(Material.OAK_BUTTON); 371 | writeCommentsButton.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.WRITE_COMMENT)); 372 | writeCommentsButton.setMetadata(ROOM_ID, new FixedMetadataValue(redditBrowserPlugin, owner.getUniqueId())); 373 | writeCommentsButton.setMetadata(BUTTON_ACTIVATED, new FixedMetadataValue(redditBrowserPlugin, false)); 374 | 375 | Directional commentsButtonDirection = (Directional) writeCommentsButton.getBlockData(); 376 | commentsButtonDirection.setFacing(BlockFace.SOUTH); 377 | writeCommentsButton.setBlockData(commentsButtonDirection); 378 | 379 | spawnHologram(writeCommentsButton.getLocation().clone().add(.5, -2, .5), WRITE_COMMENT_HOLOGRAM); 380 | } 381 | 382 | private void removeNewCommentsButton() { 383 | Location buttonLocation = location.clone().add(-roomDimensions.getRoomWidth() / 2 - 1, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() + 1); 384 | final Block writeCommentsButton = buttonLocation.getBlock(); 385 | writeCommentsButton.setType(Material.AIR); 386 | } 387 | 388 | private void buildVoteButtons(Submission submission) { 389 | Block uv = location.getWorld().getBlockAt(location.clone().add(-roomDimensions.getRoomWidth() + 1, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() + 1)); 390 | uv.setType(Material.OAK_BUTTON); 391 | uv.setMetadata(SUBMISSION_ID, new FixedMetadataValue(redditBrowserPlugin, submission.getId())); 392 | uv.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.UPVOTE)); 393 | Directional uvdir = (Directional) uv.getBlockData(); 394 | uvdir.setFacing(BlockFace.SOUTH); 395 | uv.setBlockData(uvdir); 396 | spawnHologram(uv.getLocation().clone().add(.5, -2, .5), colorCode("a") + "+1"); 397 | 398 | 399 | Block dv = location.getWorld().getBlockAt(location.clone().add(-1, -roomDimensions.getRoomHeight() + 1, -roomDimensions.getRoomDepth() + 1)); 400 | dv.setType(Material.OAK_BUTTON); 401 | dv.setMetadata(SUBMISSION_ID, new FixedMetadataValue(redditBrowserPlugin, submission.getId())); 402 | dv.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.DOWNVOTE)); 403 | 404 | Directional dvdir = (Directional) dv.getBlockData(); 405 | dvdir.setFacing(BlockFace.SOUTH); 406 | dv.setBlockData(dvdir); 407 | 408 | spawnHologram(dv.getLocation().clone().add(.5, -2, .5), colorCode("c") + "-1"); 409 | } 410 | 411 | 412 | 413 | private void buildNavigationButton() { 414 | int zPosition = (-roomDimensions.getRoomDepth() / 2) + 1; 415 | if (zPosition > -2) { 416 | zPosition = -2; 417 | } 418 | 419 | Block nextButton = location.getWorld().getBlockAt(location.clone().add(-1, -roomDimensions.getRoomHeight() + 2, zPosition)); 420 | nextButton.setType(Material.OAK_BUTTON); 421 | nextButton.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.NEXT_ROOM)); 422 | nextButton.setMetadata(BUTTON_ACTIVATED, new FixedMetadataValue(redditBrowserPlugin, false)); 423 | nextButton.setMetadata(ROOM_ID, new FixedMetadataValue(redditBrowserPlugin, owner.getUniqueId())); 424 | Directional nextButtonDirection = (Directional) nextButton.getBlockData(); 425 | nextButtonDirection.setFacing(BlockFace.WEST); 426 | nextButton.setBlockData(nextButtonDirection); 427 | spawnHologram(nextButton.getLocation().clone().add(.5, -2, .5), colorCode("9") + "Next"); 428 | 429 | if (redditQueue.hasPrevious()) { 430 | Block previousButton = location.getWorld().getBlockAt(location.clone().add(-roomDimensions.getRoomWidth() + 1, -roomDimensions.getRoomHeight() + 2, zPosition)); 431 | previousButton.setType(Material.OAK_BUTTON); 432 | previousButton.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(redditBrowserPlugin, InteractiveEnum.PREVIOUS_ROOM)); 433 | previousButton.setMetadata(BUTTON_ACTIVATED, new FixedMetadataValue(redditBrowserPlugin, false)); 434 | previousButton.setMetadata(ROOM_ID, new FixedMetadataValue(redditBrowserPlugin, owner.getUniqueId())); 435 | Directional previousButtonDirection = (Directional) nextButton.getBlockData(); 436 | previousButtonDirection.setFacing(BlockFace.EAST); 437 | previousButton.setBlockData(previousButtonDirection); 438 | spawnHologram(previousButton.getLocation().clone().add(.5, -2, .5), PREVIOUS_HOLOGRAM_NAME); 439 | } else { 440 | Block previousButton = location.getWorld().getBlockAt(location.clone().add(-roomDimensions.getRoomWidth() + 1, -roomDimensions.getRoomHeight() + 2, zPosition)); 441 | previousButton.setType(Material.AIR); 442 | removeHologramByName(previousButton.getLocation().clone().add(.5, -2, .5), PREVIOUS_HOLOGRAM_NAME); 443 | } 444 | } 445 | 446 | private void removeHologramByName(Location location, String name) { 447 | location.getWorld() 448 | .getNearbyEntities(location, 449 | -roomDimensions.getRoomWidth(), 450 | -roomDimensions.getRoomHeight(), 451 | -roomDimensions.getRoomDepth(), 452 | o -> Objects.equals(name, o.getName())) 453 | .forEach(Entity::remove); 454 | } 455 | 456 | void removeHologramByType(Location location, EntityType entityType) { 457 | location.getWorld() 458 | .getNearbyEntities(location, 459 | -roomDimensions.getRoomWidth(), 460 | -roomDimensions.getRoomHeight(), 461 | -roomDimensions.getRoomDepth(), 462 | o -> Objects.equals(entityType, o.getType())) 463 | .forEach(Entity::remove); 464 | } 465 | 466 | private void cube(Material blockMaterial, Location from, Location to) { 467 | for (int x = from.getBlockX(); x >= to.getBlockX(); x--) { 468 | for (int y = from.getBlockY(); y >= to.getBlockY(); y--) { 469 | for (int z = from.getBlockZ(); z >= to.getBlockZ(); z--) { 470 | final Block block = from.getWorld().getBlockAt(x, y, z); 471 | block.setType(blockMaterial); 472 | } 473 | } 474 | } 475 | } 476 | 477 | private void spawnHologram(Location l, String name) { 478 | ArmorStand as = (ArmorStand) l.getWorld().spawnEntity(l, EntityType.ARMOR_STAND); 479 | as.setCustomName(name); 480 | as.setCustomNameVisible(true); 481 | as.setGravity(false); 482 | as.setVisible(false); 483 | as.setInvulnerable(true); 484 | as.setCollidable(false); 485 | } 486 | 487 | public boolean hasPlayers() { 488 | return location.getWorld().getNearbyEntities(location, 489 | -roomDimensions.getRoomWidth(), 490 | -roomDimensions.getRoomHeight(), 491 | -roomDimensions.getRoomDepth(), 492 | entity -> entity instanceof Player).size() > 0; 493 | } 494 | 495 | public boolean hasPlayer(UUID playerId) { 496 | return location.getWorld().getNearbyEntities(location, 497 | -roomDimensions.getRoomWidth(), 498 | -roomDimensions.getRoomHeight(), 499 | -roomDimensions.getRoomDepth(), 500 | entity -> entity instanceof Player) 501 | .stream() 502 | .anyMatch(o -> Objects.equals(playerId, o.getUniqueId())); 503 | } 504 | 505 | public Set getPlayers() { 506 | return location.getWorld().getNearbyEntities(location, 507 | -roomDimensions.getRoomWidth(), 508 | -roomDimensions.getRoomHeight(), 509 | -roomDimensions.getRoomDepth(), 510 | entity -> entity instanceof Player) 511 | .stream() 512 | .map(o -> (Player) o) 513 | .collect(Collectors.toSet()); 514 | } 515 | 516 | public void destroy() { 517 | try { 518 | Bukkit.getScheduler().runTask(redditBrowserPlugin, new Runnable() { 519 | @Override 520 | public void run() { 521 | location.getWorld().getNearbyEntities(location, 522 | -roomDimensions.getRoomWidth(), 523 | -roomDimensions.getRoomHeight(), 524 | -roomDimensions.getRoomDepth(), 525 | entity -> !(entity instanceof Player)) 526 | .forEach(Entity::remove); 527 | getPlayers().forEach(redditBrowserPlugin::kickOut); 528 | cube(Material.AIR, location, location.clone().add(-roomDimensions.getRoomWidth(), -roomDimensions.getRoomHeight(), -roomDimensions.getRoomDepth())); 529 | Bukkit.getLogger().log(Level.INFO, "Destroyed reddit room of player " + owner.getDisplayName()); 530 | } 531 | }); 532 | } catch (Exception e) { 533 | Bukkit.getLogger().log(Level.INFO, "Could not remove reddit room of player " + owner.getDisplayName()); 534 | } 535 | } 536 | 537 | public UUID getRoomId() { 538 | return owner.getUniqueId(); 539 | } 540 | 541 | public boolean isInside(Location locationToTest) { 542 | Location roomMin = this.location.clone().add(-roomDimensions.getRoomWidth(), -roomDimensions.getRoomHeight(), -roomDimensions.getRoomDepth()); 543 | Location roomMax = this.location; 544 | if (!Objects.equals(roomMax.getWorld().getName(), locationToTest.getWorld().getName())) { 545 | return false; 546 | } 547 | boolean x = locationToTest.getX() >= Math.min(roomMax.getX(), roomMin.getX()) && locationToTest.getX() <= Math.max(roomMax.getX(), roomMin.getX()); 548 | boolean y = locationToTest.getY() >= Math.min(roomMax.getY(), roomMin.getY()) && locationToTest.getY() <= Math.max(roomMax.getY(), roomMin.getY()); 549 | boolean z = locationToTest.getZ() >= Math.min(roomMax.getZ(), roomMin.getZ()) && locationToTest.getZ() <= Math.max(roomMax.getZ(), roomMin.getZ()); 550 | return x && y && z; 551 | } 552 | 553 | public void showURLtoPlayers(Player player) { 554 | if (currentSubmission != null) { 555 | player.sendMessage(ChatColor.YELLOW + "Image/Video link: " + ChatColor.BLUE + ChatColor.UNDERLINE + currentSubmission.getUrl()); 556 | } 557 | } 558 | 559 | public Material getRoomMaterial() { 560 | return ROOM_MATERIAL; 561 | } 562 | 563 | public RedditBrowserPlugin getRedditBrowserPlugin() { 564 | return redditBrowserPlugin; 565 | } 566 | } 567 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/room/RoomDimensions.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.room; 2 | 3 | 4 | public class RoomDimensions { 5 | private int roomWidth; 6 | private int roomHeight; 7 | private int roomDepth; 8 | private int screenWidth; 9 | private int screenHeight; 10 | 11 | public RoomDimensions(int roomWidth, int roomHeight, int roomDepth, int screenWidth, int screenHeight) { 12 | this.roomWidth = roomWidth; 13 | this.roomHeight = roomHeight; 14 | this.roomDepth = roomDepth; 15 | this.screenWidth = screenWidth; 16 | this.screenHeight = screenHeight; 17 | } 18 | 19 | public int getRoomWidth() { 20 | return roomWidth; 21 | } 22 | 23 | public int getRoomHeight() { 24 | return roomHeight; 25 | } 26 | 27 | public int getRoomDepth() { 28 | return roomDepth; 29 | } 30 | 31 | public int getScreenWidth() { 32 | return screenWidth; 33 | } 34 | 35 | public int getScreenHeight() { 36 | return screenHeight; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/me/delta2force/redditbrowser/room/Screen.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.room; 2 | 3 | import me.delta2force.redditbrowser.RedditBrowserPlugin; 4 | import me.delta2force.redditbrowser.interaction.InteractiveEnum; 5 | import me.delta2force.redditbrowser.renderer.TiledRenderer; 6 | import me.delta2force.redditbrowser.repository.URLToImageRepository; 7 | import net.dean.jraw.models.Submission; 8 | import org.bukkit.*; 9 | import org.bukkit.block.BlockFace; 10 | import org.bukkit.entity.Entity; 11 | import org.bukkit.entity.EntityType; 12 | import org.bukkit.entity.ItemFrame; 13 | import org.bukkit.inventory.ItemStack; 14 | import org.bukkit.inventory.meta.BookMeta; 15 | import org.bukkit.inventory.meta.MapMeta; 16 | import org.bukkit.map.MapView; 17 | import org.bukkit.metadata.FixedMetadataValue; 18 | 19 | import java.awt.image.BufferedImage; 20 | import java.util.Collection; 21 | import java.util.Objects; 22 | 23 | import static java.lang.Math.ceil; 24 | import static me.delta2force.redditbrowser.RedditBrowserPlugin.INTERACTIVE_ENUM; 25 | import static me.delta2force.redditbrowser.RedditBrowserPlugin.ROOM_ID; 26 | import static me.delta2force.redditbrowser.room.Room.COMMENT_DISPLAY_NAME; 27 | 28 | public class Screen { 29 | private final Room room; 30 | private TiledRenderer tiledRenderer = null; 31 | private final Location screenLocation; 32 | private final int screenWidth; 33 | private final int screenHeight; 34 | 35 | public Screen( 36 | Room room, 37 | Location screenLocation, 38 | int screenWidth, 39 | int screenHeight) { 40 | this.room = room; 41 | this.screenLocation = screenLocation; 42 | this.screenWidth = screenWidth; 43 | this.screenHeight = screenHeight; 44 | } 45 | 46 | public void buildScreen(Submission submission) { 47 | if (submission != null && !submission.isSelfPost()) { 48 | if (tiledRenderer == null) { 49 | cleanBackWall(); 50 | final World world = screenLocation.getWorld(); 51 | tiledRenderer = new TiledRenderer(room.getRedditBrowserPlugin(), screenWidth, screenHeight); 52 | Bukkit.getScheduler().runTaskLater(room.getRedditBrowserPlugin(), () -> { 53 | for (int row = 0; row < screenHeight; row++) { 54 | for (int col = 0; col < screenWidth; col++) { 55 | final Location blockLocation = screenLocation.clone().add(+col, -row, 0); 56 | blockLocation.getBlock().setType(Material.GLOWSTONE); 57 | ItemFrame itf = (ItemFrame) world.spawnEntity(blockLocation.clone().add(0, 0, 1), EntityType.ITEM_FRAME); 58 | itf.setFacingDirection(BlockFace.SOUTH); 59 | itf.setInvulnerable(true); 60 | itf.setMetadata(ROOM_ID, new FixedMetadataValue(room.getRedditBrowserPlugin(), room.getRoomId())); 61 | itf.setMetadata(INTERACTIVE_ENUM, new FixedMetadataValue(room.getRedditBrowserPlugin(), InteractiveEnum.SHOW_URL)); 62 | itf.setRotation(Rotation.NONE); 63 | ItemStack map = new ItemStack(Material.FILLED_MAP); 64 | MapMeta mapMeta = (MapMeta) map.getItemMeta(); 65 | MapView mapView = Bukkit.createMap(world); 66 | mapView.setTrackingPosition(false); 67 | mapView.setLocked(true); 68 | mapView.setUnlimitedTracking(false); 69 | mapView.getRenderers().forEach(mapView::removeRenderer); 70 | mapView.addRenderer(tiledRenderer.getRenderer(row, col)); 71 | mapMeta.setMapView(mapView); 72 | map.setItemMeta(mapMeta); 73 | itf.setItem(map); 74 | 75 | } 76 | } 77 | updateRenderer(submission); 78 | 79 | }, 5); 80 | } else { 81 | updateRenderer(submission); 82 | } 83 | } else if (submission != null && submission.isSelfPost()) { 84 | buildSelfPost(submission); 85 | } else { 86 | cleanBackWall(); 87 | } 88 | } 89 | 90 | private void updateRenderer(Submission submission) { 91 | Bukkit.getScheduler().runTaskAsynchronously(room.getRedditBrowserPlugin(), () -> { 92 | final BufferedImage image = URLToImageRepository.findImage(submission); 93 | if (tiledRenderer != null) { 94 | tiledRenderer.updateImage(submission.getUrl(), image); 95 | } 96 | }); 97 | } 98 | 99 | public void clean() { 100 | cleanBackWall(); 101 | } 102 | 103 | private void buildSelfPost(Submission submission) { 104 | cleanBackWall(); 105 | ItemStack book = new ItemStack(Material.WRITTEN_BOOK); 106 | BookMeta bookmeta = (BookMeta) book.getItemMeta(); 107 | bookmeta.setTitle(submission.getTitle()); 108 | bookmeta.setDisplayName(COMMENT_DISPLAY_NAME); 109 | bookmeta.setAuthor(submission.getAuthor()); 110 | if (submission.getSelfText().length() > 255) { 111 | double f = ceil(((float) submission.getSelfText().length()) / 255f); 112 | for (int i = 0; i < f; i++) { 113 | if (submission.getSelfText().length() < (i + 1) * 255) { 114 | bookmeta.addPage(submission.getSelfText().substring(i * 255)); 115 | } else { 116 | bookmeta.addPage(submission.getSelfText().substring(i * 255, (i + 1) * 255)); 117 | } 118 | } 119 | } else { 120 | bookmeta.addPage(submission.getSelfText()); 121 | } 122 | 123 | Bukkit.getScheduler().runTaskLater(room.getRedditBrowserPlugin(), () -> { 124 | final Location smallScreenLocation = screenLocation.clone().add(screenWidth / 2, -screenHeight + 1, 0); 125 | smallScreenLocation.getBlock().setType(Material.GLOWSTONE); 126 | ItemFrame itf = (ItemFrame) smallScreenLocation.getWorld().spawnEntity(smallScreenLocation.clone().add(0, 0, 1), EntityType.ITEM_FRAME); 127 | itf.setFacingDirection(BlockFace.SOUTH); 128 | book.setItemMeta(bookmeta); 129 | itf.setItem(book); 130 | }, 5); 131 | } 132 | 133 | private void cleanBackWall() { 134 | clearItemFrames(); 135 | tiledRenderer = null; 136 | 137 | for (int row = 0; row < screenHeight; row++) { 138 | for (int col = 0; col < screenWidth; col++) { 139 | final Location blockLocation = screenLocation.clone().add(+col, -row, 0); 140 | blockLocation.getBlock().setType(room.getRoomMaterial()); 141 | } 142 | } 143 | } 144 | 145 | private void clearItemFrames() { 146 | final Collection nearbyEntities = screenLocation.getWorld() 147 | .getNearbyEntities(screenLocation, 148 | screenWidth, 149 | -screenHeight, 150 | 2, 151 | o -> Objects.equals(EntityType.ITEM_FRAME, o.getType())); 152 | nearbyEntities.forEach(Entity::remove); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | username: "exampleUsername" 2 | password: "examplePassword" 3 | clientId: "exampleClientId" 4 | clientSecret: "exampleClientSecret" 5 | screenWidth: 5 6 | screenHeight: 5 7 | roomDepth: 8 -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: RedditBrowser 2 | author: Delta2Force 3 | version: 1.8 4 | api-version: 1.13 5 | main: me.delta2force.redditbrowser.RedditBrowserPlugin 6 | 7 | commands: 8 | reddit: 9 | description: browse reddit in minecraft! 10 | permission: redditbrowser.browse 11 | reddit-join: 12 | description: join a player in an existing lobby. 13 | permissions: 14 | redditbrowser.browse: 15 | default: op -------------------------------------------------------------------------------- /src/test/java/me/delta2force/redditbrowser/repository/URLOptimizerTest.java: -------------------------------------------------------------------------------- 1 | package me.delta2force.redditbrowser.repository; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.*; 6 | 7 | 8 | class URLOptimizerTest { 9 | 10 | @Test 11 | void youtube() { 12 | final String optimizedURL = URLOptimizer.optimizeURL("https://www.youtube.com/watch?v=kayFrIR-Qfw&feature=youtu.be"); 13 | assertEquals("https://img.youtube.com/vi/kayFrIR-Qfw/hqdefault.jpg", optimizedURL); 14 | } 15 | 16 | 17 | @Test 18 | void vimeo() { 19 | final String optimizedURL = URLOptimizer.optimizeURL("https://vimeo.com/343241535"); 20 | assertEquals("https://i.vimeocdn.com/video/794908709_640.jpg", optimizedURL); 21 | } 22 | } --------------------------------------------------------------------------------