├── src ├── main │ └── java │ │ └── com │ │ └── mojang │ │ └── api │ │ ├── profiles │ │ ├── ProfileRepository.java │ │ ├── Profile.java │ │ └── HttpProfileRepository.java │ │ └── http │ │ ├── HttpBody.java │ │ ├── HttpClient.java │ │ ├── HttpHeader.java │ │ └── BasicHttpClient.java └── test │ └── java │ └── com │ └── mojang │ └── api │ ├── http │ └── HttpBodyTests.java │ └── profiles │ ├── HttpProfileRepositoryIntegrationTests.java │ └── HttpProfileRepositoryTests.java ├── .gitignore └── README /src/main/java/com/mojang/api/profiles/ProfileRepository.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.profiles; 2 | 3 | public interface ProfileRepository { 4 | public Profile[] findProfilesByNames(String... names); 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle/ 2 | .idea/ 3 | build/ 4 | out/ 5 | classes/ 6 | /*.iml 7 | /*.ipr 8 | /*.iws 9 | .DS_Store 10 | *.sublime-project 11 | *.sublime-workspace 12 | user.gradle 13 | logs/ 14 | /*.ids 15 | *.log 16 | -------------------------------------------------------------------------------- /src/main/java/com/mojang/api/http/HttpBody.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.http; 2 | 3 | public class HttpBody { 4 | 5 | private String bodyString; 6 | 7 | public HttpBody(String bodyString) { 8 | this.bodyString = bodyString; 9 | } 10 | 11 | public byte[] getBytes() { 12 | return bodyString != null ? bodyString.getBytes() : new byte[0]; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/mojang/api/http/HttpClient.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.http; 2 | 3 | import java.io.IOException; 4 | import java.net.Proxy; 5 | import java.net.URL; 6 | import java.util.List; 7 | 8 | public interface HttpClient { 9 | public String post(URL url, HttpBody body, List headers) throws IOException; 10 | public String post(URL url, Proxy proxy, HttpBody body, List headers) throws IOException; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/mojang/api/profiles/Profile.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.profiles; 2 | 3 | public class Profile { 4 | private String id; 5 | private String name; 6 | 7 | public String getId() { 8 | return id; 9 | } 10 | 11 | public void setId(String id) { 12 | this.id = id; 13 | } 14 | 15 | public String getName() { 16 | return name; 17 | } 18 | 19 | public void setName(String name) { 20 | this.name = name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/mojang/api/http/HttpHeader.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.http; 2 | 3 | public class HttpHeader { 4 | private String name; 5 | private String value; 6 | 7 | public HttpHeader(String name, String value) { 8 | this.name = name; 9 | this.value = value; 10 | } 11 | 12 | public String getName() { 13 | return name; 14 | } 15 | 16 | public void setName(String name) { 17 | this.name = name; 18 | } 19 | 20 | public String getValue() { 21 | return value; 22 | } 23 | 24 | public void setValue(String value) { 25 | this.value = value; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/com/mojang/api/http/HttpBodyTests.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.http; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.JUnit4; 6 | 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static org.hamcrest.core.Is.is; 9 | import static org.hamcrest.core.IsEqual.equalTo; 10 | 11 | @RunWith(JUnit4.class) 12 | public class HttpBodyTests { 13 | 14 | @Test 15 | public void getBytes_constructedWithString_returnsBytesOfString() { 16 | String s = "someString"; 17 | HttpBody body = new HttpBody(s); 18 | byte[] expected = s.getBytes(); 19 | 20 | byte[] actual = body.getBytes(); 21 | 22 | assertThat(actual, is(equalTo(expected))); 23 | } 24 | 25 | @Test 26 | public void getBytes_constructedWithoutString_returnsEmptyByteArray() { 27 | HttpBody body = new HttpBody(null); 28 | byte[] expected = new byte[0]; 29 | 30 | byte[] actual = body.getBytes(); 31 | 32 | assertThat(actual, is(equalTo(expected))); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | . _///_, 2 | . / ` ' '> 3 | ) o' __/_'> 4 | ( / _/ )_\'> 5 | ' "__/ /_/\_> 6 | ____/_/_/_/ 7 | /,---, _/ / 8 | "" /_/_/_/ 9 | /_(_(_(_ \ 10 | ( \_\_\\_ )\ 11 | \'__\_\_\_\__ ).\ 12 | //____|___\__) )_/ 13 | | _ \'___'_( /' 14 | \_ (-'\'___'_\ __,'_' 15 | __) \ \\___(_ __/.__,' 16 | b'ger ,((,-,__\ '", __\_/. __,' 17 | '"./_._._-' 18 | 19 | This is the Java client lib used by Minecraft to interact with the newly deployed public API for handling accounts and profiles at Mojang. 20 | 21 | It's currently very limited and only allows player name -> id resolution but we're aiming to add on this in the future. The only documentation is the code, and if you find something particularly weird it's probably a combination between not wanting to introduce unnecessary dependencies to Minecraft and not going overkill. 22 | 23 | This code is provided as a courtesy and feel free to build a client in another language to share with the community. 24 | -------------------------------------------------------------------------------- /src/test/java/com/mojang/api/profiles/HttpProfileRepositoryIntegrationTests.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.profiles; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.JUnit4; 6 | 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static org.hamcrest.core.Is.is; 9 | import static org.hamcrest.core.IsEqual.equalTo; 10 | import static org.junit.Assert.fail; 11 | 12 | @RunWith(JUnit4.class) 13 | public class HttpProfileRepositoryIntegrationTests { 14 | 15 | @Test 16 | public void findProfilesByNames_existingNameProvided_returnsProfile() throws Exception { 17 | ProfileRepository repository = new HttpProfileRepository("minecraft"); 18 | 19 | Profile[] profiles = repository.findProfilesByNames("mollstam"); 20 | 21 | assertThat(profiles.length, is(1)); 22 | assertThat(profiles[0].getName(), is(equalTo("mollstam"))); 23 | assertThat(profiles[0].getId(), is(equalTo("f8cdb6839e9043eea81939f85d9c5d69"))); 24 | } 25 | 26 | @Test 27 | public void findProfilesByNames_existingMultipleNamesProvided_returnsProfiles() throws Exception { 28 | ProfileRepository repository = new HttpProfileRepository("minecraft"); 29 | 30 | Profile[] profiles = repository.findProfilesByNames("mollstam", "KrisJelbring"); 31 | 32 | assertThat(profiles.length, is(2)); 33 | assertThat(profiles[0].getName(), is(equalTo("mollstam"))); 34 | assertThat(profiles[0].getId(), is(equalTo("f8cdb6839e9043eea81939f85d9c5d69"))); 35 | assertThat(profiles[1].getName(), is(equalTo("KrisJelbring"))); 36 | assertThat(profiles[1].getId(), is(equalTo("7125ba8b1c864508b92bb5c042ccfe2b"))); 37 | } 38 | 39 | @Test 40 | public void findProfilesByNames_nonExistingNameProvided_returnsEmptyArray() throws Exception { 41 | ProfileRepository repository = new HttpProfileRepository("minecraft"); 42 | 43 | Profile[] profiles = repository.findProfilesByNames("doesnotexist$*not even legal"); 44 | 45 | assertThat(profiles.length, is(0)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/mojang/api/http/BasicHttpClient.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.http; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.DataOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStreamReader; 7 | import java.net.HttpURLConnection; 8 | import java.net.Proxy; 9 | import java.net.URL; 10 | import java.util.List; 11 | 12 | /* 13 | TODO: refactor so unit tests can be written :) 14 | */ 15 | public class BasicHttpClient implements HttpClient { 16 | 17 | private static BasicHttpClient instance; 18 | 19 | private BasicHttpClient() { 20 | } 21 | 22 | public static BasicHttpClient getInstance() { 23 | if (instance == null) { 24 | instance = new BasicHttpClient(); 25 | } 26 | return instance; 27 | } 28 | 29 | @Override 30 | public String post(URL url, HttpBody body, List headers) throws IOException { 31 | return post(url, null, body, headers); 32 | } 33 | 34 | @Override 35 | public String post(URL url, Proxy proxy, HttpBody body, List headers) throws IOException { 36 | if (proxy == null) proxy = Proxy.NO_PROXY; 37 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(proxy); 38 | connection.setRequestMethod("POST"); 39 | 40 | for (HttpHeader header : headers) { 41 | connection.setRequestProperty(header.getName(), header.getValue()); 42 | } 43 | 44 | connection.setUseCaches(false); 45 | connection.setDoInput(true); 46 | connection.setDoOutput(true); 47 | 48 | DataOutputStream writer = new DataOutputStream(connection.getOutputStream()); 49 | writer.write(body.getBytes()); 50 | writer.flush(); 51 | writer.close(); 52 | 53 | BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); 54 | String line; 55 | StringBuffer response = new StringBuffer(); 56 | 57 | while ((line = reader.readLine()) != null) { 58 | response.append(line); 59 | response.append('\r'); 60 | } 61 | 62 | reader.close(); 63 | return response.toString(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/com/mojang/api/profiles/HttpProfileRepositoryTests.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.profiles; 2 | 3 | import com.google.gson.Gson; 4 | import com.mojang.api.http.HttpBody; 5 | import com.mojang.api.http.HttpClient; 6 | import org.hamcrest.CoreMatchers; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.junit.runners.JUnit4; 10 | 11 | import java.io.IOException; 12 | import java.net.URL; 13 | 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.Matchers.hasItemInArray; 16 | import static org.hamcrest.Matchers.hasProperty; 17 | import static org.hamcrest.core.Is.is; 18 | import static org.hamcrest.core.IsEqual.equalTo; 19 | import static org.mockito.Matchers.*; 20 | import static org.mockito.Mockito.mock; 21 | import static org.mockito.Mockito.when; 22 | 23 | @RunWith(JUnit4.class) 24 | public class HttpProfileRepositoryTests { 25 | 26 | private HttpClient client; 27 | private Gson gson = new Gson(); 28 | 29 | @Test 30 | public void findProfilesByCriteria_someProfileNames_returnsExpectedProfiles() throws Exception{ 31 | client = mock(HttpClient.class); 32 | String someAgent = "someAgent"; 33 | 34 | Profile someProfile = getProfile("someName"); 35 | Profile someOtherProfile = getProfile("someOtherName"); 36 | Profile[] profiles = {someProfile, someOtherProfile}; 37 | 38 | setProfilesForUrl(client, new URL("https://api.mojang.com/profiles/" + someAgent), profiles); 39 | ProfileRepository repository = new HttpProfileRepository(someAgent, client); 40 | 41 | Profile[] actual = repository.findProfilesByNames("someName", "someOtherName"); 42 | 43 | assertThat(actual.length, is(equalTo(2))); 44 | assertThat(actual, hasItemInArray(hasProperty("name", CoreMatchers.is("someName")))); 45 | assertThat(actual, hasItemInArray(hasProperty("name", CoreMatchers.is("someOtherName")))); 46 | } 47 | 48 | private void setProfilesForUrl(HttpClient mock, URL url, Profile[] profiles) throws IOException { 49 | String jsonString = gson.toJson(profiles); 50 | when(mock.post(eq(url), any(HttpBody.class), anyList())).thenReturn(jsonString); 51 | } 52 | 53 | private static Profile getProfile(String name) { 54 | Profile profile = new Profile(); 55 | profile.setName(name); 56 | return profile; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/mojang/api/profiles/HttpProfileRepository.java: -------------------------------------------------------------------------------- 1 | package com.mojang.api.profiles; 2 | 3 | import com.google.gson.Gson; 4 | import com.mojang.api.http.BasicHttpClient; 5 | import com.mojang.api.http.HttpBody; 6 | import com.mojang.api.http.HttpClient; 7 | import com.mojang.api.http.HttpHeader; 8 | 9 | import java.io.IOException; 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.Collection; 15 | import java.util.List; 16 | 17 | public class HttpProfileRepository implements ProfileRepository { 18 | 19 | // You're not allowed to request more than 100 profiles per go. 20 | private static final int PROFILES_PER_REQUEST = 100; 21 | 22 | private static Gson gson = new Gson(); 23 | private final String agent; 24 | private HttpClient client; 25 | 26 | public HttpProfileRepository(String agent) { 27 | this(agent, BasicHttpClient.getInstance()); 28 | } 29 | 30 | public HttpProfileRepository(String agent, HttpClient client) { 31 | this.agent = agent; 32 | this.client = client; 33 | } 34 | 35 | @Override 36 | public Profile[] findProfilesByNames(String... names) { 37 | List profiles = new ArrayList(); 38 | try { 39 | 40 | List headers = new ArrayList(); 41 | headers.add(new HttpHeader("Content-Type", "application/json")); 42 | 43 | int namesCount = names.length; 44 | int start = 0; 45 | int i = 0; 46 | do { 47 | int end = PROFILES_PER_REQUEST * (i + 1); 48 | if (end > namesCount) { 49 | end = namesCount; 50 | } 51 | String[] namesBatch = Arrays.copyOfRange(names, start, end); 52 | HttpBody body = getHttpBody(namesBatch); 53 | Profile[] result = post(getProfilesUrl(), body, headers); 54 | profiles.addAll(Arrays.asList(result)); 55 | 56 | start = end; 57 | i++; 58 | } while (start < namesCount); 59 | } catch (Exception e) { 60 | // TODO: logging and allowing consumer to react? 61 | } 62 | 63 | return profiles.toArray(new Profile[profiles.size()]); 64 | } 65 | 66 | private URL getProfilesUrl() throws MalformedURLException { 67 | // To lookup Minecraft profiles, agent should be "minecraft" 68 | return new URL("https://api.mojang.com/profiles/" + agent); 69 | } 70 | 71 | private Profile[] post(URL url, HttpBody body, List headers) throws IOException { 72 | String response = client.post(url, body, headers); 73 | return gson.fromJson(response, Profile[].class); 74 | } 75 | 76 | private static HttpBody getHttpBody(String... namesBatch) { 77 | return new HttpBody(gson.toJson(namesBatch)); 78 | } 79 | } 80 | --------------------------------------------------------------------------------