FlexPlex api wrapper first version

master
Jonttuuu 2023-07-30 04:39:23 +03:00
commit 7e096d3d9e
8 changed files with 512 additions and 0 deletions

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
/target

36
pom.xml 100644
View File

@ -0,0 +1,36 @@
<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">
<modelVersion>4.0.0</modelVersion>
<groupId>fi.flexplex</groupId>
<artifactId>lib</artifactId>
<version>1.0.0</version>
<name>FlexConnect</name>
<build>
<finalName>${project.name}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>16</source>
<target>16</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.neovisionaries</groupId>
<artifactId>nv-websocket-client</artifactId>
<version>2.14</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,98 @@
package fi.flexplex.lib;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Logger;
public final class FlexLib {
private final String token;
private final Logger logger;
private final GraphQLApi graphQLApi;
private final Set<FlexLibAdapter> eventListeners = new HashSet<>();
private WebSocketClient webSocketClient;
private final HashMap<String, PlayerFriends> playerFriends = new HashMap<>();
protected Set<FlexLibAdapter> getEventListeners() {
return this.eventListeners;
}
protected void updatePlayerFriends(final String player) {
final PlayerFriends pFriends = this.playerFriends.get(player);
if (pFriends != null) {
pFriends.update();
}
}
/**
* Instantiates FlexLib API
* @param token token for FlexPlex API
*/
public FlexLib(final String token, final Logger logger) {
this.token = token;
this.logger = logger;
this.logger.info("Initializing FlexLib");
this.graphQLApi = new GraphQLApi(this, "https://api.flexplex.fi/graphql");
new FlexLibAdapterImpl(this);
try {
this.webSocketClient = new WebSocketClient(this, "wss://api.flexplex.fi/flexlib", this.token);
} catch (final IOException e) {
this.logger.warning("Failed to initialize FlexLib WebSocket client");
}
}
/**
* Get API token
* @return API Token used to connect FlexPlex
*/
public String getToken() {
return this.token;
}
/**
* Get logger used by FlexLib
* @return FlexLib logger
*/
public Logger getLogger() {
return this.logger;
}
/**
* Add new event listener
* @param listener
*/
public void addListener(final FlexLibAdapter listener) {
this.eventListeners.add(listener);
}
/**
* Get Friends API
* @return Friends API if available
*/
public Optional<PlayerFriends> getPlayerFriends(final String player) {
PlayerFriends pFriends = this.playerFriends.get(player);
if (pFriends == null) {
try {
pFriends = new PlayerFriends(this.graphQLApi, this.token, player);
} catch (final IOException e) {
return Optional.empty();
}
this.playerFriends.put(player, pFriends);
}
return Optional.of(pFriends);
}
public void disable() {
if (this.webSocketClient != null) {
this.webSocketClient.disable();
}
this.eventListeners.clear();
this.playerFriends.clear();
}
}

View File

@ -0,0 +1,39 @@
package fi.flexplex.lib;
public interface FlexLibAdapter {
/**
* Called when player friend list or friend requests are updated
* @param player
*/
public default void onFriendsUpdate(final String player) {}
/**
* New friend request event
* @param player
* @param targetPlayer
*/
public default void onFriendRequest(final String player, final String targetPlayer) {}
/**
* Friend request accepted event
* @param player
* @param targetPlayer
*/
public default void onFriendRequestAccept(final String player, final String targetPlayer) {}
/**
* Friend request deleted event
* @param player
* @param targetPlayer
*/
public default void onFriendRequestDelete(final String player, final String targetPlayer) {}
/**
* Friend deleted event
* @param player
* @param targetPlayer
*/
public default void onFriendDelete(final String player, final String targetPlayer) {}
}

View File

@ -0,0 +1,17 @@
package fi.flexplex.lib;
public final class FlexLibAdapterImpl implements FlexLibAdapter {
private final FlexLib flexLib;
protected FlexLibAdapterImpl(final FlexLib flexLib) {
this.flexLib = flexLib;
this.flexLib.addListener(this);
}
@Override
public void onFriendsUpdate(final String player) {
this.flexLib.updatePlayerFriends(player);
}
}

View File

