org.bspfsystems.basicmojangapi.BasicMojangAPI Maven / Gradle / Ivy
Show all versions of basic-mojang-api Show documentation
/*
* This file is part of the BasicMojangAPI Java library.
*
* Copyright 2021 BSPF Systems, LLC
*
* 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 org.bspfsystems.basicmojangapi;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.bspfsystems.simplejson.JSONArray;
import org.bspfsystems.simplejson.JSONObject;
import org.bspfsystems.simplejson.SimpleJSONArray;
import org.bspfsystems.simplejson.parser.JSONException;
import org.bspfsystems.simplejson.parser.JSONParser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Represents a utility class to interact with basic parts of the Mojang API
* using Java.
*
* The implemented API features include:
*
Usernames to UUIDs
*
* PLEASE NOTE: Since November 2020, Mojang stopped supporting the timestamp
* parameter. If a timestamp is provided, it is effectively ignored, and the
* current time is used instead. This means that a username that has been
* used on multiple accounts will return only the current {@link UUID} (if
* any) that is associated with the given username. Please remind Mojang to
* fix this issue here:
* WEB-3367
*
* @param username The username of the account to retrieve.
* @return The {@link Account} associated with the given username, including
* the case-corrected username and the {@link UUID}, or {@code null}
* if there is no current account associated with the username.
* @throws IllegalArgumentException If the username is invalid or otherwise
* causes a bad request to be handled.
* @throws IOException If there was an error while parsing the data from the
* API.
* @see BasicMojangAPI#usernameToUniqueId(String, long)
*/
@Nullable
public static Account usernameToUniqueId(@NotNull final String username) throws IllegalArgumentException, IOException {
return BasicMojangAPI.usernameToUniqueId(username, System.currentTimeMillis());
}
/**
* Gets the {@link Account} data, containing the case-corrected username and
* the associated {@link UUID}. This will use the given time as the optional
* timestamp parameter.
*
* PLEASE NOTE: Since November 2020, Mojang stopped supporting the timestamp
* parameter. If a timestamp is provided, it is effectively ignored, and the
* current time is used instead. This means that a username that has been
* used on multiple accounts will return only the current {@link UUID} (if
* any) that is associated with the given username. Please remind Mojang to
* fix this issue here:
* WEB-3367
*
* @param username The username of the account to retrieve.
* @param timestamp The timestamp to use. The milliseconds portion of the
* timestamp will be ignored, as the API uses the UNIX
* timestamp without milliseconds.
* @return The {@link Account} associated with the given username, including
* the case-corrected username and the {@link UUID}, or {@code null}
* if there is no current account associated with the username.
* @throws IllegalArgumentException If the username is invalid or otherwise
* causes a bad request to be handled.
* @throws IOException If there was an error while parsing the data from the
* API.
*/
@Nullable
public static Account usernameToUniqueId(@NotNull final String username, final long timestamp) throws IllegalArgumentException, IOException {
final URL url = new URL(BasicMojangAPI.BASE_URL + BasicMojangAPI.USERNAME_TO_UUID.replace("", username).replace("", String.valueOf(timestamp / 1000L)));
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
final int responseCode = connection.getResponseCode();
if (responseCode == 204) {
return null;
}
final JSONObject responseData;
try {
responseData = JSONParser.deserializeObject(BasicMojangAPI.readData(connection.getInputStream()));
} catch (JSONException e) {
throw new IOException("Unable to parse data from the Mojang API.", e);
}
if (responseData == null) {
return null;
}
if (responseCode == 400) {
throw new IllegalArgumentException(responseData.getString("errorMessage", "An invalid parameter was given."));
}
return new SimpleAccount(responseData);
}
/**
* Gets a {@link List} of {@link Account} data from the Mojang API, given a
* {@link List} of usernames. The usernames in the respective
* {@link Account}s will be case-corrected.
*
* The supplied {@link List} may contain duplicates (Mojang usernames are
* unique from a case-insensitive aspect, so {@code "example"} and
* {@code "EXAMPLE"} cannot both be used at the same time). How Mojang
* handles duplicates in the submitted data may change at any time.
*
* Depending on how the Mojang API handles duplicates, there may or may not
* be duplicate {@link Account}s in the returned {@link List}.
*
* The supplied {@link List} may not contain more than 10 entries, and none
* of the entries may be {@code null}. Otherwise, an
* {@link IllegalArgumentException} will be thrown.
*
* @param usernames The {@link List} of usernames to retrieve.
* @return The {@link List} of {@link Account}s returned from the Mojang
* API.
* @throws IllegalArgumentException If a username is invalid or otherwise
* causes a bad request to be handled, or
* there are more than 10 usernames.
* @throws IOException If there was an error while parsing the data from the
* API.
*/
@NotNull
public static List usernamesToUniqueIds(@NotNull final List usernames) throws IllegalArgumentException, IOException {
if (usernames.size() > 10) {
throw new IllegalArgumentException("You cannot request " + usernames.size() + " usernames (maximum 10).");
}
final JSONArray postData = new SimpleJSONArray();
for (final String username : usernames) {
if (username == null) {
throw new IllegalArgumentException("You cannot request a null username.");
}
postData.addEntry(username);
}
final String postDataSerialized;
try {
postDataSerialized = JSONParser.serialize(postData);
} catch (JSONException e) {
throw new IOException("Unable to serialize the list of usernames.", e);
}
final byte[] postDataBytes = postDataSerialized.getBytes(StandardCharsets.UTF_8);
final URL url = new URL(BasicMojangAPI.BASE_URL + BasicMojangAPI.USERNAMES_TO_UUIDS);
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", BasicMojangAPI.POST_PROPERTY + "; utf-8");
connection.setRequestProperty("Accept", BasicMojangAPI.POST_PROPERTY);
connection.setDoOutput(true);
connection.getOutputStream().write(postDataBytes, 0, postDataBytes.length);
final JSONArray responseData;
try {
responseData = JSONParser.deserializeArray(BasicMojangAPI.readData(connection.getInputStream()));
} catch (JSONException e) {
throw new IOException("Unable to parse data from the Mojang API.");
}
if (connection.getResponseCode() == 400) {
throw new IllegalArgumentException("An invalid username was passed in as part of the request.");
}
final List accounts = new ArrayList();
if (responseData == null || responseData.size() == 0) {
return accounts;
}
for (final Object dataItem : responseData) {
if (dataItem instanceof JSONObject) {
accounts.add(new SimpleAccount((JSONObject) dataItem));
} else {
throw new IOException("Invalid account data returned.");
}
}
return accounts;
}
/**
* Gets the {@link AccountHistory} for the given {@link UUID} from the
* Mojang API. The usernames in the {@link AccountHistory} will be
* case-corrected.
*
* If there is no Mojang account for the given {@link UUID}, {@code null}
* will be returned.
*
* @param uniqueId The {@link UUID} of the retrieved {@link AccountHistory}.
* @return The retrieved {@link AccountHistory}, or {@code null} if no
* Mojang account exist with the given {@link UUID}.
* @throws IllegalArgumentException If the {@link UUID} is invalid or
* otherwise causes an error to occur while
* creating the {@link AccountHistory}.
* @throws IOException If there was an error while parsing the data from the
* API.
*/
@Nullable
public static AccountHistory uniqueIdToNameHistory(@NotNull final UUID uniqueId) throws IllegalArgumentException, IOException {
final URL url = new URL(BasicMojangAPI.BASE_URL + BasicMojangAPI.UUID_TO_NAME_HISTORY.replace("", uniqueId.toString()));
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
final int responseCode = connection.getResponseCode();
if (responseCode == 204) {
return null;
}
final JSONArray responseData;
try {
responseData = JSONParser.deserializeArray(BasicMojangAPI.readData(connection.getInputStream()));
} catch (JSONException e) {
throw new IOException("Unable to parse data from the Mojang API.", e);
}
if (responseData == null) {
return null;
}
if (responseCode == 400) {
throw new IllegalArgumentException("An invalid UUID was passed in as part of the request.");
}
return new SimpleAccountHistory(uniqueId, responseData);
}
/**
* Reads the data in from the given {@link InputStream} and returns the data
* as a {@link String}.
*
* @param input The {@link InputStream} to read the data from.
* @return A {@link String} representing the data.
* @throws IOException If an I/O error occurs while reading in the data.
*/
@NotNull
private static String readData(@NotNull final InputStream input) throws IOException {
final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
final StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
return builder.toString();
}
/**
* Translates a 32-character {@link UUID} from the Mojang API into a
* {@link UUID} that will represent the 36-character version (with the "-"
* characters in the {@link UUID}).
*
* @param shortId The 32-character version of the {@link UUID} as a
* {@link String}.
* @return The {@link UUID} translated from the given {@link String}.
* @throws IllegalArgumentException See {@link UUID#fromString(String)}.
*/
@NotNull
static UUID translateUniqueId(@NotNull final String shortId) throws IllegalArgumentException {
final StringBuilder builder = new StringBuilder();
builder.append(shortId, 0, 8);
builder.append("-");
builder.append(shortId, 8, 12);
builder.append("-");
builder.append(shortId, 12, 16);
builder.append("-");
builder.append(shortId, 16, 20);
builder.append("-");
builder.append(shortId, 20, 32);
return UUID.fromString(builder.toString());
}
}