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