From 82646880dac382ba5b1adcd035758c3bba10aa6f Mon Sep 17 00:00:00 2001 From: Dark98 Date: Wed, 6 Oct 2021 16:28:08 +0100 Subject: [PATCH] Import Parabot "API" & Rename Directory Names --- pom.xml | 5 - .../java/org/parabot/api/Configuration.java | 14 + .../org/parabot/api/calculations/Random.java | 26 ++ .../java/org/parabot/api/io/Directories.java | 277 +++++++++++++++ .../java/org/parabot/api/io/FileUtil.java | 123 +++++++ .../java/org/parabot/api/io/ImageUtil.java | 43 +++ .../org/parabot/api/io/ProgressListener.java | 44 +++ .../org/parabot/api/io/SizeInputStream.java | 62 ++++ src/main/java/org/parabot/api/io/WebUtil.java | 330 ++++++++++++++++++ .../org/parabot/api/io/build/BuildPath.java | 19 + .../org/parabot/api/io/images/Images.java | 30 ++ .../parabot/api/io/libraries/Environment.java | 23 ++ .../org/parabot/api/io/libraries/Library.java | 67 ++++ .../io/libraries/jpushbullet/JPushBullet.java | 67 ++++ .../java/org/parabot/api/misc/JavaUtil.java | 15 + .../org/parabot/api/misc/OperatingSystem.java | 26 ++ .../parabot/api/misc/ProjectProperties.java | 44 +++ .../java/org/parabot/api/misc/StringUtil.java | 117 +++++++ .../java/org/parabot/api/misc/Version.java | 55 +++ .../notifications/NotificationManager.java | 113 ++++++ .../types/MacNotificationType.java | 43 +++ .../notifications/types/NotificationType.java | 33 ++ .../types/PushBulletNotificationType.java | 55 +++ .../pushbullet/PushBulletController.java | 52 +++ .../java/org/parabot/api/output/Logger.java | 94 +++++ .../java/org/parabot/api/output/Verboser.java | 10 + .../java/org/parabot/api/ui/JavaFxUtil.java | 142 ++++++++ .../java/org/parabot/api/ui/SwingUtil.java | 66 ++++ src/main/resources/storage/images/icon.png | Bin 0 -> 77476 bytes .../resources/storage/internal.properties | 1 + src/test/java/org/parabot/MacOSTest.java | 20 ++ .../java/org/parabot/NotificationTest.java | 16 + 32 files changed, 2027 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/parabot/api/Configuration.java create mode 100644 src/main/java/org/parabot/api/calculations/Random.java create mode 100644 src/main/java/org/parabot/api/io/Directories.java create mode 100644 src/main/java/org/parabot/api/io/FileUtil.java create mode 100644 src/main/java/org/parabot/api/io/ImageUtil.java create mode 100644 src/main/java/org/parabot/api/io/ProgressListener.java create mode 100644 src/main/java/org/parabot/api/io/SizeInputStream.java create mode 100644 src/main/java/org/parabot/api/io/WebUtil.java create mode 100644 src/main/java/org/parabot/api/io/build/BuildPath.java create mode 100644 src/main/java/org/parabot/api/io/images/Images.java create mode 100644 src/main/java/org/parabot/api/io/libraries/Environment.java create mode 100644 src/main/java/org/parabot/api/io/libraries/Library.java create mode 100644 src/main/java/org/parabot/api/io/libraries/jpushbullet/JPushBullet.java create mode 100644 src/main/java/org/parabot/api/misc/JavaUtil.java create mode 100644 src/main/java/org/parabot/api/misc/OperatingSystem.java create mode 100644 src/main/java/org/parabot/api/misc/ProjectProperties.java create mode 100644 src/main/java/org/parabot/api/misc/StringUtil.java create mode 100644 src/main/java/org/parabot/api/misc/Version.java create mode 100644 src/main/java/org/parabot/api/notifications/NotificationManager.java create mode 100644 src/main/java/org/parabot/api/notifications/types/MacNotificationType.java create mode 100644 src/main/java/org/parabot/api/notifications/types/NotificationType.java create mode 100644 src/main/java/org/parabot/api/notifications/types/PushBulletNotificationType.java create mode 100644 src/main/java/org/parabot/api/notifications/types/pushbullet/PushBulletController.java create mode 100644 src/main/java/org/parabot/api/output/Logger.java create mode 100644 src/main/java/org/parabot/api/output/Verboser.java create mode 100644 src/main/java/org/parabot/api/ui/JavaFxUtil.java create mode 100644 src/main/java/org/parabot/api/ui/SwingUtil.java create mode 100644 src/main/resources/storage/images/icon.png create mode 100644 src/main/resources/storage/internal.properties create mode 100644 src/test/java/org/parabot/MacOSTest.java create mode 100644 src/test/java/org/parabot/NotificationTest.java diff --git a/pom.xml b/pom.xml index 1340001..49ee6f7 100755 --- a/pom.xml +++ b/pom.xml @@ -70,11 +70,6 @@ 4.12 test - - org.parabot - internal-api - 1.53.1 - diff --git a/src/main/java/org/parabot/api/Configuration.java b/src/main/java/org/parabot/api/Configuration.java new file mode 100644 index 0000000..c8d19c6 --- /dev/null +++ b/src/main/java/org/parabot/api/Configuration.java @@ -0,0 +1,14 @@ +package org.parabot.api; + +/** + * @author JKetelaar + */ +public class Configuration { + + public static final String BOT_TITLE = "Parabot"; + public static final String BOT_SLOGAN = "The best RuneScape private server bot"; + + public static final String V3_API_ENDPOINT = "http://v3.bdn.parabot.org/api/"; + + public static final String LIBRARIES_DOWNLOAD = V3_API_ENDPOINT + "bot/libraries/download"; +} diff --git a/src/main/java/org/parabot/api/calculations/Random.java b/src/main/java/org/parabot/api/calculations/Random.java new file mode 100644 index 0000000..b873087 --- /dev/null +++ b/src/main/java/org/parabot/api/calculations/Random.java @@ -0,0 +1,26 @@ +package org.parabot.api.calculations; + +/** + * A random class is used for generating random numbers + * + * @author Everel + */ +public class Random { + private final static java.util.Random RANDOM = new java.util.Random(); + + /** + * Randomizes a number between minimum and maximum + * + * @param min + * @param max + * @return randomized number + */ + public static int between(final int min, final int max) { + try { + return min + (max == min ? 0 : RANDOM.nextInt(max - min)); + } catch (Exception e) { + return min + (max - min); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/parabot/api/io/Directories.java b/src/main/java/org/parabot/api/io/Directories.java new file mode 100644 index 0000000..1dd3bcf --- /dev/null +++ b/src/main/java/org/parabot/api/io/Directories.java @@ -0,0 +1,277 @@ +package org.parabot.api.io; + +import org.parabot.api.misc.OperatingSystem; +import org.parabot.api.output.Verboser; + +import javax.swing.*; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.*; + +/** + * Holds and manages Parabot's used directories + * + * @author Everel + * @author JKetelaar + * @author Matt + */ +public class Directories { + + private static Map cached; + private static File temp = null; + + static { + cached = new HashMap<>(); + switch (OperatingSystem.getOS()) { + case WINDOWS: + cached.put("Root", new JFileChooser().getFileSystemView().getDefaultDirectory()); + break; + default: + cached.put("Root", new File(System.getProperty("user.home"))); + break; + } + + Verboser.verbose("Caching directories..."); + cached.put("Root", getDefaultDirectory()); + cached.put("Workspace", new File(cached.get("Root"), "/2006Scape/")); + cached.put("Sources", new File(cached.get("Root"), "/2006Scape/scripts/sources/")); + cached.put("Compiled", new File(cached.get("Root"), "/2006Scape/scripts/compiled/")); + cached.put("Resources", new File(cached.get("Root"), "/2006Scape/scripts/resources/")); + cached.put("Settings", new File(cached.get("Root"), "/2006Scape/settings/")); + cached.put("Servers", new File(cached.get("Root"), "/2006Scape/servers/")); + cached.put("Cache", new File(cached.get("Root"), "/2006Scape/cache/")); + cached.put("Screenshots", new File(cached.get("Root"), "/2006Scape/screenshots/")); + Verboser.verbose("Directories cached."); + + clearCache(259200); + } + + /** + * Set script bin folder + * + * @param f + */ + public static void setScriptCompiledDirectory(File f) { + if (!f.isDirectory()) { + throw new IllegalArgumentException(f + "is not a directory."); + } + cached.put("Compiled", f); + } + + /** + * Set server bin folder + * + * @param f + */ + public static void setServerCompiledDirectory(File f) { + if (!f.isDirectory()) { + throw new IllegalArgumentException(f + "is not a directory."); + } + cached.put("Servers", f); + } + + /** + * Returns the root directory outside of the main Parabot folder. + * + * @return + */ + public static File getDefaultDirectory() { + return cached.get("Root"); + } + + /** + * Returns the Parabot folder. + * + * @return + */ + public static File getWorkspace() { + return cached.get("Workspace"); + } + + /** + * Returns the script sources folder. + * + * @return + */ + public static File getScriptSourcesPath() { + return cached.get("Sources"); + } + + /** + * Returns the compiled scripts folder. + * + * @return + */ + public static File getScriptCompiledPath() { + return cached.get("Compiled"); + } + + /** + * Returns the scripts resources folder. + * + * @return + */ + public static File getResourcesPath() { + return cached.get("Resources"); + } + + /** + * Returns the Parabot settings folder. + * + * @return + */ + public static File getSettingsPath() { + return cached.get("Settings"); + } + + /** + * Returns the Parabot servers folder. + * + * @return + */ + public static File getServerPath() { + return cached.get("Servers"); + } + + /** + * Returns the Parabot cache folder. + * + * @return + */ + public static File getCachePath() { + return cached.get("Cache"); + } + + /** + * Returns the redirected Home Directory + * + * @return + */ + public static File getHomeDir() { + return cached.get("Home"); + } + + /** + * Returns the screenshot folder. + * + * @return + */ + public static File getScreenshotDir() { + return cached.get("Screenshots"); + } + + /** + * Validates all directories and makes them if necessary + */ + public static void validate() { + final File defaultPath = getDefaultDirectory(); + if (defaultPath == null || !defaultPath.exists()) { + throw new RuntimeException("Default path not found"); + } + final Queue files = new LinkedList(); + files.addAll(cached.values()); + while (files.size() > 0) { + final File file = files.poll(); + if (!file.exists()) { + Verboser.verbose("Generating directory: " + file.getAbsolutePath()); + file.mkdirs(); + if (!file.exists()) { + System.err.println("Failed to make directory: " + file.getAbsolutePath()); + } + } + } + } + + public static File getTempDirectory() { + if (temp != null) { + return temp; + } + int randomNum = new Random().nextInt(999999999); + temp = new File(getResourcesPath(), randomNum + "/"); + temp.mkdirs(); + temp.deleteOnExit(); + return temp; + } + + /** + * Clears the cache based on the latest modification + * + * @param remove A long that represents the amount of seconds that a file may have since the latest modification + * @param force Defines if the cache folder, within user.home, should also be removed + */ + public static void clearCache(int remove, boolean force) { + File[] cache = getCachePath().listFiles(); + if (cache != null) { + for (File f : cache) { + if (f != null && System.currentTimeMillis() / 1000 - f.lastModified() / 1000 > remove) { + Verboser.verbose("Clearing " + (f.isDirectory() ? "directory " : "file ") + f.getName() + " from cache (" + (force ? "Force-cleared" : "Expired") + ")..."); + + try { + FileUtil.delete(f); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } + } + + private static void clearCache(int remove) { + clearCache(remove, false); + } + + public static void clearCache() { + clearCache(0, true); + } + + /** + * @param file Directory to be removed + */ + private static void removeDirectory(File file, boolean root) { + if (file.isDirectory()) { + if (file.list().length == 0) { + file.delete(); + Verboser.verbose("Directory is deleted : " + + file.getAbsolutePath()); + } else { + String files[] = file.list(); + for (String temp : files) { + File fileDelete = new File(file, temp); + removeDirectory(fileDelete, false); + } + + if (file.list().length == 0) { + file.delete(); + Verboser.verbose("Directory is deleted: " + + file.getAbsolutePath()); + } + } + if (root) { + file.delete(); + } + } else { + file.delete(); + Verboser.verbose("File is deleted : " + file.getAbsolutePath()); + } + } + + /** + * Returns an array of files with from a given directory and a given extension + * + * @param directory The directory where should be searched + * @param extension The extension to be searched for, including the dot (like .json) + * @return An array of of files that match the request + */ + public static File[] listFilesWithExtension(File directory, final String extension) { + return directory.listFiles(new FilenameFilter() { + public boolean accept(File dir, String filename) { + return filename.endsWith(extension); + } + }); + } + + public static File[] listJSONFiles(File directory) { + return listFilesWithExtension(directory, ".json"); + } +} diff --git a/src/main/java/org/parabot/api/io/FileUtil.java b/src/main/java/org/parabot/api/io/FileUtil.java new file mode 100644 index 0000000..423698e --- /dev/null +++ b/src/main/java/org/parabot/api/io/FileUtil.java @@ -0,0 +1,123 @@ +package org.parabot.api.io; + +import org.parabot.api.output.Verboser; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +/** + * @author JKetelaar + */ +public class FileUtil { + + public static String getChecksum(File file) { + if (file.isFile()) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + if (file.exists()) { + FileInputStream fis = new FileInputStream(file); + byte[] dataBytes = new byte[1024]; + + int nread; + + while ((nread = fis.read(dataBytes)) != -1) { + md.update(dataBytes, 0, nread); + } + + byte[] mdbytes = md.digest(); + + StringBuilder sb = new StringBuilder(""); + for (int i = 0; i < mdbytes.length; i++) { + sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1)); + } + + return sb.toString(); + } + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + } + + return null; + } + + public static byte[] getChecksumBytes(File file) { + if (file.isFile()) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + if (file.exists()) { + FileInputStream fis = new FileInputStream(file); + byte[] dataBytes = new byte[1024]; + + int nread; + + while ((nread = fis.read(dataBytes)) != -1) { + md.update(dataBytes, 0, nread); + } + + return md.digest(); + } + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } + } + + return null; + } + + public static void copyFile(File sourceFile, File destFile) + throws IOException { + if (!sourceFile.exists()) { + return; + } + if (!destFile.exists()) { + destFile.createNewFile(); + } + FileChannel source = null; + FileChannel destination = null; + source = new FileInputStream(sourceFile).getChannel(); + destination = new FileOutputStream(destFile).getChannel(); + if (source != null) { + destination.transferFrom(source, 0, source.size()); + } + if (source != null) { + source.close(); + } + destination.close(); + } + + public static void delete(File file) throws IOException { + if (file.isDirectory()) { + if (file.list().length == 0) { + + boolean success = file.delete(); + Verboser.verbose(success ? "Deleted " + file.getAbsolutePath() + " OK" : + "FAILED to delete " + file.getAbsolutePath()); + } else { + + String files[] = file.list(); + + for (String temp : files) { + File fileDelete = new File(file, temp); + delete(fileDelete); + } + + if (file.list().length == 0) { + boolean success = file.delete(); + Verboser.verbose(success ? "Deleted " + file.getAbsolutePath() + " OK" : + "FAILED to delete " + file.getAbsolutePath()); + } + } + + } else { + boolean success = file.delete(); + Verboser.verbose(success ? "Deleted " + file.getAbsolutePath() + " OK" : + "FAILED to delete " + file.getAbsolutePath()); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/parabot/api/io/ImageUtil.java b/src/main/java/org/parabot/api/io/ImageUtil.java new file mode 100644 index 0000000..3b41156 --- /dev/null +++ b/src/main/java/org/parabot/api/io/ImageUtil.java @@ -0,0 +1,43 @@ +package org.parabot.api.io; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.io.File; +import java.io.IOException; +import java.net.URL; + +/** + * @author Fryslan + */ +public class ImageUtil { + + /** + * Gets an image from local device + * + * @param location Location of the image + * @return image from location. + */ + public Image getLocalImage(String location) { + try { + File sourceimage = new File(location); + return ImageIO.read(sourceimage); + } catch (IOException e) { + return null; + } + } + + /** + * Gets an image from the given URL. + * + * @param url Url of th image. + * @return image from location. + */ + public Image getURLImage(String url) { + try { + URL sourceimage = new URL(url); + return ImageIO.read(sourceimage); + } catch (IOException e) { + return null; + } + } +} diff --git a/src/main/java/org/parabot/api/io/ProgressListener.java b/src/main/java/org/parabot/api/io/ProgressListener.java new file mode 100644 index 0000000..b23c1d5 --- /dev/null +++ b/src/main/java/org/parabot/api/io/ProgressListener.java @@ -0,0 +1,44 @@ +package org.parabot.api.io; + +/** + * Keeps track of a progress + * + * @author Everel + */ +public interface ProgressListener { + + /** + * Called when progress increased + * + * @param value + */ + void onProgressUpdate(double value); + + /** + * Updates upload speed + * + * @param mbPerSecond + */ + void updateDownloadSpeed(double mbPerSecond); + + /** + * Sets the message displayed + * @param message The Text to show + */ + void updateMessage(String message); + + /** + * Combination of the two + * @param message + * @param progress + */ + void updateMessageAndProgress(String message, double progress); + + /** + * Usage would be + *
double before = getCurrentProgress() 
setCurrent(0)
for 0 .. 100: setCurrent(x)
done!
setCurrent(before)
+ * @return + */ + double getCurrentProgress(); + +} diff --git a/src/main/java/org/parabot/api/io/SizeInputStream.java b/src/main/java/org/parabot/api/io/SizeInputStream.java new file mode 100644 index 0000000..7d0bace --- /dev/null +++ b/src/main/java/org/parabot/api/io/SizeInputStream.java @@ -0,0 +1,62 @@ +package org.parabot.api.io; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Everel + */ +public class SizeInputStream extends InputStream { + public int bytesRead; + private ProgressListener l; + private InputStream in; + private long startTime; + private double size; + + public SizeInputStream(InputStream in, int size, ProgressListener l) { + this.in = in; + this.size = size; + this.l = l; + this.startTime = System.currentTimeMillis(); + } + + public int available() { + return ((int) size - bytesRead); + } + + public int read() throws IOException { + int b = in.read(); + if (b != -1) { + bytesRead++; + } + updateListener(); + return b; + } + + public int read(byte[] b) throws IOException { + int read = in.read(b); + bytesRead += read; + updateListener(); + return read; + } + + public int read(byte[] b, int off, int len) throws IOException { + int read = in.read(b, off, len); + bytesRead += read; + updateListener(); + return read; + } + + private void updateListener() { + if (l != null) { + double percent = (bytesRead / size) * 100.0D; + l.onProgressUpdate(percent); + + long curTime = System.currentTimeMillis(); + double timeSeconds = (curTime - startTime) / 1000.0D; + double speed = bytesRead / (1024.0D * 1024.0D) / timeSeconds; + l.updateDownloadSpeed(speed); + } + } +} + diff --git a/src/main/java/org/parabot/api/io/WebUtil.java b/src/main/java/org/parabot/api/io/WebUtil.java new file mode 100644 index 0000000..6eaa746 --- /dev/null +++ b/src/main/java/org/parabot/api/io/WebUtil.java @@ -0,0 +1,330 @@ +package org.parabot.api.io; + +import org.json.simple.parser.JSONParser; +import org.parabot.api.output.Verboser; + +import java.io.*; +import java.net.*; + +/** + * A WebUtil class fetches data from an URL + * + * @author Everel + */ +public class WebUtil { + + private static JSONParser jsonParser; + + private static String agent = "Mozilla/5.0 (Wind0ws NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1"; + + /** + * Gets useragent + * + * @return useragent + */ + public static String getUserAgent() { + return agent; + } + + /** + * Agent to set at a URL connection + * + * @param userAgent + */ + public static void setUserAgent(final String userAgent) { + agent = userAgent; + } + + /** + * Fetches content of a page + * + * @param location + * @return contents of page + * @throws MalformedURLException + */ + public static String getContents(final String location) + throws MalformedURLException { + return getContents(new URL(location)); + } + + public static String getContents(final String location, String parameters) throws MalformedURLException { + return getContents(new URL(location), parameters); + } + + /** + * Get contents from URL + * + * @param url + * @return page contents + */ + public static String getContents(final URL url) { + return getContents(getConnection(url)); + } + + public static String getContents(final URL url, final String parameters) { + return getContents(getConnection(url), parameters); + } + + /** + * Gets contents from URLConnection + * + * @param urlConnection + * @return page contents + */ + public static String getContents(URLConnection urlConnection) { + try { + final BufferedReader in = getReader(urlConnection); + final StringBuilder builder = new StringBuilder(); + String line; + if (in != null) { + while ((line = in.readLine()) != null) { + builder.append(line); + } + in.close(); + } + return builder.toString(); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + public static String getContents(URLConnection urlConnection, String parameters) { + try { + urlConnection.setDoOutput(true); + OutputStreamWriter wr = new OutputStreamWriter(urlConnection.getOutputStream()); + wr.write(parameters); + wr.flush(); + wr.close(); + + final BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); + final StringBuilder builder = new StringBuilder(); + String line; + while ((line = in.readLine()) != null) { + builder.append(line); + } + return builder.toString(); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + /** + * Gets buffered reader from string url + * + * @param url + * @return bufferedreader + */ + public static BufferedReader getReader(final String url) { + try { + return getReader(new URL(url)); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Gets BufferedReader from URL + * + * @param url + * @return BufferedReader from URL + */ + public static BufferedReader getReader(final URL url) { + return getReader(getConnection(url)); + } + + public static BufferedReader getReader(final URLConnection urlConnection) { + try { + return new BufferedReader(new InputStreamReader( + urlConnection.getInputStream())); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + /** + * Gets inputstream from url + * + * @param url + * @return inputstream from url + */ + public static InputStream getInputStream(final URL url) { + final URLConnection con = getConnection(url); + try { + return con.getInputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + /** + * Opens a connection + * + * @param url + * @return URLConnection to URL + */ + public static URLConnection getConnection(final URL url) { + try { + final URLConnection con = url.openConnection(); + con.setRequestProperty("User-Agent", agent); + return con; + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + public static URLConnection getConnection(final URL url, String parameters) { + try { + final URLConnection con = getConnection(url); + con.setDoOutput(true); + OutputStreamWriter wr = new OutputStreamWriter(con.getOutputStream()); + wr.write(parameters); + wr.flush(); + wr.close(); + + return con; + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + public static BufferedReader getReader(final URL url, String username, String password) { + try { + String data = URLEncoder.encode("username", "UTF-8") + "=" + username; + data += "&" + URLEncoder.encode("password", "UTF-8") + "=" + password; + + URLConnection connection = url.openConnection(); + + connection.setDoOutput(true); + OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream()); + wr.write(data); + wr.flush(); + wr.close(); + + return new BufferedReader(new InputStreamReader(connection.getInputStream())); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + public static URLConnection getConnection(final URL url, String username, String password) { + try { + String data = URLEncoder.encode("username", "UTF-8") + "=" + username; + data += "&" + URLEncoder.encode("password", "UTF-8") + "=" + password; + + URLConnection connection = url.openConnection(); + + connection.setDoOutput(true); + OutputStreamWriter wr = new OutputStreamWriter(connection.getOutputStream()); + wr.write(data); + wr.flush(); + wr.close(); + + return connection; + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + /** + * Downloads a file on the internet + * + * @param url + * @param destination + */ + public static void downloadFile(final URL url, final File destination) { + downloadFile(url, destination, null, null, null); + } + + /** + * Downloads a file on the internet + * + * @param url + * @param destination + * @param listener + */ + public static void downloadFile(final URL url, final File destination, + final ProgressListener listener) { + downloadFile(url, destination, listener, null, null); + } + + /** + * Downloads a file on the internet + * + * @param url + * @param destination + * @param listener + */ + public static void downloadFile(final URL url, final File destination, + final ProgressListener listener, String username, String password) { + try { + final URLConnection connection; + if (username == null || password == null) { + connection = getConnection(url); + } else { + connection = getConnection(url, username, password); + } + final int size = connection.getContentLength(); + SizeInputStream sizeInputStream = new SizeInputStream( + connection.getInputStream(), size, listener); + BufferedInputStream in = new BufferedInputStream(sizeInputStream); + FileOutputStream fileOut = new FileOutputStream(destination); + final double before = listener == null ? 0d : listener.getCurrentProgress(); + final long startTime = System.currentTimeMillis(); + int totalRead = 0; + try { + byte data[] = new byte[1024]; + int count; + while ((count = in.read(data, 0, 1024)) != -1) { + fileOut.write(data, 0, count); + totalRead += count; + if (listener != null) { + double progress = (((double)totalRead / (double)size) * 100d); + String rate = (totalRead / Math.max(1, ((System.currentTimeMillis()-startTime)/1000))) + "/s"; + listener.updateMessageAndProgress(String.format("[%s] Downloading... %02f%% (%s of %s bytes) %s from %s to %s", WebUtil.class.getName(), progress, totalRead, size, rate, url.getPath(), destination.getAbsolutePath()), + progress); + } + } + } finally { + in.close(); + fileOut.close(); + } + if (listener != null) { + listener.onProgressUpdate(before); + } + Verboser.verbose("[WebUtil] Downloaded " + totalRead + " bytes from " + url + " -> " + destination.getAbsolutePath()); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + /** + * Converts file format to url format + * + * @param file + * @return url to file + */ + public static URL toURL(File file) { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } + + public static JSONParser getJsonParser() { + if (jsonParser == null) { + jsonParser = new JSONParser(); + } + return jsonParser; + } +} \ No newline at end of file diff --git a/src/main/java/org/parabot/api/io/build/BuildPath.java b/src/main/java/org/parabot/api/io/build/BuildPath.java new file mode 100644 index 0000000..bafe195 --- /dev/null +++ b/src/main/java/org/parabot/api/io/build/BuildPath.java @@ -0,0 +1,19 @@ +package org.parabot.api.io.build; + +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +public class BuildPath { + + public static void add(final URL url) { + try { + Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); + method.invoke((URLClassLoader) ClassLoader.getSystemClassLoader(), url); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/parabot/api/io/images/Images.java b/src/main/java/org/parabot/api/io/images/Images.java new file mode 100644 index 0000000..0028ac2 --- /dev/null +++ b/src/main/java/org/parabot/api/io/images/Images.java @@ -0,0 +1,30 @@ +package org.parabot.api.io.images; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.util.HashMap; + +/** + * + * Caches and loads images from resource + * + * @author Everel, JKetelaar + * + */ +public class Images { + private static final HashMap IMAGE_CACHE = new HashMap<>(); + + public static BufferedImage getResource(final String resource) { + if(IMAGE_CACHE.containsKey(resource)) { + return IMAGE_CACHE.get(resource); + } + try { + final BufferedImage img = ImageIO.read(Images.class.getResourceAsStream(resource)); + IMAGE_CACHE.put(resource, img); + return img; + } catch (Throwable t) { + throw new RuntimeException("Failed to load image from resource. " + t.getMessage()); + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/parabot/api/io/libraries/Environment.java b/src/main/java/org/parabot/api/io/libraries/Environment.java new file mode 100644 index 0000000..092dc0c --- /dev/null +++ b/src/main/java/org/parabot/api/io/libraries/Environment.java @@ -0,0 +1,23 @@ +package org.parabot.api.io.libraries; + +import org.parabot.api.io.WebUtil; + +/** + * @author JKetelaar + */ +public class Environment { + + /** + * Loads library into environment + * + * @param library + */ + public static void loadLibrary(Library library) { + if (library.requiresJar()) { + if (!library.hasJar()) { + WebUtil.downloadFile(library.getDownloadLink(), library.getJarFile()); + } + library.init(); + } + } +} diff --git a/src/main/java/org/parabot/api/io/libraries/Library.java b/src/main/java/org/parabot/api/io/libraries/Library.java new file mode 100644 index 0000000..a72c8bf --- /dev/null +++ b/src/main/java/org/parabot/api/io/libraries/Library.java @@ -0,0 +1,67 @@ +package org.parabot.api.io.libraries; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * + * @author Everel + * + */ +public abstract class Library { + + /** + * Determines if this library jar has already been downloaded. + * @return false if library jar has not been downloaded, otherwise true + */ + public boolean hasJar() { + return getJarFile().exists(); + } + + /** + * Adds the library to the buildpath and validates if it has been added + */ + public abstract void init(); + + /** + * Determines if library has been added to the buildpath + * @return true if library has been added to the buildpath, otherwise false. + */ + public abstract boolean isAdded(); + + /** + * Gets the local file target/location of the jar file + * @return local file (target) to library + */ + public abstract File getJarFile(); + + /** + * Gets download url to the library + * @return url + */ + public abstract URL getDownloadLink(); + + /** + * Defines if the system requires a jar + * @return boolean + */ + public abstract boolean requiresJar(); + + + /** + * Fetches URL from {@link Library#getJarFile()} + * @return URL to local library jar file + */ + public URL getJarFileURL() { + try { + return getJarFile().toURI().toURL(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return null; + } + + public abstract String getLibraryName(); + +} diff --git a/src/main/java/org/parabot/api/io/libraries/jpushbullet/JPushBullet.java b/src/main/java/org/parabot/api/io/libraries/jpushbullet/JPushBullet.java new file mode 100644 index 0000000..e4a3fa8 --- /dev/null +++ b/src/main/java/org/parabot/api/io/libraries/jpushbullet/JPushBullet.java @@ -0,0 +1,67 @@ +package org.parabot.api.io.libraries.jpushbullet; + +import org.parabot.api.Configuration; +import org.parabot.api.io.Directories; +import org.parabot.api.io.build.BuildPath; +import org.parabot.api.io.libraries.Library; + +import java.io.File; +import java.net.URL; + +/** + * @author EmmaStone + */ +public class JPushBullet extends Library { + private static boolean valid; + + @Override + public void init() { + if (!hasJar()) { + System.err.println("Failed to load jpushbullet... [jar missing]"); + return; + } + BuildPath.add(getJarFileURL()); + + try { + Class.forName("com.github.sheigutn.pushbullet.Pushbullet"); + valid = true; + } catch (ClassNotFoundException e) { + System.err + .println("Failed to add jpushbullet to build path, or incorrupt download"); + } + } + + @Override + public boolean isAdded() { + return valid; + } + + @Override + public File getJarFile() { + return new File(Directories.getCachePath(), "jpushbullet.jar"); + } + + @Override + public URL getDownloadLink() { + try { + return new URL(Configuration.LIBRARIES_DOWNLOAD + "/JPushBullet"); + } catch (Throwable t) { + t.printStackTrace(); + } + return null; + } + + @Override + public boolean requiresJar() { + return true; + } + + @Override + public String getLibraryName() { + return "JPushBullet"; + } + + public static boolean isValid() { + return valid; + } +} diff --git a/src/main/java/org/parabot/api/misc/JavaUtil.java b/src/main/java/org/parabot/api/misc/JavaUtil.java new file mode 100644 index 0000000..964ee11 --- /dev/null +++ b/src/main/java/org/parabot/api/misc/JavaUtil.java @@ -0,0 +1,15 @@ +package org.parabot.api.misc; + +/** + * @author JKetelaar + */ +public class JavaUtil { + public static double JAVA_VERSION = getVersion(); + + static double getVersion() { + String version = System.getProperty("java.version"); + int pos = version.indexOf('.'); + pos = version.indexOf('.', pos + 1); + return Double.parseDouble(version.substring(0, pos)); + } +} \ No newline at end of file diff --git a/src/main/java/org/parabot/api/misc/OperatingSystem.java b/src/main/java/org/parabot/api/misc/OperatingSystem.java new file mode 100644 index 0000000..2c57000 --- /dev/null +++ b/src/main/java/org/parabot/api/misc/OperatingSystem.java @@ -0,0 +1,26 @@ +package org.parabot.api.misc; + +/** + * This class is used for detecting the user's operating system + * + * @author Everel + */ +public enum OperatingSystem { + + WINDOWS, LINUX, MAC, OTHER; + + public static final OperatingSystem getOS() { + String str = System.getProperty("os.name").toLowerCase(); + if (str.contains("win")) { + return OperatingSystem.WINDOWS; + } + if (str.contains("mac")) { + return OperatingSystem.MAC; + } + if (str.contains("nix") || str.contains("nux")) { + return OperatingSystem.LINUX; + } + return OperatingSystem.OTHER; + } + +} diff --git a/src/main/java/org/parabot/api/misc/ProjectProperties.java b/src/main/java/org/parabot/api/misc/ProjectProperties.java new file mode 100644 index 0000000..5edfc9a --- /dev/null +++ b/src/main/java/org/parabot/api/misc/ProjectProperties.java @@ -0,0 +1,44 @@ +package org.parabot.api.misc; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * @author JKetelaar + */ +public class ProjectProperties { + + private static ProjectProperties instance; + private Properties cached = new Properties(); + + private ProjectProperties() { + setProperties(); + } + + public static Version getProjectVersion() { + return new Version(getInstance().getCached().getProperty("application.version")); + } + + public static ProjectProperties getInstance() { + return instance == null ? instance = new ProjectProperties() : instance; + } + + private void setProperties() { + InputStream input; + try { + String propertiesFileName = "storage/internal.properties"; + + input = getClass().getClassLoader() + .getResourceAsStream(propertiesFileName); + + cached.load(input); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private Properties getCached() { + return cached; + } +} diff --git a/src/main/java/org/parabot/api/misc/StringUtil.java b/src/main/java/org/parabot/api/misc/StringUtil.java new file mode 100644 index 0000000..54bdc33 --- /dev/null +++ b/src/main/java/org/parabot/api/misc/StringUtil.java @@ -0,0 +1,117 @@ +package org.parabot.api.misc; + +/** + * @author mkyong, JKetelaar + */ +public class StringUtil { + + private static java.util.Random random = new java.util.Random(); + private static char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".toCharArray(); + + public static String implode(String separator, String... data) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < data.length - 1; i++) { + //data.length - 1 => to not add separator at the end + if (!data[i].matches(" *")) {//empty string are ""; " "; " "; and so on + sb.append(data[i]); + sb.append(separator); + } + } + sb.append(data[data.length - 1].trim()); + return sb.toString(); + } + + public static String convertHexToString(String hex) { + + StringBuilder sb = new StringBuilder(); + StringBuilder temp = new StringBuilder(); + + for (int i = 0; i < hex.length() - 1; i += 2) { + + // grab the hex in pairs + String output = hex.substring(i, (i + 2)); + // convert hex to decimal + int decimal = Integer.parseInt(output, 16); + // convert the decimal to character + sb.append((char) decimal); + + temp.append(decimal); + } + + return sb.toString(); + } + + public static String randomString(final int length) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < 20; i++) { + char c = chars[random.nextInt(chars.length)]; + sb.append(c); + } + return sb.toString(); + } + + public static long longForName(String name) { + long l = 0L; + for (int i = 0; i < name.length() && i < 12; i++) { + char c = name.charAt(i); + l *= 37L; + if (c >= 'A' && c <= 'Z') { + l += (1 + c) - 65; + } else if (c >= 'a' && c <= 'z') { + l += (1 + c) - 97; + } else if (c >= '0' && c <= '9') { + l += (27 + c) - 48; + } + } + + while (l % 37L == 0L && l != 0L) { + l /= 37L; + } + + return l; + } + + public static String nameForLong(long name) { + try { + if (name <= 0L || name >= 0x5b5b57f8a98a5dd1L) { + return "invalid_name"; + } + if (name % 37L == 0L) { + return "invalid_name"; + } + + int i = 0; + char ac[] = new char[12]; + while (name != 0L) { + long l1 = name; + name /= 37L; + ac[11 - i++] = chars[(int) (l1 - name * 37L)]; + } + return new String(ac, 12 - i, i); + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + public static String fixName(String name) { + if (name.length() > 0) { + char ac[] = name.toCharArray(); + for (int j = 0; j < ac.length; j++) + if (ac[j] == '_') { + ac[j] = ' '; + if (j + 1 < ac.length && ac[j + 1] >= 'a' && ac[j + 1] <= 'z') { + ac[j + 1] = (char) ((ac[j + 1] + 65) - 97); + } + } + + if (ac[0] >= 'a' && ac[0] <= 'z') { + ac[0] = (char) ((ac[0] + 65) - 97); + } + return new String(ac); + } else { + return name; + } + } +} \ No newline at end of file diff --git a/src/main/java/org/parabot/api/misc/Version.java b/src/main/java/org/parabot/api/misc/Version.java new file mode 100644 index 0000000..b55bcc4 --- /dev/null +++ b/src/main/java/org/parabot/api/misc/Version.java @@ -0,0 +1,55 @@ +package org.parabot.api.misc; + +public class Version implements Comparable { + + private String version; + + public Version(String version) { + if (version == null) { + throw new IllegalArgumentException("Version can not be null"); + } + + if (!version.matches("[0-9]+(\\.[0-9]+)*") && !version.matches("[0-9]+(\\.[0-9]+)*-RC-([0-9]+)")) { + throw new IllegalArgumentException("Invalid version format"); + } + this.version = version; + } + + public final String get() { + return this.version; + } + + public boolean isNightly() { + return this.version.contains("RC"); + } + + @Override + public int compareTo(Version that) { + if (that == null) { + return 1; + } + + String[] thisParts = this.get().split("\\."); + String[] thatParts = that.get().split("\\."); + int length = Math.max(thisParts.length, thatParts.length); + + for (int i = 0; i < length; i++) { + int thisPart = i < thisParts.length ? + Integer.parseInt(thisParts[i]) : 0; + int thatPart = i < thatParts.length ? + Integer.parseInt(thatParts[i]) : 0; + if (thisPart < thatPart) { + return -1; + } + if (thisPart > thatPart) { + return 1; + } + } + return 0; + } + + @Override + public boolean equals(Object that) { + return this == that || that != null && this.getClass() == that.getClass() && this.compareTo((Version) that) == 0; + } +} \ No newline at end of file diff --git a/src/main/java/org/parabot/api/notifications/NotificationManager.java b/src/main/java/org/parabot/api/notifications/NotificationManager.java new file mode 100644 index 0000000..08d0b21 --- /dev/null +++ b/src/main/java/org/parabot/api/notifications/NotificationManager.java @@ -0,0 +1,113 @@ +package org.parabot.api.notifications; + +import org.parabot.api.notifications.types.MacNotificationType; +import org.parabot.api.notifications.types.NotificationType; +import org.parabot.api.notifications.types.PushBulletNotificationType; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * @author JKetelaar + */ +public class NotificationManager { + + private static NotificationManager context; + + private ArrayList notificationTypes; + private ArrayList enabledTypes; + + public NotificationManager() { + this.notificationTypes = new ArrayList<>(); + this.enabledTypes = new ArrayList<>(); + + this.fillNotificationTypes(); + } + + public ArrayList getNotificationTypes() { + return notificationTypes; + } + + public void addNotificationType(NotificationType type) { + this.notificationTypes.add(type); + } + + private void fillNotificationTypes() { + this.notificationTypes.add(new MacNotificationType()); + this.notificationTypes.add(new PushBulletNotificationType()); + } + + public void enableNotificationType(NotificationType type) { + this.enabledTypes.add(type); + type.enable(); + } + + public void disableNotificationType(NotificationType type) { + Iterator iterator = this.enabledTypes.iterator(); + while(iterator.hasNext()){ + NotificationType t = (NotificationType) iterator.next(); + if (t.getName().equalsIgnoreCase(type.getName())){ + iterator.remove(); + break; + } + } + } + + public void sendNotification(String message) { + for (NotificationType notificationType : this.enabledTypes) { + notificationType.notify(message); + } + } + + public void sendNotification(String header, String message) { + for (NotificationType notificationType : this.enabledTypes) { + notificationType.notify(header, message); + } + } + + public void sendNotification(String title, String header, String message) { + for (NotificationType notificationType : this.enabledTypes) { + notificationType.notify(title, header, message); + } + } + + public ArrayList getEnabledTypes() { + return enabledTypes; + } + + public ArrayList getAvailableNotificationTypes() { + ArrayList types = new ArrayList<>(); + for (NotificationType notificationType : this.notificationTypes) { + boolean inAdded = false; + for (NotificationType enabledType : this.enabledTypes) { + if (enabledType.getName().equalsIgnoreCase(notificationType.getName())) { + inAdded = true; + } + } + + if (!inAdded) { + types.add(notificationType); + } + } + + return types; + } + + public NotificationType getNotificationType(String name) { + for (NotificationType notificationType : this.notificationTypes) { + if (notificationType.getName().equalsIgnoreCase(name)) { + return notificationType; + } + } + + return null; + } + + public static NotificationManager getContext() { + if (context == null) { + context = new NotificationManager(); + } + + return context; + } +} diff --git a/src/main/java/org/parabot/api/notifications/types/MacNotificationType.java b/src/main/java/org/parabot/api/notifications/types/MacNotificationType.java new file mode 100644 index 0000000..8c23696 --- /dev/null +++ b/src/main/java/org/parabot/api/notifications/types/MacNotificationType.java @@ -0,0 +1,43 @@ +package org.parabot.api.notifications.types; + +import org.parabot.api.misc.OperatingSystem; + +import java.io.IOException; + +/** + * @author JKetelaar + */ +public class MacNotificationType extends NotificationType { + + public MacNotificationType() { + super("Mac"); + } + + @Override + public boolean isAvailable() { + return OperatingSystem.getOS().equals(OperatingSystem.MAC); + } + + @Override + public void notify(String title, String header, String message) { + final StringBuilder src = new StringBuilder(); + + src.append("display notification") + .append(" \"").append(message).append("\""); + if (title != null && title.length() > 0) { + src.append(" with title ").append("\"").append(title).append("\""); + } + if (header != null && header.length() > 0) { + src.append(" subtitle ").append("\"").append(header).append("\""); + } + src.append(" sound name \"Ping\" "); + + final Runtime rt = Runtime.getRuntime(); + final String[] cmd = new String[]{"/usr/bin/osascript", "-e", src.toString()}; + try { + rt.exec(cmd); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/parabot/api/notifications/types/NotificationType.java b/src/main/java/org/parabot/api/notifications/types/NotificationType.java new file mode 100644 index 0000000..dbe1464 --- /dev/null +++ b/src/main/java/org/parabot/api/notifications/types/NotificationType.java @@ -0,0 +1,33 @@ +package org.parabot.api.notifications.types; + +import org.parabot.api.Configuration; + +/** + * @author JKetelaar + */ +public abstract class NotificationType { + + private String name; + + public NotificationType(String name) { + this.name = name; + } + + public abstract boolean isAvailable(); + + public abstract void notify(String title, String header, String message); + + public void notify(String header, String message){ + notify(Configuration.BOT_TITLE, header, message); + } + + public void notify(String message){ + notify("Notification", message); + } + + public String getName() { + return name; + } + + public void enable(){} +} diff --git a/src/main/java/org/parabot/api/notifications/types/PushBulletNotificationType.java b/src/main/java/org/parabot/api/notifications/types/PushBulletNotificationType.java new file mode 100644 index 0000000..24e20db --- /dev/null +++ b/src/main/java/org/parabot/api/notifications/types/PushBulletNotificationType.java @@ -0,0 +1,55 @@ +package org.parabot.api.notifications.types; + +import org.parabot.api.io.libraries.Environment; +import org.parabot.api.io.libraries.jpushbullet.JPushBullet; +import org.parabot.api.notifications.types.pushbullet.PushBulletController; + +import javax.swing.*; + +/** + * @author JKetelaar + */ +public class PushBulletNotificationType extends NotificationType { + + private boolean enabled = false; + + public PushBulletNotificationType() { + super("PushBullet"); + } + + @Override + public boolean isAvailable() { + double version = 1.0; + + try { + version = Double.parseDouble(System.getProperty("java.specification.version")); + } catch (NumberFormatException ignored) { + } + + return version >= 1.8; + } + + @Override + public void enable() { + final String message = "Please insert your PushBullet API key, so we could send notifications."; + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + String s = JOptionPane.showInputDialog(null, message, "PushBullet API key", JOptionPane.QUESTION_MESSAGE); + + if (s != null) { + Environment.loadLibrary(new JPushBullet()); + enabled = PushBulletController.pushNote("Parabot", "PushBullets notifications have been enabled for Parabot", s); + } + } + }); + } + + @Override + public void notify(String title, String header, String message) { + if (this.enabled) { + PushBulletController.pushNote(title, message); + } + } +} diff --git a/src/main/java/org/parabot/api/notifications/types/pushbullet/PushBulletController.java b/src/main/java/org/parabot/api/notifications/types/pushbullet/PushBulletController.java new file mode 100644 index 0000000..3471a3e --- /dev/null +++ b/src/main/java/org/parabot/api/notifications/types/pushbullet/PushBulletController.java @@ -0,0 +1,52 @@ +package org.parabot.api.notifications.types.pushbullet; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * @author JKetelaar + */ +public class PushBulletController { + + private static Object pushBulletInstance; + + public static void setPushBulletInstance(String key){ + ClassLoader classLoader = PushBulletController.class.getClassLoader(); + try { + PushBulletController.pushBulletInstance = classLoader.loadClass("com.github.sheigutn.pushbullet.Pushbullet").getConstructors()[0].newInstance(key); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + e.printStackTrace(); + } + } + + public static Object getPushBulletInstance(String key){ + PushBulletController.setPushBulletInstance(key); + + return PushBulletController.pushBulletInstance; + } + + public static boolean pushNote(String title, String message, String key){ + PushBulletController.setPushBulletInstance(key); + + return PushBulletController.pushNote(title, message); + } + + public static boolean pushNote(String title, String message){ + Class[] cArg = new Class[2]; + cArg[0] = String.class; + cArg[1] = String.class; + + if (PushBulletController.pushBulletInstance != null){ + try { + Method method = PushBulletController.pushBulletInstance.getClass().getMethod("pushNote", cArg); + method.setAccessible(true); + method.invoke(PushBulletController.pushBulletInstance, title, message); + + return true; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + return false; + } + } + return false; + } +} diff --git a/src/main/java/org/parabot/api/output/Logger.java b/src/main/java/org/parabot/api/output/Logger.java new file mode 100644 index 0000000..7a171b9 --- /dev/null +++ b/src/main/java/org/parabot/api/output/Logger.java @@ -0,0 +1,94 @@ +package org.parabot.api.output; + +/** + * @author Fryslan + */ +public class Logger { + + public enum LoggerColor { + RESET("\u001B[0m"), + BLACK("\u001B[30m"), + WHITE("\u001B[37m"), + RED("\u001B[31m"), + GREEN("\u001B[32m"), + YELLOW("\u001B[33m"), + BLUE("\u001B[34m"), + PURPLE("\u001B[35m"), + CYAN("\u001B[36m"); + + private final String ansi; + + LoggerColor(String ansi) { + this.ansi = ansi; + } + + public String getAnsi() { + return ansi; + } + } + + + /** + * Prints Error data in Red. + * + * @param tag Reference to the location , I.E Core, UserInterface. + * @param data Error data. + */ + public static void error(String tag, String data) { + System.out.println(LoggerColor.RED.getAnsi() + "[ERROR] : " + tag + " - " + data + LoggerColor.RESET.getAnsi()); + } + + /** + * Prints Error data in Red. + * + * @param tag Reference to the location , I.E Core, UserInterface. + * @param data Error data. + * @param throwable Error or Exception. + */ + public static void error(String tag, String data, Throwable throwable) { + System.out.println(LoggerColor.RED.getAnsi() + "[ERROR] : " + tag + " - " + data); + throwable.printStackTrace(); + System.out.println(LoggerColor.RESET.getAnsi()); + + } + + /** + * Prints Information data in Blue. + * + * @param tag Reference to the location , I.E Core, UserInterface. + * @param data Information data. + */ + public static void info(String tag, String data) { + System.out.println(LoggerColor.BLUE.getAnsi() + "[INFO] : " + tag + " - " + data + LoggerColor.RESET.getAnsi()); + } + + /** + * Prints Warning data in Yellow. + * + * @param tag Reference to the location , I.E Core, UserInterface. + * @param data Warning data. + */ + public static void warning(String tag, String data) { + System.out.println(LoggerColor.YELLOW.getAnsi() + "[WARNING] : " + tag + " - " + data + LoggerColor.RESET.getAnsi()); + } + + /** + * Prints Debug data in Green, if debugging is enabled. + * + * @param tag Reference to the location , I.E Core, UserInterface. + * @param data Debug data. + */ + public static void debug(String tag, String data) { + System.out.println(LoggerColor.GREEN.getAnsi() + "[DEBUG] : " + tag + " - " + data + LoggerColor.RESET.getAnsi()); + } + + /** + * @param tag Reference to the location , I.E Core, UserInterface. + * @param data Data to Print. + * @param color Color to print the the data in. + * @param header Reference to the reason this message is printed I.E Warning, Debug. + */ + public static void custom(String tag, String data, LoggerColor color, String header) { + System.out.println(color.getAnsi() + "[" + header.toUpperCase() + "] : " + tag + " - " + data + LoggerColor.RESET.getAnsi()); + } +} diff --git a/src/main/java/org/parabot/api/output/Verboser.java b/src/main/java/org/parabot/api/output/Verboser.java new file mode 100644 index 0000000..538575f --- /dev/null +++ b/src/main/java/org/parabot/api/output/Verboser.java @@ -0,0 +1,10 @@ +package org.parabot.api.output; + +/** + * @author JKetelaar + */ +public class Verboser { + public static void verbose(String s) { + System.out.println(s); + } +} diff --git a/src/main/java/org/parabot/api/ui/JavaFxUtil.java b/src/main/java/org/parabot/api/ui/JavaFxUtil.java new file mode 100644 index 0000000..9097369 --- /dev/null +++ b/src/main/java/org/parabot/api/ui/JavaFxUtil.java @@ -0,0 +1,142 @@ +package org.parabot.api.ui; + +import javafx.application.Platform; +import javafx.embed.swing.JFXPanel; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.Pane; +import org.parabot.api.io.ProgressListener; +import org.parabot.api.io.WebUtil; +import org.parabot.api.output.Verboser; + +import javax.swing.*; +import java.awt.event.WindowAdapter; +import java.io.File; +import java.io.IOException; +import java.net.URL; + +/** + * If a JavaFX program runs as an "Application" it can only be launched once due to + * how the UI threadding works. To solve this, JavaFX should be used by embedded the FX content inside an FXPanel, + * which itself should just fill the content of a empty JFrame. + * + * @author Shadowrs + */ +public abstract class JavaFxUtil { + + private JFrame frame; + private JFXPanel jfxp; + private JavaFxUtil instance; + private final URL end; + private final File target; + private final Class controller; + private final ProgressListener listener; + + /** + * Constructor to use when stylesheet.fxml is in the Parabot Jar + * + * @param fxmlSheet Location to .fxml such as "/storage/ui/notifications.fxml" + */ + public JavaFxUtil(final URL fxmlSheet, final Class controller) { + this.end = fxmlSheet; + this.target = null; + this.listener = null; + this.controller = controller; + launchJFX(); + } + + /** + * Constructor to use when stylesheet.fxml requires downloading from a remote target. + * + * @param endpoint + * @param target + * @param listener + * @param controller + */ + public JavaFxUtil(URL endpoint, final File target, final ProgressListener listener, final Class controller) { + this.end = endpoint; + this.target = target; + this.listener = listener; + this.controller = controller; + + if (!target.exists() || !target.canRead()) { + WebUtil.downloadFile(end, target, listener); + } + Verboser.verbose("ui from " + end); + + launchJFX(); + } + + /** + * Kick off the GUI by creating a JFrame on the Swing Thread, then load the JavaFX on the Platform Thread + */ + private void launchJFX() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + createJFrame(); + Platform.setImplicitExit(false); + Platform.runLater(new Runnable() { + @Override + public void run() { + addJavaFX(); + } + }); + } + }); + } + + /** + * Create the JFrame and attach the window listener + */ + private void createJFrame() { + Verboser.verbose("Creating JFrame for JavaFXPanel"); + if (getFrame() != null) { + System.err.println("frame exists"); + return; + } + frame = new JFrame(); + jfxp = new JFXPanel(); + getFrame().add(jfxp); + getFrame().setSize(230, 300); + getFrame().setLocationRelativeTo(null); + getFrame().setVisible(true); + + getFrame().setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); + getFrame().addWindowListener(getWindowAdapter()); + Verboser.verbose("frame created: " + getFrame()); + } + + protected abstract WindowAdapter getWindowAdapter(); + + /** + * Add the JavaFX based on the controller and stylesheet to the Scene of the JavaFxPanel + */ + private void addJavaFX() { + Verboser.verbose("panel init"); + instance = this; + try { + FXMLLoader loader = new FXMLLoader(end); // ui.fxml + if (loader.getController() == null) { + loader.setController(controller.newInstance()); + } + Pane page = (Pane) loader.load(); + + Scene scene = new Scene(page); + jfxp.setScene(scene); + getFrame().pack(); + + onLaunched(); + Verboser.verbose("UI showing"); + } catch (IOException | InstantiationException | IllegalAccessException e) { + System.err.println("Error loading ui.fxml!"); + e.printStackTrace(); + } + } + + protected abstract void onLaunched(); + + public JFrame getFrame() { + return frame; + } +} diff --git a/src/main/java/org/parabot/api/ui/SwingUtil.java b/src/main/java/org/parabot/api/ui/SwingUtil.java new file mode 100644 index 0000000..f9e5659 --- /dev/null +++ b/src/main/java/org/parabot/api/ui/SwingUtil.java @@ -0,0 +1,66 @@ +package org.parabot.api.ui; + +import org.parabot.api.io.images.Images; +import org.parabot.api.misc.JavaUtil; +import org.parabot.api.misc.OperatingSystem; + +import javax.swing.*; +import java.awt.*; +import java.lang.reflect.Method; + +/** + * Holds various swing util based methods + * + * @author Dane, JKetelaar + */ +public class SwingUtil { + + /** + * Packs, centers, and shows the frame. + * + * @param f + */ + public static void finalize(JFrame f) { + f.pack(); + f.setLocationRelativeTo(null); + f.setVisible(true); + } + + /** + * Adds the dock icon to mac users + * + * @param f + */ + public static void setParabotIcons(JFrame f) { + setParabotIcon(f); + } + + /** + * Adds the dock icon to mac users + * + * @param f + */ + public static boolean setParabotIcon(JFrame f) { + f.setIconImage(Images.getResource("/storage/images/icon.png")); + + if (OperatingSystem.getOS() == OperatingSystem.MAC) { + Image image = Images.getResource("/storage/images/icon.png"); + try { + if (JavaUtil.JAVA_VERSION >= 9) { + Class taskbar = Class.forName("java.awt.Taskbar"); + Object application = taskbar.getMethod("getTaskbar").invoke(null); + taskbar.getMethod("setIconImage", Image.class).invoke(application, image); + } else { + Class util = Class.forName("com.apple.eawt.Application"); + Object application = util.getMethod("getApplication").invoke(null); + Method setDockIconImage = util.getMethod("setDockIconImage", Image.class); + setDockIconImage.invoke(application, Images.getResource("/storage/images/icon.png")); + } + } catch (Throwable t) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/main/resources/storage/images/icon.png b/src/main/resources/storage/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..33ef49f89d9064a95299ac926a96623bea3c80d1 GIT binary patch literal 77476 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
    pW&(L>A^=I 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πSMI0ptYoJaIe#YRNr(VoE-l6H-uQ0-?es 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 p6BYoNFk!+1023xm8<_qPU;um$V) 0); + } +}