@ -0,0 +1,60 @@
package fi.flexplex.lib;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Optional;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
public final class GraphQLApi {
private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.build();
private final FlexLib flexLib;
private final URI uri;
protected GraphQLApi(final FlexLib flexLib, final String url) {
this.flexLib = flexLib;
this.uri = URI.create(url);
}
protected Optional<JsonObject> execute(final String query) {
final HttpResponse<String> response;
final HttpRequest request = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString("{\"query\":\"" + query + "\"}"))
.uri(this.uri)
.build();
try {
response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
} catch (final IOException e) {
this.flexLib.getLogger().warning("Failed to send GraphQL query to FlexPlex server.");
return Optional.empty();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
return Optional.empty();
}
final JsonObject json;
try {
json = JsonParser.parseString(response.body()).getAsJsonObject();
} catch (final JsonSyntaxException | IllegalStateException e) {
this.flexLib.getLogger().warning("Received invalid json from FlexPlex GraphQL API");
return Optional.empty();
}
if (json.has("data")) {
return Optional.of(json.get("data").getAsJsonObject());
} else {
return Optional.empty();
}
}
}

View File

@ -0,0 +1,164 @@
package fi.flexplex.lib;
import java.io.IOException;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public final class PlayerFriends {
private final GraphQLApi graphQLApi;
private final String token;
private final String player;
private final Set<UUID> friends = new HashSet<>();
private final Set<UUID> incomingFriendRequests = new HashSet<>();
private final Set<UUID> outcomingFriendRequests = new HashSet<>();
protected PlayerFriends(final GraphQLApi graphQLApi, final String token, final String player) throws IOException {
this.graphQLApi = graphQLApi;
this.token = token;
this.player = player;
if (!this.update()) {
throw new IOException();
}
}
protected boolean update() {
final Optional<JsonObject> result = this.graphQLApi.execute(
"query{" +
"player(uuid:\"" + this.player + "\"){" +
"friends{uuid}" +
"incomingFriendRequests(token:\"" + this.token + "\"){uuid}" +
"outcomingFriendRequests(token:\"" + this.token + "\"){uuid}" +
"}" +
"}"
);
if (result.isEmpty()) {
return false;
}
final JsonObject playerObject = result.get().get("player").getAsJsonObject();
this.friends.clear();
for (final JsonElement element : playerObject.get("friends").getAsJsonArray()) {
this.friends.add(UUID.fromString(element.getAsJsonObject().get("uuid").getAsString()));
}
this.incomingFriendRequests.clear();
for (final JsonElement element : playerObject.get("incomingFriendRequests").getAsJsonArray()) {
this.incomingFriendRequests.add(UUID.fromString(element.getAsJsonObject().get("uuid").getAsString()));
}
this.outcomingFriendRequests.clear();
for (final JsonElement element : playerObject.get("outcomingFriendRequests").getAsJsonArray()) {
this.outcomingFriendRequests.add(UUID.fromString(element.getAsJsonObject().get("uuid").getAsString()));
}
return true;
}
/**
* Get friends
* @return friends
*/
public Set<UUID> getFriends() {
return this.friends;
}
/**
* Get incoming friend requests
* @return friend requests
*/
public Set<UUID> getIncomingFriendRequests() {
return this.incomingFriendRequests;
}
/**
* Get outcoming friend requests
* @return friend requests
*/
public Set<UUID> getOutcomingFriendRequests() {
return this.outcomingFriendRequests;
}
/**
* Create new friend request
* @param player from
* @param targetPlayer to
* @return true if was created successfully
*/
public boolean createFriendRequest(final String player, final String targetPlayer) {
final Optional<JsonObject> result = this.graphQLApi.execute(
"mutation{" +
"createFriendRequest(" +
"token:\"" + this.token + "\"," +
"player:\"" + player + "\"," +
"targetPlayer:\"" + targetPlayer + "\"" +
")" +
"}"
);
return result.isPresent() && result.get().get("createFriendRequest").getAsBoolean();
}
/**
* Accept incoming friend request
* @param player coming from
* @param targetPlayer who is accepting
* @return true if was accepted successfully
*/
public boolean acceptFriendRequest(final String player, final String targetPlayer) {
final Optional<JsonObject> result = this.graphQLApi.execute(
"mutation{" +
"acceptFriendRequest(" +
"token:\"" + this.token + "\"," +
"player:\"" + player + "\"," +
"targetPlayer:\"" + targetPlayer + "\"" +
")" +
"}"
);
return result.isPresent() && result.get().get("acceptFriendRequest").getAsBoolean();
}
/**
* Delete friend or friend request
* @param player
* @param targetPlayer
* @return true if was deleted successfully
*/
public boolean deleteFriend(final String player, final String targetPlayer) {
final Optional<JsonObject> result = this.graphQLApi.execute(
"mutation{" +
"deleteFriend(" +
"token:\"" + this.token + "\"," +
"player:\"" + player + "\"," +
"targetPlayer:\"" + targetPlayer + "\"" +
")" +
"}"
);
return result.isPresent() && result.get().get("deleteFriend").getAsBoolean();
}
/**
* Change settings does player accept incoming friend requests
* @param player
* @param enabled
* @return true if was changed successfully
*/
public boolean setFriendRequestsEnabled(final String player, final boolean enabled) {
final Optional<JsonObject> result = this.graphQLApi.execute(
"mutation{" +
"updateAcceptFriendRequests(" +
"token:\"" + this.token + "\"," +
"player:\"" + player + "\"," +
"acceptRequests:\"" + (enabled ? "true" : "false") + "\"" +
")" +
"}"
);
return result.isPresent() && result.get().get("updateAcceptFriendRequests").getAsBoolean();
}
}

