[
  {
    "path": ".gitignore",
    "content": "# Compiled class file\r\n*.class\r\n\r\n# Log file\r\n*.log\r\n\r\n# BlueJ files\r\n*.ctxt\r\n\r\n# Mobile Tools for Java (J2ME)\r\n.mtj.tmp/\r\n\r\n# Package Files #\r\n*.jar\r\n*.war\r\n*.ear\r\n*.zip\r\n*.tar.gz\r\n*.rar\r\n\r\n# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml\r\nhs_err_pid*\r\n\r\n# Intellij Project Files\r\n/.idea/\r\n/out/\r\n*.iml\r\n\r\n# Testing\r\n/src/test/\r\n/target/"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2017 John A Grosh\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "[version]: https://api.bintray.com/packages/jagrosh/maven/DiscordIPC/images/download.svg\n[download]: https://bintray.com/jagrosh/maven/DiscordIPC/_latestVersion\n[license]: https://img.shields.io/badge/License-Apache%202.0-lightgrey.svg\n\n[ ![version][] ][download]\n[ ![license][] ](https://github.com/jagrosh/DiscordIPC/tree/master/LICENSE)\n\n# DiscordIPC\n\nConnect locally to the Discord client using IPC for a subset of RPC features like Rich Presence and Activity Join/Spectate\n\n\n# Features\n\n- Setting Rich Presence\n- Listen for Join, Spectate, and Join-Request events\n- Detect and specify priority for client build (Stable, PTB, Canary)\n- 100% Java\n\n\n# Getting Started\n\nFirst you'll need to add this project as a dependency. If you're using maven:\n```xml\n  <dependency>\n    <groupId>com.jagrosh</groupId>\n    <artifactId>DiscordIPC</artifactId>\n    <version>LATEST</version>\n  </dependency>\n```\n```xml\n  <repository>\n    <id>central</id>\n    <name>bintray</name>\n    <url>http://jcenter.bintray.com</url>\n  </repository>\n```\nWith gradle:\n```groovy\ndependencies {\n    compile 'com.jagrosh:DiscordIPC:LATEST'\n}\n\nrepositories {\n    jcenter()\n}\n```\n\n# Example\n\nQuick example, assuming you already have a GUI application\n```java\nIPCClient client = new IPCClient(345229890980937739L);\nclient.setListener(new IPCListener(){\n    @Override\n    public void onReady(IPCClient client)\n    {\n        RichPresence.Builder builder = new RichPresence.Builder();\n        builder.setState(\"West of House\")\n            .setDetails(\"Frustration level: Over 9000\")\n            .setStartTimestamp(OffsetDateTime.now())\n            .setLargeImage(\"canary-large\", \"Discord Canary\")\n            .setSmallImage(\"ptb-small\", \"Discord PTB\")\n            .setParty(\"party1234\", 1, 6)\n            .setMatchSecret(\"xyzzy\")\n            .setJoinSecret(\"join\")\n            .setSpectateSecret(\"look\");\n        client.sendRichPresence(builder.build());\n    }\n});\nclient.connect();\n```\n\n### Other Examples\n* [Monster Hunter Gathering Hall App](https://github.com/MHGatheringHall/App) - App for displaying in-game info for a non-PC game series\n\n\n# Official Discord-RPC Bindings\n\nThe official RPC bindings can be found here: https://github.com/discordapp/discord-rpc\n\nA Java wrapper for the official bindings is available here: https://github.com/MinnDevelopment/Java-DiscordRPC\n"
  },
  {
    "path": "pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>com.jagrosh</groupId>\n    <artifactId>DiscordIPC</artifactId>\n    <version>0.4</version>\n    <packaging>jar</packaging>\n    \n    <dependencies>\n        <dependency>\n            <groupId>org.json</groupId>\n            <artifactId>json</artifactId>\n            <version>20230227</version>\n        </dependency>\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n            <version>2.0.7</version>\n        </dependency>\n        <dependency>\n            <groupId>com.kohlschutter.junixsocket</groupId>\n            <artifactId>junixsocket-common</artifactId>\n            <version>2.6.2</version>\n        </dependency>\n        <dependency>\n            <groupId>com.kohlschutter.junixsocket</groupId>\n            <artifactId>junixsocket-native-common</artifactId>\n            <version>2.6.2</version>\n        </dependency>\n    </dependencies>\n    \n    <build>\n        <plugins>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-source-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>attach-sources</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n            <plugin>\n                <groupId>org.apache.maven.plugins</groupId>\n                <artifactId>maven-javadoc-plugin</artifactId>\n                <executions>\n                    <execution>\n                        <id>attach-javadocs</id>\n                        <goals>\n                            <goal>jar</goal>\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n    \n    <properties>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n        <maven.compiler.source>1.8</maven.compiler.source>\n        <maven.compiler.target>1.8</maven.compiler.target>\n    </properties>\n    <name>DiscordIPC</name>\n</project>"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/IPCClient.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jagrosh.discordipc;\n\nimport com.jagrosh.discordipc.entities.*;\nimport com.jagrosh.discordipc.entities.Packet.OpCode;\nimport com.jagrosh.discordipc.entities.pipe.Pipe;\nimport com.jagrosh.discordipc.entities.pipe.PipeStatus;\nimport com.jagrosh.discordipc.exceptions.NoDiscordClientException;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.lang.management.ManagementFactory;\nimport java.util.HashMap;\n\n/**\n * Represents a Discord IPC Client that can send and receive\n * Rich Presence data.<p>\n *\n * The ID provided should be the <b>client ID of the particular\n * application providing Rich Presence</b>, which can be found\n * <a href=https://discordapp.com/developers/applications/me>here</a>.<p>\n *\n * When initially created using {@link #IPCClient(long)} the client will\n * be inactive awaiting a call to {@link #connect(DiscordBuild...)}.<br>\n * After the call, this client can send and receive Rich Presence data\n * to and from discord via {@link #sendRichPresence(RichPresence)} and\n * {@link #setListener(IPCListener)} respectively.<p>\n *\n * Please be mindful that the client created is initially unconnected,\n * and calling any methods that exchange data between this client and\n * Discord before a call to {@link #connect(DiscordBuild...)} will cause\n * an {@link IllegalStateException} to be thrown.<br>\n * This also means that the IPCClient cannot tell whether the client ID\n * provided is valid or not before a handshake.\n *\n * @author John Grosh (john.a.grosh@gmail.com)\n */\npublic final class IPCClient implements Closeable\n{\n    private static final Logger LOGGER = LoggerFactory.getLogger(IPCClient.class);\n    private final long clientId;\n    private final HashMap<String,Callback> callbacks = new HashMap<>();\n    private volatile Pipe pipe;\n    private IPCListener listener = null;\n    private Thread readThread = null;\n    \n    /**\n     * Constructs a new IPCClient using the provided {@code clientId}.<br>\n     * This is initially unconnected to Discord.\n     *\n     * @param clientId The Rich Presence application's client ID, which can be found\n     *                 <a href=https://discordapp.com/developers/applications/me>here</a>\n     */\n    public IPCClient(long clientId)\n    {\n        this.clientId = clientId;\n    }\n    \n    /**\n     * Sets this IPCClient's {@link IPCListener} to handle received events.<p>\n     *\n     * A single IPCClient can only have one of these set at any given time.<br>\n     * Setting this {@code null} will remove the currently active one.<p>\n     *\n     * This can be set safely before a call to {@link #connect(DiscordBuild...)}\n     * is made.\n     *\n     * @param listener The {@link IPCListener} to set for this IPCClient.\n     *\n     * @see IPCListener\n     */\n    public void setListener(IPCListener listener)\n    {\n        this.listener = listener;\n        if (pipe != null)\n            pipe.setListener(listener);\n    }\n    \n    /**\n     * Opens the connection between the IPCClient and Discord.<p>\n     *\n     * <b>This must be called before any data is exchanged between the\n     * IPCClient and Discord.</b>\n     *\n     * @param preferredOrder the priority order of client builds to connect to\n     *\n     * @throws IllegalStateException\n     *         There is an open connection on this IPCClient.\n     * @throws NoDiscordClientException\n     *         No client of the provided {@link DiscordBuild build type}(s) was found.\n     */\n    public void connect(DiscordBuild... preferredOrder) throws NoDiscordClientException\n    {\n        checkConnected(false);\n        callbacks.clear();\n        pipe = null;\n\n        pipe = Pipe.openPipe(this, clientId, callbacks, preferredOrder);\n\n        LOGGER.debug(\"Client is now connected and ready!\");\n        if(listener != null)\n            listener.onReady(this);\n        startReading();\n    }\n    \n    /**\n     * Sends a {@link RichPresence} to the Discord client.<p>\n     *\n     * This is where the IPCClient will officially display\n     * a Rich Presence in the Discord client.<p>\n     *\n     * Sending this again will overwrite the last provided\n     * {@link RichPresence}.\n     *\n     * @param presence The {@link RichPresence} to send.\n     *\n     * @throws IllegalStateException\n     *         If a connection was not made prior to invoking\n     *         this method.\n     *\n     * @see RichPresence\n     */\n    public void sendRichPresence(RichPresence presence)\n    {\n        sendRichPresence(presence, null);\n    }\n    \n    /**\n     * Sends a {@link RichPresence} to the Discord client.<p>\n     *\n     * This is where the IPCClient will officially display\n     * a Rich Presence in the Discord client.<p>\n     *\n     * Sending this again will overwrite the last provided\n     * {@link RichPresence}.\n     *\n     * @param presence The {@link RichPresence} to send.\n     * @param callback A {@link Callback} to handle success or error\n     *\n     * @throws IllegalStateException\n     *         If a connection was not made prior to invoking\n     *         this method.\n     *\n     * @see RichPresence\n     */\n    public void sendRichPresence(RichPresence presence, Callback callback)\n    {\n        checkConnected(true);\n        LOGGER.debug(\"Sending RichPresence to discord: \"+(presence == null ? null : presence.toJson().toString()));\n        pipe.send(OpCode.FRAME, new JSONObject()\n                            .put(\"cmd\",\"SET_ACTIVITY\")\n                            .put(\"args\", new JSONObject()\n                                        .put(\"pid\",getPID())\n                                        .put(\"activity\",presence == null ? null : presence.toJson())), callback);\n    }\n\n    /**\n     * Adds an event {@link Event} to this IPCClient.<br>\n     * If the provided {@link Event} is added more than once,\n     * it does nothing.\n     * Once added, there is no way to remove the subscription\n     * other than {@link #close() closing} the connection\n     * and creating a new one.\n     *\n     * @param sub The event {@link Event} to add.\n     *\n     * @throws IllegalStateException\n     *         If a connection was not made prior to invoking\n     *         this method.\n     */\n    public void subscribe(Event sub)\n    {\n        subscribe(sub, null);\n    }\n    \n    /**\n     * Adds an event {@link Event} to this IPCClient.<br>\n     * If the provided {@link Event} is added more than once,\n     * it does nothing.\n     * Once added, there is no way to remove the subscription\n     * other than {@link #close() closing} the connection\n     * and creating a new one.\n     *\n     * @param sub The event {@link Event} to add.\n     * @param callback The {@link Callback} to handle success or failure\n     *\n     * @throws IllegalStateException\n     *         If a connection was not made prior to invoking\n     *         this method.\n     */\n    public void subscribe(Event sub, Callback callback)\n    {\n        checkConnected(true);\n        if(!sub.isSubscribable())\n            throw new IllegalStateException(\"Cannot subscribe to \"+sub+\" event!\");\n        LOGGER.debug(String.format(\"Subscribing to Event: %s\", sub.name()));\n        pipe.send(OpCode.FRAME, new JSONObject()\n                            .put(\"cmd\", \"SUBSCRIBE\")\n                            .put(\"evt\", sub.name()), callback);\n    }\n\n    /**\n     * Gets the IPCClient's current {@link PipeStatus}.\n     *\n     * @return The IPCClient's current {@link PipeStatus}.\n     */\n    public PipeStatus getStatus()\n    {\n        if (pipe == null) return PipeStatus.UNINITIALIZED;\n\n        return pipe.getStatus();\n    }\n\n    /**\n     * Attempts to close an open connection to Discord.<br>\n     * This can be reopened with another call to {@link #connect(DiscordBuild...)}.\n     *\n     * @throws IllegalStateException\n     *         If a connection was not made prior to invoking\n     *         this method.\n     */\n    @Override\n    public void close()\n    {\n        checkConnected(true);\n\n        try {\n            pipe.close();\n        } catch (IOException e) {\n            LOGGER.debug(\"Failed to close pipe\", e);\n        }\n    }\n\n    /**\n     * Gets the IPCClient's {@link DiscordBuild}.<p>\n     *\n     * This is always the first specified DiscordBuild when\n     * making a call to {@link #connect(DiscordBuild...)},\n     * or the first one found if none or {@link DiscordBuild#ANY}\n     * is specified.<p>\n     *\n     * Note that specifying ANY doesn't mean that this will return\n     * ANY. In fact this method should <b>never</b> return the\n     * value ANY.\n     *\n     * @return The {@link DiscordBuild} of this IPCClient, or null if not connected.\n     */\n    public DiscordBuild getDiscordBuild()\n    {\n        if (pipe == null) return null;\n\n        return pipe.getDiscordBuild();\n    }\n\n    /**\n     * Constants representing events that can be subscribed to\n     * using {@link #subscribe(Event)}.<p>\n     *\n     * Each event corresponds to a different function as a\n     * component of the Rich Presence.<br>\n     * A full breakdown of each is available\n     * <a href=https://discordapp.com/developers/docs/rich-presence/how-to>here</a>.\n     */\n    public enum Event\n    {\n        NULL(false), // used for confirmation\n        READY(false),\n        ERROR(false),\n        ACTIVITY_JOIN(true),\n        ACTIVITY_SPECTATE(true),\n        ACTIVITY_JOIN_REQUEST(true),\n        /**\n         * A backup key, only important if the\n         * IPCClient receives an unknown event\n         * type in a JSON payload.\n         */\n        UNKNOWN(false);\n        \n        private final boolean subscribable;\n        \n        Event(boolean subscribable)\n        {\n            this.subscribable = subscribable;\n        }\n        \n        public boolean isSubscribable()\n        {\n            return subscribable;\n        }\n        \n        static Event of(String str)\n        {\n            if(str==null)\n                return NULL;\n            for(Event s : Event.values())\n            {\n                if(s != UNKNOWN && s.name().equalsIgnoreCase(str))\n                    return s;\n            }\n            return UNKNOWN;\n        }\n    }\n\n\n    // Private methods\n    \n    /**\n     * Makes sure that the client is connected (or not) depending on if it should\n     * for the current state.\n     *\n     * @param connected Whether to check in the context of the IPCClient being\n     *                  connected or not.\n     */\n    private void checkConnected(boolean connected)\n    {\n        if(connected && getStatus() != PipeStatus.CONNECTED)\n            throw new IllegalStateException(String.format(\"IPCClient (ID: %d) is not connected!\", clientId));\n        if(!connected && getStatus() == PipeStatus.CONNECTED)\n            throw new IllegalStateException(String.format(\"IPCClient (ID: %d) is already connected!\", clientId));\n    }\n    \n    /**\n     * Initializes this IPCClient's {@link IPCClient#readThread readThread}\n     * and calls the first {@link Pipe#read()}.\n     */\n    private void startReading()\n    {\n        readThread = new Thread(() -> {\n            try\n            {\n                Packet p;\n                while((p = pipe.read()).getOp() != OpCode.CLOSE)\n                {\n                    JSONObject json = p.getJson();\n                    Event event = Event.of(json.optString(\"evt\", null));\n                    String nonce = json.optString(\"nonce\", null);\n                    switch(event)\n                    {\n                        case NULL:\n                            if(nonce != null && callbacks.containsKey(nonce))\n                                callbacks.remove(nonce).succeed(p);\n                            break;\n                            \n                        case ERROR:\n                            if(nonce != null && callbacks.containsKey(nonce))\n                                callbacks.remove(nonce).fail(json.getJSONObject(\"data\").optString(\"message\", null));\n                            break;\n                            \n                        case ACTIVITY_JOIN:\n                            LOGGER.debug(\"Reading thread received a 'join' event.\");\n                            break;\n                            \n                        case ACTIVITY_SPECTATE:\n                            LOGGER.debug(\"Reading thread received a 'spectate' event.\");\n                            break;\n                            \n                        case ACTIVITY_JOIN_REQUEST:\n                            LOGGER.debug(\"Reading thread received a 'join request' event.\");\n                            break;\n                            \n                        case UNKNOWN:\n                            LOGGER.debug(\"Reading thread encountered an event with an unknown type: \" +\n                                         json.getString(\"evt\"));\n                            break;\n                    }\n                    if(listener != null && json.has(\"cmd\") && json.getString(\"cmd\").equals(\"DISPATCH\"))\n                    {\n                        try\n                        {\n                            JSONObject data = json.getJSONObject(\"data\");\n                            switch(Event.of(json.getString(\"evt\")))\n                            {\n                                case ACTIVITY_JOIN:\n                                    listener.onActivityJoin(this, data.getString(\"secret\"));\n                                    break;\n                                    \n                                case ACTIVITY_SPECTATE:\n                                    listener.onActivitySpectate(this, data.getString(\"secret\"));\n                                    break;\n                                    \n                                case ACTIVITY_JOIN_REQUEST:\n                                    JSONObject u = data.getJSONObject(\"user\");\n                                    User user = new User(\n                                        u.getString(\"username\"),\n                                        u.getString(\"discriminator\"),\n                                        Long.parseLong(u.getString(\"id\")),\n                                        u.optString(\"avatar\", null)\n                                    );\n                                    listener.onActivityJoinRequest(this, data.optString(\"secret\", null), user);\n                                    break;\n                            }\n                        }\n                        catch(Exception e)\n                        {\n                            LOGGER.error(\"Exception when handling event: \", e);\n                        }\n                    }\n                }\n                pipe.setStatus(PipeStatus.DISCONNECTED);\n                if(listener != null)\n                    listener.onClose(this, p.getJson());\n            }\n            catch(IOException | JSONException ex)\n            {\n                if(ex instanceof IOException)\n                    LOGGER.error(\"Reading thread encountered an IOException\", ex);\n                else\n                    LOGGER.error(\"Reading thread encountered an JSONException\", ex);\n\n                pipe.setStatus(PipeStatus.DISCONNECTED);\n                if(listener != null)\n                    listener.onDisconnect(this, ex);\n            }\n        });\n\n        LOGGER.debug(\"Starting IPCClient reading thread!\");\n        readThread.start();\n    }\n    \n    // Private static methods\n    \n    /**\n     * Finds the current process ID.\n     *\n     * @return The current process ID.\n     */\n    private static int getPID()\n    {\n        String pr = ManagementFactory.getRuntimeMXBean().getName();\n        return Integer.parseInt(pr.substring(0,pr.indexOf('@')));\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/IPCListener.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jagrosh.discordipc;\n\nimport com.jagrosh.discordipc.entities.Packet;\nimport com.jagrosh.discordipc.entities.User;\nimport org.json.JSONObject;\n\n/**\n * An implementable listener used to handle events caught by an {@link IPCClient}.<p>\n *\n * Can be attached to an IPCClient using {@link IPCClient#setListener(IPCListener)}.\n *\n * @author John Grosh (john.a.grosh@gmail.com)\n */\npublic interface IPCListener\n{\n    /**\n     * Fired whenever an {@link IPCClient} sends a {@link Packet} to Discord.\n     *\n     * @param client The IPCClient sending the Packet.\n     * @param packet The Packet being sent.\n     */\n    default void onPacketSent(IPCClient client, Packet packet) {}\n\n    /**\n     * Fired whenever an {@link IPCClient} receives a {@link Packet} to Discord.\n     *\n     * @param client The IPCClient receiving the Packet.\n     * @param packet The Packet being received.\n     */\n    default void onPacketReceived(IPCClient client, Packet packet) {}\n\n    /**\n     * Fired whenever a RichPresence activity informs us that\n     * a user has clicked a \"join\" button.\n     *\n     * @param client The IPCClient receiving the event.\n     * @param secret The secret of the event, determined by the implementation and specified by the user.\n     */\n    default void onActivityJoin(IPCClient client, String secret) {}\n\n    /**\n     * Fired whenever a RichPresence activity informs us that\n     * a user has clicked a \"spectate\" button.\n     *\n     * @param client The IPCClient receiving the event.\n     * @param secret The secret of the event, determined by the implementation and specified by the user.\n     */\n    default void onActivitySpectate(IPCClient client, String secret) {}\n\n    /**\n     * Fired whenever a RichPresence activity informs us that\n     * a user has clicked a \"ask to join\" button.<p>\n     *\n     * As opposed to {@link #onActivityJoin(IPCClient, String)},\n     * this also provides packaged {@link User} data.\n     *\n     * @param client The IPCClient receiving the event.\n     * @param secret The secret of the event, determined by the implementation and specified by the user.\n     * @param user The user who clicked the clicked the event, containing data on the account.\n     */\n    default void onActivityJoinRequest(IPCClient client, String secret, User user) {}\n\n    /**\n     * Fired whenever an {@link IPCClient} is ready and connected to Discord.\n     *\n     * @param client The now ready IPCClient.\n     */\n    default void onReady(IPCClient client) {}\n\n    /**\n     * Fired whenever an {@link IPCClient} has closed.\n     *\n     * @param client The now closed IPCClient.\n     * @param json A {@link JSONObject} with close data.\n     */\n    default void onClose(IPCClient client, JSONObject json) {}\n\n    /**\n     * Fired whenever an {@link IPCClient} has disconnected,\n     * either due to bad data or an exception.\n     *\n     * @param client The now closed IPCClient.\n     * @param t A {@link Throwable} responsible for the disconnection.\n     */\n    default void onDisconnect(IPCClient client, Throwable t) {}\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/Callback.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jagrosh.discordipc.entities;\n\nimport java.util.function.Consumer;\n\n/**\n * A callback for asynchronous logic when dealing processes that\n * would normally block the calling thread.<p>\n *\n * This is most visibly implemented in {@link com.jagrosh.discordipc.IPCClient IPCClient}.\n *\n * @author John Grosh (john.a.grosh@gmail.com)\n */\npublic class Callback\n{\n    private final Consumer<Packet> success;\n    private final Consumer<String> failure;\n\n    /**\n     * Constructs an empty Callback.\n     */\n    public Callback()\n    {\n        this((Consumer<Packet>) null, null);\n    }\n\n    /**\n     * Constructs a Callback with a success {@link Consumer} that\n     * occurs when the process it is attached to executes without\n     * error.\n     *\n     * @param success The Consumer to launch after a successful process.\n     */\n    public Callback(Consumer<Packet> success)\n    {\n        this(success, null);\n    }\n\n    /**\n     * Constructs a Callback with a success {@link Consumer} <i>and</i>\n     * a failure {@link Consumer} that occurs when the process it is\n     * attached to executes without or with error (respectively).\n     *\n     * @param success The Consumer to launch after a successful process.\n     * @param failure The Consumer to launch if the process has an error.\n     */\n    public Callback(Consumer<Packet> success, Consumer<String> failure)\n    {\n        this.success = success;\n        this.failure = failure;\n    }\n\n    /**\n     * @param success The Runnable to launch after a successful process.\n     * @param failure The Consumer to launch if the process has an error.\n     */\n    @Deprecated\n    public Callback(Runnable success, Consumer<String> failure)\n    {\n        this(p -> success.run(), failure);\n    }\n\n    /**\n     * @param success The Runnable to launch after a successful process.\n     */\n    @Deprecated\n    public Callback(Runnable success)\n    {\n        this(p -> success.run(), null);\n    }\n\n    /**\n     * Gets whether or not this Callback is \"empty\" which is more precisely\n     * defined as not having a specified success {@link Consumer} and/or a\n     * failure {@link Consumer}.<br>\n     * This is only true if the Callback is constructed with the parameter-less\n     * constructor ({@link #Callback()}) or another constructor that leaves\n     * one or both parameters {@code null}.\n     *\n     * @return {@code true} if and only if the\n     */\n    public boolean isEmpty()\n    {\n        return success == null && failure == null;\n    }\n\n    /**\n     * Launches the success {@link Consumer}.\n     */\n    public void succeed(Packet packet)\n    {\n        if(success != null)\n            success.accept(packet);\n    }\n\n    /**\n     * Launches the failure {@link Consumer} with the\n     * provided message.\n     *\n     * @param message The message to launch the failure consumer with.\n     */\n    public void fail(String message)\n    {\n        if(failure != null)\n            failure.accept(message);\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/DiscordBuild.java",
    "content": "/*\n * Copyright 2017 Kaidan Gustave\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jagrosh.discordipc.entities;\n\n/**\n * Constants representing various Discord client builds,\n * such as Stable, Canary, Public Test Build (PTB)\n */\npublic enum DiscordBuild\n{\n    /**\n     * Constant for the current Discord Canary release.\n     */\n    CANARY(\"//canary.discordapp.com/api\"),\n\n    /**\n     * Constant for the current Discord Public Test Build or PTB release.\n     */\n    PTB(\"//ptb.discordapp.com/api\"),\n\n    /**\n     * Constant for the current stable Discord release.\n     */\n    STABLE(\"//discordapp.com/api\"),\n\n    /**\n     * 'Wildcard' build constant used in {@link com.jagrosh.discordipc.IPCClient#connect(DiscordBuild...)\n     * IPCClient#connect(DiscordBuild...)} to signify that the build to target is not important, and\n     * that the first valid build will be used.<p>\n     *\n     * Other than this exact function, there is no use for this value.\n     */\n    ANY;\n\n    private final String endpoint;\n\n    DiscordBuild(String endpoint)\n    {\n        this.endpoint = endpoint;\n    }\n\n    DiscordBuild()\n    {\n        this(null);\n    }\n\n    /**\n     * Gets a {@link DiscordBuild} matching the specified endpoint.<p>\n     *\n     * This is only internally implemented.\n     *\n     * @param endpoint The endpoint to get from.\n     *\n     * @return The DiscordBuild corresponding to the endpoint, or\n     *         {@link DiscordBuild#ANY} if none match.\n     */\n    public static DiscordBuild from(String endpoint)\n    {\n        for(DiscordBuild value : values())\n        {\n            if(value.endpoint != null && value.endpoint.equals(endpoint))\n            {\n                return value;\n            }\n        }\n        return ANY;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/Packet.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jagrosh.discordipc.entities;\n\nimport java.nio.ByteBuffer;\nimport java.nio.charset.StandardCharsets;\nimport org.json.JSONObject;\n\n/**\n * A data-packet received from Discord via an {@link com.jagrosh.discordipc.IPCClient IPCClient}.<br>\n * These can be handled via an implementation of {@link com.jagrosh.discordipc.IPCListener IPCListener}.\n *\n * @author John Grosh (john.a.grosh@gmail.com)\n */\npublic class Packet\n{\n    private final OpCode op;\n    private final JSONObject data;\n\n    /**\n     * Constructs a new Packet using an {@link OpCode} and {@link JSONObject}.\n     *\n     * @param op The OpCode value of this new Packet.\n     * @param data The JSONObject payload of this new Packet.\n     */\n    public Packet(OpCode op, JSONObject data)\n    {\n        this.op = op;\n        this.data = data;\n    }\n\n    /**\n     * Converts this {@link Packet} to a {@code byte} array.\n     *\n     * @return This Packet as a {@code byte} array.\n     */\n    public byte[] toBytes()\n    {\n        byte[] d = data.toString().getBytes(StandardCharsets.UTF_8);\n        ByteBuffer packet = ByteBuffer.allocate(d.length + 2*Integer.BYTES);\n        packet.putInt(Integer.reverseBytes(op.ordinal()));\n        packet.putInt(Integer.reverseBytes(d.length));\n        packet.put(d);\n        return packet.array();\n    }\n\n    /**\n     * Gets the {@link OpCode} value of this {@link Packet}.\n     *\n     * @return This Packet's OpCode.\n     */\n    public OpCode getOp()\n    {\n        return op;\n    }\n\n    /**\n     * Gets the {@link JSONObject} value as a part of this {@link Packet}.\n     *\n     * @return The JSONObject value of this Packet.\n     */\n    public JSONObject getJson()\n    {\n        return data;\n    }\n    \n    @Override\n    public String toString()\n    {\n        return \"Pkt:\"+getOp()+getJson().toString();\n    }\n\n    /**\n     * Discord response OpCode values that are\n     * sent with response data to and from Discord\n     * and the {@link com.jagrosh.discordipc.IPCClient IPCClient}\n     * connected.\n     */\n    public enum OpCode\n    {\n        HANDSHAKE, FRAME, CLOSE, PING, PONG\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/RichPresence.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jagrosh.discordipc.entities;\n\nimport java.time.OffsetDateTime;\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\n/**\n * An encapsulation of all data needed to properly construct a JSON RichPresence payload.\n *\n * <p>These can be built using {@link RichPresence.Builder}.\n *\n * @author John Grosh (john.a.grosh@gmail.com)\n */\npublic class RichPresence\n{\n    private final String state;\n    private final String details;\n    private final OffsetDateTime startTimestamp;\n    private final OffsetDateTime endTimestamp;\n    private final String largeImageKey;\n    private final String largeImageText;\n    private final String smallImageKey;\n    private final String smallImageText;\n    private final String partyId;\n    private final int partySize;\n    private final int partyMax;\n    private final String matchSecret;\n    private final String joinSecret;\n    private final String spectateSecret;\n    private final boolean instance;\n    \n    public RichPresence(String state, String details, OffsetDateTime startTimestamp, OffsetDateTime endTimestamp, \n            String largeImageKey, String largeImageText, String smallImageKey, String smallImageText, \n            String partyId, int partySize, int partyMax, String matchSecret, String joinSecret, \n            String spectateSecret, boolean instance)\n    {\n        this.state = state;\n        this.details = details;\n        this.startTimestamp = startTimestamp;\n        this.endTimestamp = endTimestamp;\n        this.largeImageKey = largeImageKey;\n        this.largeImageText = largeImageText;\n        this.smallImageKey = smallImageKey;\n        this.smallImageText = smallImageText;\n        this.partyId = partyId;\n        this.partySize = partySize;\n        this.partyMax = partyMax;\n        this.matchSecret = matchSecret;\n        this.joinSecret = joinSecret;\n        this.spectateSecret = spectateSecret;\n        this.instance = instance;\n    }\n\n    /**\n     * Constructs a {@link JSONObject} representing a payload to send to discord\n     * to update a user's Rich Presence.\n     *\n     * <p>This is purely internal, and should not ever need to be called outside of\n     * the library.\n     *\n     * @return A JSONObject payload for updating a user's Rich Presence.\n     */\n    public JSONObject toJson()\n    {\n        return new JSONObject()\n                .put(\"state\", state)\n                .put(\"details\", details)\n                .put(\"timestamps\", new JSONObject()\n                        .put(\"start\", startTimestamp==null ? null : startTimestamp.toEpochSecond())\n                        .put(\"end\", endTimestamp==null ? null : endTimestamp.toEpochSecond()))\n                .put(\"assets\", new JSONObject()\n                        .put(\"large_image\", largeImageKey)\n                        .put(\"large_text\", largeImageText)\n                        .put(\"small_image\", smallImageKey)\n                        .put(\"small_text\", smallImageText))\n                .put(\"party\", partyId==null ? null : new JSONObject()\n                        .put(\"id\", partyId)\n                        .put(\"size\", new JSONArray().put(partySize).put(partyMax)))\n                .put(\"secrets\", new JSONObject()\n                        .put(\"join\", joinSecret)\n                        .put(\"spectate\", spectateSecret)\n                        .put(\"match\", matchSecret))\n                .put(\"instance\", instance);\n    }\n\n    /**\n     * A chain builder for a {@link RichPresence} object.\n     *\n     * <p>An accurate description of each field and it's functions can be found\n     * <a href=\"https://discordapp.com/developers/docs/rich-presence/how-to#updating-presence-update-presence-payload-fields\">here</a>\n     */\n    public static class Builder\n    {\n        private String state;\n        private String details;\n        private OffsetDateTime startTimestamp;\n        private OffsetDateTime endTimestamp;\n        private String largeImageKey;\n        private String largeImageText;\n        private String smallImageKey;\n        private String smallImageText;\n        private String partyId;\n        private int partySize;\n        private int partyMax;\n        private String matchSecret;\n        private String joinSecret;\n        private String spectateSecret;\n        private boolean instance;\n\n        /**\n         * Builds the {@link RichPresence} from the current state of this builder.\n         *\n         * @return The RichPresence built.\n         */\n        public RichPresence build()\n        {\n            return new RichPresence(state, details, startTimestamp, endTimestamp, \n                    largeImageKey, largeImageText, smallImageKey, smallImageText, \n                    partyId, partySize, partyMax, matchSecret, joinSecret, \n                    spectateSecret, instance);\n        }\n\n        /**\n         * Sets the state of the user's current party.\n         *\n         * @param state The state of the user's current party.\n         *\n         * @return This Builder.\n         */\n        public Builder setState(String state)\n        {\n            this.state = state;\n            return this;\n        }\n\n        /**\n         * Sets details of what the player is currently doing.\n         *\n         * @param details The details of what the player is currently doing.\n         *\n         * @return This Builder.\n         */\n        public Builder setDetails(String details)\n        {\n            this.details = details;\n            return this;\n        }\n\n        /**\n         * Sets the time that the player started a match or activity.\n         *\n         * @param startTimestamp The time the player started a match or activity.\n         *\n         * @return This Builder.\n         */\n        public Builder setStartTimestamp(OffsetDateTime startTimestamp)\n        {\n            this.startTimestamp = startTimestamp;\n            return this;\n        }\n\n        /**\n         * Sets the time that the player's current activity will end.\n         *\n         * @param endTimestamp The time the player's activity will end.\n         *\n         * @return This Builder.\n         */\n        public Builder setEndTimestamp(OffsetDateTime endTimestamp)\n        {\n            this.endTimestamp = endTimestamp;\n            return this;\n        }\n\n        /**\n         * Sets the key of the uploaded image for the large profile artwork, as well as\n         * the text tooltip shown when a cursor hovers over it.\n         *\n         * <p>These can be configured in the <a href=\"https://discordapp.com/developers/applications/me\">applications</a>\n         * page on the discord website.\n         *\n         * @param largeImageKey A key to an image to display.\n         * @param largeImageText Text displayed when a cursor hovers over the large image.\n         *\n         * @return This Builder.\n         */\n        public Builder setLargeImage(String largeImageKey, String largeImageText)\n        {\n            this.largeImageKey = largeImageKey;\n            this.largeImageText = largeImageText;\n            return this;\n        }\n\n        /**\n         * Sets the key of the uploaded image for the large profile artwork.\n         *\n         * <p>These can be configured in the <a href=\"https://discordapp.com/developers/applications/me\">applications</a>\n         * page on the discord website.\n         *\n         * @param largeImageKey A key to an image to display.\n         *\n         * @return This Builder.\n         */\n        public Builder setLargeImage(String largeImageKey)\n        {\n            return setLargeImage(largeImageKey, null);\n        }\n\n        /**\n         * Sets the key of the uploaded image for the small profile artwork, as well as\n         * the text tooltip shown when a cursor hovers over it.\n         *\n         * <p>These can be configured in the <a href=\"https://discordapp.com/developers/applications/me\">applications</a>\n         * page on the discord website.\n         *\n         * @param smallImageKey A key to an image to display.\n         * @param smallImageText Text displayed when a cursor hovers over the small image.\n         *\n         * @return This Builder.\n         */\n        public Builder setSmallImage(String smallImageKey, String smallImageText)\n        {\n            this.smallImageKey = smallImageKey;\n            this.smallImageText = smallImageText;\n            return this;\n        }\n\n        /**\n         * Sets the key of the uploaded image for the small profile artwork.\n         *\n         * <p>These can be configured in the <a href=\"https://discordapp.com/developers/applications/me\">applications</a>\n         * page on the discord website.\n         *\n         * @param smallImageKey A key to an image to display.\n         *\n         * @return This Builder.\n         */\n        public Builder setSmallImage(String smallImageKey)\n        {\n            return setSmallImage(smallImageKey, null);\n        }\n\n        /**\n         * Sets party configurations for a team, lobby, or other form of group.\n         *\n         * <p>The {@code partyId} is ID of the player's party.\n         * <br>The {@code partySize} is the current size of the player's party.\n         * <br>The {@code partyMax} is the maximum number of player's allowed in the party.\n         *\n         * @param partyId The ID of the player's party.\n         * @param partySize The current size of the player's party.\n         * @param partyMax The maximum number of player's allowed in the party.\n         *\n         * @return This Builder.\n         */\n        public Builder setParty(String partyId, int partySize, int partyMax)\n        {\n            this.partyId = partyId;\n            this.partySize = partySize;\n            this.partyMax = partyMax;\n            return this;\n        }\n\n        /**\n         * Sets the unique hashed string for Spectate and Join.\n         *\n         * @param matchSecret The unique hashed string for Spectate and Join.\n         *\n         * @return This Builder.\n         */\n        public Builder setMatchSecret(String matchSecret)\n        {\n            this.matchSecret = matchSecret;\n            return this;\n        }\n\n        /**\n         * Sets the unique hashed string for chat invitations and Ask to Join.\n         *\n         * @param joinSecret The unique hashed string for chat invitations and Ask to Join.\n         *\n         * @return This Builder.\n         */\n        public Builder setJoinSecret(String joinSecret)\n        {\n            this.joinSecret = joinSecret;\n            return this;\n        }\n\n        /**\n         * Sets the unique hashed string for Spectate button.\n         *\n         * @param spectateSecret The unique hashed string for Spectate button.\n         *\n         * @return This Builder.\n         */\n        public Builder setSpectateSecret(String spectateSecret)\n        {\n            this.spectateSecret = spectateSecret;\n            return this;\n        }\n\n        /**\n         * Marks the {@link #setMatchSecret(String) matchSecret} as a game\n         * session with a specific beginning and end.\n         *\n         * @param instance Whether or not the {@code matchSecret} is a game\n         *                 with a specific beginning and end.\n         *\n         * @return This Builder.\n         */\n        public Builder setInstance(boolean instance)\n        {\n            this.instance = instance;\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/User.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jagrosh.discordipc.entities;\n\n/**\n * A encapsulation of a Discord User's data provided when a\n * {@link com.jagrosh.discordipc.IPCListener IPCListener} fires\n * {@link com.jagrosh.discordipc.IPCListener#onActivityJoinRequest(com.jagrosh.discordipc.IPCClient, String, User)\n * onActivityJoinRequest}.\n *\n * @author John Grosh (john.a.grosh@gmail.com)\n */\npublic class User\n{\n    private final String name;\n    private final String discriminator;\n    private final long id;\n    private final String avatar;\n\n    /**\n     * Constructs a new {@link User}.<br>\n     * Only implemented internally.\n     * @param name user's name\n     * @param discriminator user's discrim\n     * @param id user's id\n     * @param avatar user's avatar hash, or {@code null} if they have no avatar\n     */\n    public User(String name, String discriminator, long id, String avatar)\n    {\n        this.name = name;\n        this.discriminator = discriminator;\n        this.id = id;\n        this.avatar = avatar;\n    }\n\n    /**\n     * Gets the Users account name.\n     *\n     * @return The Users account name.\n     */\n    public String getName()\n    {\n        return name;\n    }\n\n    /**\n     * Gets the Users discriminator.\n     *\n     * @return The Users discriminator.\n     */\n    public String getDiscriminator()\n    {\n        return discriminator;\n    }\n\n    /**\n     * Gets the Users Snowflake ID as a {@code long}.\n     *\n     * @return The Users Snowflake ID as a {@code long}.\n     */\n    public long getIdLong()\n    {\n        return id;\n    }\n\n    /**\n     * Gets the Users Snowflake ID as a {@code String}.\n     *\n     * @return The Users Snowflake ID as a {@code String}.\n     */\n    public String getId()\n    {\n        return Long.toString(id);\n    }\n\n    /**\n     * Gets the Users avatar ID.\n     *\n     * @return The Users avatar ID.\n     */\n    public String getAvatarId()\n    {\n        return avatar;\n    }\n\n    /**\n     * Gets the Users avatar URL.\n     *\n     * @return The Users avatar URL.\n     */\n    public String getAvatarUrl()\n    {\n        return getAvatarId() == null ? null : \"https://cdn.discordapp.com/avatars/\" + getId() + \"/\" + getAvatarId()\n            + (getAvatarId().startsWith(\"a_\") ? \".gif\" : \".png\");\n    }\n\n    /**\n     * Gets the Users {@link DefaultAvatar} avatar ID.\n     *\n     * @return The Users {@link DefaultAvatar} avatar ID.\n     */\n    public String getDefaultAvatarId()\n    {\n        return DefaultAvatar.values()[Integer.parseInt(getDiscriminator()) % DefaultAvatar.values().length].toString();\n    }\n\n    /**\n     * Gets the Users {@link DefaultAvatar} avatar URL.\n     *\n     * @return The Users {@link DefaultAvatar} avatar URL.\n     */\n    public String getDefaultAvatarUrl()\n    {\n        return \"https://discordapp.com/assets/\" + getDefaultAvatarId() + \".png\";\n    }\n\n    /**\n     * Gets the Users avatar URL, or their {@link DefaultAvatar} avatar URL if they\n     * do not have a custom avatar set on their account.\n     *\n     * @return The Users effective avatar URL.\n     */\n    public String getEffectiveAvatarUrl()\n    {\n        return getAvatarUrl() == null ? getDefaultAvatarUrl() : getAvatarUrl();\n    }\n\n    /**\n     * Gets whether or not this User is a bot.<p>\n     *\n     * While, at the time of writing this documentation, bots cannot\n     * use Rich Presence features, there may be a time in the future\n     * where they have such an ability.\n     *\n     * @return False\n     */\n    public boolean isBot()\n    {\n        return false; //bots cannot use RPC\n    }\n\n    /**\n     * Gets the User as a discord formatted mention.<p>\n     *\n     * {@code <@SNOWFLAKE_ID> }\n     *\n     * @return A discord formatted mention of this User.\n     */\n    public String getAsMention()\n    {\n        return \"<@\" + id + '>';\n    }\n    \n    @Override\n    public boolean equals(Object o)\n    {\n        if (!(o instanceof User))\n            return false;\n        User oUser = (User) o;\n        return this == oUser || this.id == oUser.id;\n    }\n    \n    @Override\n    public int hashCode()\n    {\n        return Long.hashCode(id);\n    }\n\n    @Override\n    public String toString()\n    {\n        return \"U:\" + getName() + '(' + id + ')';\n    }\n\n    /**\n     * Constants representing one of five different\n     * default avatars a {@link User} can have.\n     */\n    public enum DefaultAvatar\n    {\n        BLURPLE(\"6debd47ed13483642cf09e832ed0bc1b\"),\n        GREY(\"322c936a8c8be1b803cd94861bdfa868\"),\n        GREEN(\"dd4dbc0016779df1378e7812eabaa04d\"),\n        ORANGE(\"0e291f67c9274a1abdddeb3fd919cbaa\"),\n        RED(\"1cbd08c76f8af6dddce02c5138971129\");\n\n        private final String text;\n\n        DefaultAvatar(String text)\n        {\n            this.text = text;\n        }\n\n        @Override\n        public String toString()\n        {\n            return text;\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/pipe/Pipe.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.jagrosh.discordipc.entities.pipe;\n\nimport com.jagrosh.discordipc.IPCClient;\nimport com.jagrosh.discordipc.IPCListener;\nimport com.jagrosh.discordipc.entities.Callback;\nimport com.jagrosh.discordipc.entities.DiscordBuild;\nimport com.jagrosh.discordipc.entities.Packet;\nimport com.jagrosh.discordipc.exceptions.NoDiscordClientException;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.UUID;\n\npublic abstract class Pipe {\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(Pipe.class);\n    private static final int VERSION = 1;\n    PipeStatus status = PipeStatus.CONNECTING;\n    IPCListener listener;\n    private DiscordBuild build;\n    final IPCClient ipcClient;\n    private final HashMap<String,Callback> callbacks;\n\n    Pipe(IPCClient ipcClient, HashMap<String, Callback> callbacks)\n    {\n        this.ipcClient = ipcClient;\n        this.callbacks = callbacks;\n    }\n\n    public static Pipe openPipe(IPCClient ipcClient, long clientId, HashMap<String,Callback> callbacks,\n                                DiscordBuild... preferredOrder) throws NoDiscordClientException\n    {\n\n        if(preferredOrder == null || preferredOrder.length == 0)\n            preferredOrder = new DiscordBuild[]{DiscordBuild.ANY};\n\n        Pipe pipe = null;\n\n        // store some files so we can get the preferred client\n        Pipe[] open = new Pipe[DiscordBuild.values().length];\n        for(int i = 0; i < 10; i++)\n        {\n            try\n            {\n                String location = getPipeLocation(i);\n                LOGGER.debug(String.format(\"Searching for IPC: %s\", location));\n                pipe = createPipe(ipcClient, callbacks, location);\n\n                pipe.send(Packet.OpCode.HANDSHAKE, new JSONObject().put(\"v\", VERSION).put(\"client_id\", Long.toString(clientId)), null);\n\n                Packet p = pipe.read(); // this is a valid client at this point\n\n                pipe.build = DiscordBuild.from(p.getJson().getJSONObject(\"data\")\n                        .getJSONObject(\"config\")\n                        .getString(\"api_endpoint\"));\n\n                LOGGER.debug(String.format(\"Found a valid client (%s) with packet: %s\", pipe.build.name(), p.toString()));\n                // we're done if we found our first choice\n                if(pipe.build == preferredOrder[0] || DiscordBuild.ANY == preferredOrder[0])\n                {\n                    LOGGER.info(String.format(\"Found preferred client: %s\", pipe.build.name()));\n                    break;\n                }\n\n                open[pipe.build.ordinal()] = pipe; // didn't find first choice yet, so store what we have\n                open[DiscordBuild.ANY.ordinal()] = pipe; // also store in 'any' for use later\n\n                pipe.build = null;\n                pipe = null;\n            }\n            catch(IOException | JSONException ex)\n            {\n                pipe = null;\n            }\n        }\n\n        if(pipe == null)\n        {\n            // we already know we don't have our first pick\n            // check each of the rest to see if we have that\n            for(int i = 1; i < preferredOrder.length; i++)\n            {\n                DiscordBuild cb = preferredOrder[i];\n                LOGGER.debug(String.format(\"Looking for client build: %s\", cb.name()));\n                if(open[cb.ordinal()] != null)\n                {\n                    pipe = open[cb.ordinal()];\n                    open[cb.ordinal()] = null;\n                    if(cb == DiscordBuild.ANY) // if we pulled this from the 'any' slot, we need to figure out which build it was\n                    {\n                        for(int k = 0; k < open.length; k++)\n                        {\n                            if(open[k] == pipe)\n                            {\n                                pipe.build = DiscordBuild.values()[k];\n                                open[k] = null; // we don't want to close this\n                            }\n                        }\n                    }\n                    else pipe.build = cb;\n\n                    LOGGER.info(String.format(\"Found preferred client: %s\", pipe.build.name()));\n                    break;\n                }\n            }\n            if(pipe == null)\n            {\n                throw new NoDiscordClientException();\n            }\n        }\n        // close unused files, except skip 'any' because its always a duplicate\n        for(int i = 0; i < open.length; i++)\n        {\n            if(i == DiscordBuild.ANY.ordinal())\n                continue;\n            if(open[i] != null)\n            {\n                try {\n                    open[i].close();\n                } catch(IOException ex) {\n                    // This isn't really important to applications and better\n                    // as debug info\n                    LOGGER.debug(\"Failed to close an open IPC pipe!\", ex);\n                }\n            }\n        }\n\n        pipe.status = PipeStatus.CONNECTED;\n\n        return pipe;\n    }\n\n    private static Pipe createPipe(IPCClient ipcClient, HashMap<String, Callback> callbacks, String location) {\n        String osName = System.getProperty(\"os.name\").toLowerCase();\n\n        if (osName.contains(\"win\"))\n        {\n            return new WindowsPipe(ipcClient, callbacks, location);\n        }\n        else if (osName.contains(\"linux\") || osName.contains(\"mac\"))\n        {\n            try {\n                return new UnixPipe(ipcClient, callbacks, location);\n            }\n            catch (IOException e)\n            {\n                throw new RuntimeException(e);\n            }\n        }\n        else\n        {\n            throw new RuntimeException(\"Unsupported OS: \" + osName);\n        }\n    }\n\n    /**\n     * Sends json with the given {@link Packet.OpCode}.\n     *\n     * @param op The {@link Packet.OpCode} to send data with.\n     * @param data The data to send.\n     * @param callback callback for the response\n     */\n    public void send(Packet.OpCode op, JSONObject data, Callback callback)\n    {\n        try\n        {\n            String nonce = generateNonce();\n            Packet p = new Packet(op, data.put(\"nonce\",nonce));\n            if(callback!=null && !callback.isEmpty())\n                callbacks.put(nonce, callback);\n            write(p.toBytes());\n            LOGGER.debug(String.format(\"Sent packet: %s\", p.toString()));\n            if(listener != null)\n                listener.onPacketSent(ipcClient, p);\n        }\n        catch(IOException ex)\n        {\n            LOGGER.error(\"Encountered an IOException while sending a packet and disconnected!\");\n            status = PipeStatus.DISCONNECTED;\n        }\n    }\n\n    /**\n     * Blocks until reading a {@link Packet} or until the\n     * read thread encounters bad data.\n     *\n     * @return A valid {@link Packet}.\n     *\n     * @throws IOException\n     *         If the pipe breaks.\n     * @throws JSONException\n     *         If the read thread receives bad data.\n     */\n    public abstract Packet read() throws IOException, JSONException;\n\n    public abstract void write(byte[] b) throws IOException;\n\n    /**\n     * Generates a nonce.\n     *\n     * @return A random {@link UUID}.\n     */\n    private static String generateNonce()\n    {\n        return UUID.randomUUID().toString();\n    }\n\n    public PipeStatus getStatus()\n    {\n        return status;\n    }\n\n    public void setStatus(PipeStatus status)\n    {\n        this.status = status;\n    }\n\n    public void setListener(IPCListener listener)\n    {\n        this.listener = listener;\n    }\n\n    public abstract void close() throws IOException;\n\n    public DiscordBuild getDiscordBuild()\n    {\n        return build;\n    }\n\n    // a list of system property keys to get IPC file from different unix systems.\n    private final static String[] unixPaths = {\"XDG_RUNTIME_DIR\",\"TMPDIR\",\"TMP\",\"TEMP\"};\n\n    /**\n     * Finds the IPC location in the current system.\n     *\n     * @param i Index to try getting the IPC at.\n     *\n     * @return The IPC location.\n     */\n    private static String getPipeLocation(int i)\n    {\n        if(System.getProperty(\"os.name\").contains(\"Win\"))\n            return \"\\\\\\\\?\\\\pipe\\\\discord-ipc-\"+i;\n        String tmppath = null;\n        for(String str : unixPaths)\n        {\n            tmppath = System.getenv(str);\n            if(tmppath != null)\n                break;\n        }\n        if(tmppath == null)\n            tmppath = \"/tmp\";\n        return tmppath+\"/discord-ipc-\"+i;\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/pipe/PipeStatus.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.jagrosh.discordipc.entities.pipe;\n\nimport com.jagrosh.discordipc.IPCClient;\nimport com.jagrosh.discordipc.IPCListener;\nimport com.jagrosh.discordipc.entities.DiscordBuild;\nimport com.jagrosh.discordipc.entities.Packet;\n\n/**\n * Constants representing various status that an {@link IPCClient} can have.\n */\npublic enum PipeStatus\n{\n    /**\n     * Status for when the IPCClient when no attempt to connect has been made.<p>\n     *\n     * All IPCClients are created starting with this status,\n     * and it never returns for the lifespan of the client.\n     */\n    UNINITIALIZED,\n\n    /**\n     * Status for when the Pipe is attempting to connect.<p>\n     *\n     * This will become set whenever the #connect() method is called.\n     */\n    CONNECTING,\n\n    /**\n     * Status for when the Pipe is connected with Discord.<p>\n     *\n     * This is only present when the connection is healthy, stable,\n     * and reading good data without exception.<br>\n     * If the environment becomes out of line with these principles\n     * in any way, the IPCClient in question will become\n     * {@link PipeStatus#DISCONNECTED}.\n     */\n    CONNECTED,\n\n    /**\n     * Status for when the Pipe has received an {@link Packet.OpCode#CLOSE}.<p>\n     *\n     * This signifies that the reading thread has safely and normally shut\n     * and the client is now inactive.\n     */\n    CLOSED,\n\n    /**\n     * Status for when the Pipe has unexpectedly disconnected, either because\n     * of an exception, and/or due to bad data.<p>\n     *\n     * When the status of an Pipe becomes this, a call to\n     * {@link IPCListener#onDisconnect(IPCClient, Throwable)} will be made if one\n     * has been provided to the IPCClient.<p>\n     *\n     * Note that the IPCClient will be inactive with this status, after which a\n     * call to {@link IPCClient#connect(DiscordBuild...)} can be made to \"reconnect\" the\n     * IPCClient.\n     */\n    DISCONNECTED\n}"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/pipe/UnixPipe.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.jagrosh.discordipc.entities.pipe;\n\nimport com.jagrosh.discordipc.IPCClient;\nimport com.jagrosh.discordipc.entities.Callback;\nimport com.jagrosh.discordipc.entities.Packet;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.newsclub.net.unix.AFUNIXSocket;\nimport org.newsclub.net.unix.AFUNIXSocketAddress;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\nimport java.nio.file.Paths;\nimport java.util.HashMap;\n\npublic class UnixPipe extends Pipe\n{\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(UnixPipe.class);\n    private final AFUNIXSocket socket;\n\n    UnixPipe(IPCClient ipcClient, HashMap<String, Callback> callbacks, String location) throws IOException\n    {\n        super(ipcClient, callbacks);\n\n        socket = AFUNIXSocket.newInstance();\n        socket.connect(AFUNIXSocketAddress.of(Paths.get(location)));\n    }\n\n    @SuppressWarnings(\"ResultOfMethodCallIgnored\")\n    @Override\n    public Packet read() throws IOException, JSONException\n    {\n        InputStream is = socket.getInputStream();\n\n        while(is.available() == 0 && status == PipeStatus.CONNECTED)\n        {\n            try {\n                Thread.sleep(50);\n            } catch(InterruptedException ignored) {}\n        }\n\n        /*byte[] buf = new byte[is.available()];\n        is.read(buf, 0, buf.length);\n        LOGGER.info(new String(buf));\n\n        if (true) return null;*/\n\n        if(status==PipeStatus.DISCONNECTED)\n            throw new IOException(\"Disconnected!\");\n\n        if(status==PipeStatus.CLOSED)\n            return new Packet(Packet.OpCode.CLOSE, null);\n\n        // Read the op and length. Both are signed ints\n        byte[] d = new byte[8];\n        is.read(d);\n        ByteBuffer bb = ByteBuffer.wrap(d);\n\n        Packet.OpCode op = Packet.OpCode.values()[Integer.reverseBytes(bb.getInt())];\n        d = new byte[Integer.reverseBytes(bb.getInt())];\n\n        is.read(d);\n        Packet p = new Packet(op, new JSONObject(new String(d)));\n        LOGGER.debug(String.format(\"Received packet: %s\", p.toString()));\n        if(listener != null)\n            listener.onPacketReceived(ipcClient, p);\n        return p;\n    }\n\n    @Override\n    public void write(byte[] b) throws IOException\n    {\n        socket.getOutputStream().write(b);\n    }\n\n    @Override\n    public void close() throws IOException\n    {\n        LOGGER.debug(\"Closing IPC pipe...\");\n        send(Packet.OpCode.CLOSE, new JSONObject(), null);\n        status = PipeStatus.CLOSED;\n        socket.close();\n    }\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/entities/pipe/WindowsPipe.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage com.jagrosh.discordipc.entities.pipe;\n\nimport com.jagrosh.discordipc.IPCClient;\nimport com.jagrosh.discordipc.entities.Callback;\nimport com.jagrosh.discordipc.entities.Packet;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.FileNotFoundException;\nimport java.io.IOException;\nimport java.io.RandomAccessFile;\nimport java.util.HashMap;\n\npublic class WindowsPipe extends Pipe\n{\n\n    private static final Logger LOGGER = LoggerFactory.getLogger(WindowsPipe.class);\n\n    private final RandomAccessFile file;\n\n    WindowsPipe(IPCClient ipcClient, HashMap<String, Callback> callbacks, String location)\n    {\n        super(ipcClient, callbacks);\n        try {\n            this.file = new RandomAccessFile(location, \"rw\");\n        } catch (FileNotFoundException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public void write(byte[] b) throws IOException {\n        file.write(b);\n    }\n\n    @Override\n    public Packet read() throws IOException, JSONException {\n        while(file.length() == 0 && status == PipeStatus.CONNECTED)\n        {\n            try {\n                Thread.sleep(50);\n            } catch(InterruptedException ignored) {}\n        }\n\n        if(status==PipeStatus.DISCONNECTED)\n            throw new IOException(\"Disconnected!\");\n\n        if(status==PipeStatus.CLOSED)\n            return new Packet(Packet.OpCode.CLOSE, null);\n\n        Packet.OpCode op = Packet.OpCode.values()[Integer.reverseBytes(file.readInt())];\n        int len = Integer.reverseBytes(file.readInt());\n        byte[] d = new byte[len];\n\n        file.readFully(d);\n        Packet p = new Packet(op, new JSONObject(new String(d)));\n        LOGGER.debug(String.format(\"Received packet: %s\", p.toString()));\n        if(listener != null)\n            listener.onPacketReceived(ipcClient, p);\n        return p;\n    }\n\n    @Override\n    public void close() throws IOException {\n        LOGGER.debug(\"Closing IPC pipe...\");\n        send(Packet.OpCode.CLOSE, new JSONObject(), null);\n        status = PipeStatus.CLOSED;\n        file.close();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/jagrosh/discordipc/exceptions/NoDiscordClientException.java",
    "content": "/*\n * Copyright 2017 John Grosh (john.a.grosh@gmail.com).\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.jagrosh.discordipc.exceptions;\n\nimport com.jagrosh.discordipc.entities.DiscordBuild;\n\n/**\n * An exception thrown when an {@link com.jagrosh.discordipc.IPCClient IPCClient}\n * when the client cannot find the proper application to use for RichPresence when\n * attempting to {@link com.jagrosh.discordipc.IPCClient#connect(DiscordBuild...) connect}.<p>\n *\n * This purely and always means the IPCClient in question (specifically the client ID)\n * is <i>invalid</i> and features using this library cannot be accessed using the instance.\n *\n * @author John Grosh (john.a.grosh@gmail.com)\n */\npublic class NoDiscordClientException extends Exception\n{\n    \n}\n"
  }
]