View File

@ -0,0 +1,97 @@
package fi.flexplex.lib;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
public final class WebSocketClient {
private final FlexLib flexLib;
private final Timer timer;
private final TimerTask timerTask;
private final WebSocket webSocket;
protected WebSocketClient(final FlexLib flexLib, final String url, final String token) throws IOException {
this.flexLib = flexLib;
this.webSocket = this.createWebSocket(url, token);
this.timer = new Timer();
this.timerTask = new TimerTask() {
@Override
public void run() {
if (!webSocket.isOpen()) {
try {
webSocket.connect();
} catch (final WebSocketException e) {
flexLib.getLogger().warning("WebSocket connection to FlexPlex server failed.");
}
}
}
};
this.timer.schedule(this.timerTask, 0, 60_000);
}
protected void disable() {
this.timerTask.cancel();
this.timer.cancel();
this.webSocket.disconnect();
}
private WebSocket createWebSocket(final String url, final String token) throws IOException {
return new WebSocketFactory()
.createSocket(url)
.addHeader("Token", token)
.addListener(new WebSocketAdapter() {
@Override
public void onTextMessage(final WebSocket ws, final String message) {
final String[] args = message.split(" ");
switch (args[0]) {
case "FRIEND_REQUEST":
if (args.length == 3) {
for (final FlexLibAdapter listener : flexLib.getEventListeners()) {
listener.onFriendRequest(args[1], args[2]);
listener.onFriendsUpdate(args[1]);
listener.onFriendsUpdate(args[2]);
}
}
break;
case "FRIEND_ACCEPT":
if (args.length == 3) {
for (final FlexLibAdapter listener : flexLib.getEventListeners()) {
listener.onFriendRequestAccept(args[1], args[2]);
listener.onFriendsUpdate(args[1]);
listener.onFriendsUpdate(args[2]);
}
}
break;
case "FRIEND_REQUEST_DELETE":
if (args.length == 3) {
for (final FlexLibAdapter listener : flexLib.getEventListeners()) {
listener.onFriendRequestDelete(args[1], args[2]);
listener.onFriendsUpdate(args[1]);
listener.onFriendsUpdate(args[2]);
}
}
break;
case "FRIEND_DELETE":
if (args.length == 3) {
for (final FlexLibAdapter listener : flexLib.getEventListeners()) {
listener.onFriendDelete(args[1], args[2]);
listener.onFriendsUpdate(args[1]);
listener.onFriendsUpdate(args[2]);
}
}
break;
default:
flexLib.getLogger().warning("Received invalid WebSocket message from FlexPlex. Message: " + args[0]);
break;
}
}
});
}
}