Skip to content

Commit ca114de

Browse files
committed
Update lots of stuff, settle #16
1 parent cf2b568 commit ca114de

File tree

11 files changed

+276
-55
lines changed

11 files changed

+276
-55
lines changed

src/main/java/net/dec4234/javadestinyapi/exceptions/APIException.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
package net.dec4234.javadestinyapi.exceptions;
99

1010
/**
11-
* Base class for all exceptions generated when an issue occurs in the request pipeline to Bungie.
12-
* This most common reason for this error would be when the API is offline, and all objects are misparsed.
11+
* Base class for all exceptions generated when an issue occurs in the request pipeline to Bungie. <br>
12+
* This is defined as a RuntimeException to give more flexibility internally (namely in {@link net.dec4234.javadestinyapi.material.DestinyAPI#searchUsers(String)}).
13+
* You should still try/catch this rather than deferring it to being a solely runtime exception
1314
*/
14-
public abstract class APIException extends Exception {
15+
public abstract class APIException extends RuntimeException {
1516

1617
public APIException() {
1718

src/main/java/net/dec4234/javadestinyapi/material/DestinyAPI.java

Lines changed: 104 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,27 @@
1212
import com.google.gson.JsonObject;
1313
import net.dec4234.javadestinyapi.exceptions.APIException;
1414
import net.dec4234.javadestinyapi.material.clan.Clan;
15+
import net.dec4234.javadestinyapi.material.clan.ClanMember;
16+
import net.dec4234.javadestinyapi.material.clan.GroupType;
1517
import net.dec4234.javadestinyapi.material.user.BungieUser;
1618
import net.dec4234.javadestinyapi.material.user.UserCredential;
1719
import net.dec4234.javadestinyapi.material.user.UserCredentialType;
1820
import net.dec4234.javadestinyapi.responses.user.SanitizedUsernamesResponse;
1921
import net.dec4234.javadestinyapi.utils.HttpUtils;
2022
import net.dec4234.javadestinyapi.utils.StringUtils;
23+
import net.dec4234.javadestinyapi.utils.fast.Pagination;
2124
import net.dec4234.javadestinyapi.utils.framework.OAuthManager;
25+
import org.jetbrains.annotations.NotNull;
2226

2327
import java.util.ArrayList;
28+
import java.util.Iterator;
2429
import java.util.LinkedList;
2530
import java.util.List;
2631

2732
/**
2833
* This is the base class for the entire API. It MUST be initialized with an API key before <u>any part</u>
2934
* of the API is used.
35+
* // TODO: its time to rethink this class and instancing the API
3036
*/
3137
public class DestinyAPI {
3238

@@ -163,8 +169,9 @@ public static SanitizedUsernamesResponse getSanitizedUsernames(String id) throws
163169
}
164170

165171
/**
166-
* Get a BungieUser from a Steam ID
167-
* NOT the same IDs used by Bungie to identify individual users
172+
* Get a BungieUser from a Steam ID, which is NOT the same IDs used by Bungie to identify individual users.
173+
* @param steamID The steamID corresponding with the user you are looking for.
174+
* @return The bungie user corresponding with that steam ID
168175
*/
169176
public static BungieUser getMemberFromSteamID(String steamID) throws APIException {
170177
return getMemberFromPlatformID("SteamId", steamID);
@@ -251,44 +258,85 @@ public static BungieUser getUserWithName(String nameAndDiscrim) throws APIExcept
251258
/**
252259
* Search users across all platforms for anyone with that name.
253260
* <p>
254-
* Searching "dec4234" will return an array containing a single
255-
* BungieUser. While searching "Gladd" or "Datto" should return many users.
256-
* <p>
257-
* // TODO: Issue #16
261+
* Returns a "Pagination" object. This allows you to request the next page of guardians with that name one at a time,
262+
* rather than returning a huge array after a long time. This was proposed in issue #16.
263+
* <br>
264+
* <pre>
265+
* {@code
266+
* Pagination<List<BungieUser>> pagination = DestinyAPI.searchUsers("Etho");
267+
* for(List<BungieUser> bungieUsers : pagination) { // iterate through all pages one by one
268+
* bungieUsers.forEach(bungieUser -> { // Print details for users on the current page
269+
* System.out.println(bungieUser);
270+
* });
271+
* }
272+
* }
273+
* </pre>
274+
* @param name The name of the user you want to search for
275+
* @return A Pagination object that returns a List of matching bungie users per page.
258276
*/
259-
public static List<BungieUser> searchUsers(String name) throws APIException {
260-
List<BungieUser> users = new ArrayList<>();
261-
277+
public static Pagination<List<BungieUser>> searchUsers(String name) throws APIException {
262278
JsonObject body = new JsonObject();
263279
body.addProperty("displayNamePrefix", name);
264280

265-
List<JsonObject> jsonObjects = new ArrayList<>();
281+
return new Pagination<>() {
282+
private int i = 0;
283+
private boolean isDone = false;
284+
285+
@Override
286+
public boolean hasNext() throws APIException {
287+
if(isDone) {
288+
return false;
289+
}
266290

267-
for (int i = 0; i < 100; i++) { // Start at page 0 and increment each time until there are no more valid pages
268-
JsonObject jsonObject = httpUtils.urlRequestPOST(HttpUtils.URL_BASE + "/User/Search/GlobalName/" + i + "/", body);
291+
JsonObject response = httpUtils.urlRequestPOST(HttpUtils.URL_BASE + "/User/Search/GlobalName/" + i + "/", body);
292+
i++;
269293

270-
// Only continue looping if the result has a list of search results
271-
if (jsonObject.has("Response") && jsonObject.getAsJsonObject("Response").getAsJsonArray("searchResults").size() != 0) {
272-
JsonArray jsonArray = jsonObject.getAsJsonObject("Response").getAsJsonArray("searchResults");
294+
List<JsonObject> jsonObjects = new ArrayList<>();
295+
List<BungieUser> users = new ArrayList<>();
273296

274-
for (JsonElement jsonElement : jsonArray) { // Add all user info objects into one list
275-
jsonObjects.add(jsonElement.getAsJsonObject());
297+
// Only continue looping if the result has a list of search results
298+
if (response.has("Response") && !response.getAsJsonObject("Response").getAsJsonArray("searchResults").isEmpty()) {
299+
JsonArray jsonArray = response.getAsJsonObject("Response").getAsJsonArray("searchResults");
300+
301+
for (JsonElement jsonElement : jsonArray) { // Add all user info objects into one list
302+
jsonObjects.add(jsonElement.getAsJsonObject());
303+
}
304+
} else {
305+
currentResponse = null;
306+
this.hasGrabbed = false;
307+
this.isDone = true;
308+
return false;
276309
}
277-
} else {
278-
break;
279-
}
280-
}
281310

282-
// Process the one big list to convert bungie.net profile info into destiny profile info
283-
for (JsonObject jsonObject : jsonObjects) {
284-
BungieUser bungieUser = processListOfProfiles(jsonObject.getAsJsonArray("destinyMemberships"));
311+
// Process the one big list to convert bungie.net profile info into destiny profile info
312+
for (JsonObject jsonObject : jsonObjects) {
313+
BungieUser bungieUser = processListOfProfiles(jsonObject.getAsJsonArray("destinyMemberships"));
285314

286-
if (bungieUser != null) {
287-
users.add(bungieUser);
288-
}
289-
}
315+
if (bungieUser != null) {
316+
users.add(bungieUser);
317+
}
318+
}
319+
320+
this.currentResponse = users;
321+
this.hasGrabbed = false;
322+
return true;
323+
}
290324

291-
return users;
325+
@Override
326+
public List<BungieUser> next() throws APIException {
327+
if(isDone) {
328+
return null;
329+
}
330+
331+
if(!hasGrabbed) {
332+
hasGrabbed = true;
333+
return this.currentResponse;
334+
} else {
335+
this.hasNext();
336+
return this.currentResponse;
337+
}
338+
}
339+
};
292340
}
293341

294342
/**
@@ -403,6 +451,33 @@ public static List<BungieUser> searchGlobalDisplayNames(String prefix) throws AP
403451
return bungieUsers;
404452
}
405453

454+
/**
455+
* Search for the clan with the given name. This is given as the preferred method for searching for clans by name.
456+
* @param search The search query
457+
* @return A single clan that matches the search query, null if none are found
458+
* @throws APIException If anything goes wrong when interacting with the API
459+
*/
460+
public static Clan searchClan(String search) throws APIException {
461+
JsonObject body = new JsonObject();
462+
body.addProperty("groupName", search);
463+
body.addProperty("groupType", GroupType.CLAN.getType());
464+
465+
JsonObject jsonObject = httpUtils.urlRequestPOST(HttpUtils.URL_BASE + "/GroupV2/NameV2/", body);
466+
467+
if(!jsonObject.has("Response")) {
468+
return null; // no clan found -- error 686
469+
}
470+
471+
jsonObject = jsonObject.getAsJsonObject("Response");
472+
473+
if(jsonObject.has("founder") && jsonObject.has("detail")) {
474+
long id = jsonObject.getAsJsonObject("detail").get("groupId").getAsLong();
475+
return new Clan(id, jsonObject.getAsJsonObject("detail"), jsonObject.getAsJsonObject("founder"));
476+
} else {
477+
return null;
478+
}
479+
}
480+
406481
public static String getApiKey() {
407482
return apiKey;
408483
}

src/main/java/net/dec4234/javadestinyapi/material/clan/Clan.java

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@
2626

2727
public class Clan extends ContentFramework {
2828

29-
private String apiKey = DestinyAPI.getApiKey();
3029
private HttpUtils hu = DestinyAPI.getHttpUtils();
31-
private JsonObject jo, cjo; // The entire Clan response
3230

3331
private long clanId = -1;
3432
private String clanName, clanDescription, motto;
@@ -37,19 +35,28 @@ public class Clan extends ContentFramework {
3735
private Date creationDate;
3836
private int memberCount = -1;
3937

40-
private List<BungieUser> admins, members;
38+
private ClanMember founder;
39+
private List<ClanMember> admins, members;
4140
private ClanManagement clanManagement;
42-
private JsonObject jj;
41+
private JsonObject jj, detail, founderJO;
4342

43+
/**
44+
* @param clanId The ID of the clan
45+
*/
4446
public Clan(long clanId) {
4547
super("https://www.bungie.net/platform/GroupV2/" + clanId + "/?components=200", source -> {
4648
return source.getAsJsonObject("Response");
4749
});
4850
this.clanId = clanId;
4951
}
5052

53+
/**
54+
* This is no longer the preferred method for searching by name. Use {@link DestinyAPI#searchClan(String)}
55+
* @param clanName The name of the clan that you would like to look at
56+
*/
57+
@Deprecated
5158
public Clan(String clanName) {
52-
super(("https://www.bungie.net/Platform/GroupV2/Name/" + clanName.replace(" ", "%20") + "/1/?components=200"), source -> {
59+
super(("https://www.bungie.net/Platform/GroupV2/Name/" + StringUtils.httpEncode(clanName) + "/1/?components=200"), source -> {
5360
return source.getAsJsonObject("Response");
5461
});
5562
this.clanName = clanName;
@@ -63,6 +70,15 @@ public Clan(long clanId, String clanName) {
6370
this.clanName = clanName;
6471
}
6572

73+
public Clan(long clanId, JsonObject detail, JsonObject founder) {
74+
super(("https://www.bungie.net/platform/GroupV2/" + clanId + "/?components=200"), source -> {
75+
return source.getAsJsonObject("Response");
76+
});
77+
this.clanId = clanId;
78+
this.detail = detail;
79+
this.founderJO = founder;
80+
}
81+
6682
public String getClanID() throws APIException {
6783
if (clanId == -1) {
6884
clanId = getDetail().get("groupId").getAsLong();
@@ -119,35 +135,37 @@ public boolean isAllowChat() throws APIException {
119135
/**
120136
* Get the founder of the clan
121137
*/
122-
public BungieUser getFounder() throws APIException {
123-
return new BungieUser(getJO().getAsJsonObject("founder").getAsJsonObject("destinyUserInfo").get("membershipId").getAsString());
138+
public ClanMember getFounder() throws APIException {
139+
if(founder != null) {
140+
return new ClanMember(founderJO);
141+
}
142+
143+
return new ClanMember(getJO().getAsJsonObject("founder"));
124144
}
125145

126146
/**
127-
* Returns a list of the founder and the admins of the clan
147+
* Returns a list of the founder and the admins of the clan.
128148
* The founder is always the first in this list?
129149
* Followed by the admins in the order they were promoted
130150
*/
131-
public List<BungieUser> getAdmins() throws APIException {
151+
public List<ClanMember> getAdmins() throws APIException {
132152
if (admins != null) {
133153
return admins;
134154
}
135155

136-
List<BungieUser> temp = new ArrayList<>();
156+
List<ClanMember> temp = new ArrayList<>();
137157
JsonArray ja = hu.urlRequestGET("https://www.bungie.net/Platform/GroupV2/" + getClanID() + "/AdminsAndFounder/?components=200").getAsJsonObject("Response").getAsJsonArray("results");
138158

139159
for (JsonElement je : ja) {
140-
temp.add(new BungieUser(je.getAsJsonObject().get("destinyUserInfo").getAsJsonObject().get("membershipId").getAsString()));
160+
temp.add(new ClanMember(je.getAsJsonObject()));
141161
}
142162

143163
admins = temp; // Cache this information
144164
return temp;
145165
}
146166

147167
/**
148-
* Returns the average number of days since all members last played.
149-
*
150-
* Thanks to the ClanMember system, requires very little calls and should be pretty fast.
168+
* @return A double representing the average amount of days since clan members have last logged in.
151169
*/
152170
public double getAverageInactivityAmongMembers() throws APIException {
153171
ArrayList<Double> averages = new ArrayList<>();
@@ -163,6 +181,10 @@ public double getAverageInactivityAmongMembers() throws APIException {
163181

164182
/**
165183
* Get the most inactive members of a clan, using the getMembers() endpoint and the ClanMember class
184+
* @param numberOfResults The number of inactive members you want
185+
* @param exclude An exclusion list of bungie IDs. If an inactive user has their id in this list then they will NOT
186+
* be included in the returned list.
187+
* @return A list of the most inactive members, sorted from most to least inactive
166188
*/
167189
public List<ClanMember> getMostInactiveMembers(int numberOfResults, String... exclude) throws APIException {
168190
List<ClanMember> list = getMembers();
@@ -348,6 +370,10 @@ public ClanManagement getClanManagement() {
348370
}
349371

350372
private JsonObject getDetail() throws APIException {
373+
if(detail != null) {
374+
return detail;
375+
}
376+
351377
return getJO().getAsJsonObject("detail");
352378
}
353379
}

src/main/java/net/dec4234/javadestinyapi/material/clan/ClanMember.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313

1414
import java.util.Date;
1515

16+
/**
17+
* Represents a "ClanMember" which is basically the same as a BungieUser but with some added-on info like join date,
18+
* is online and last online date. This information is pulled from the /Members/ list of a clan.
19+
*/
1620
public class ClanMember extends BungieUser {
1721

1822
private int memberType;
@@ -34,10 +38,19 @@ public int getMemberType() {
3438
return memberType;
3539
}
3640

41+
/**
42+
* @return True if this user is currently online in Destiny 2
43+
*/
3744
public boolean isOnline() {
3845
return isOnline;
3946
}
4047

48+
/**
49+
* When was the last time this user's online status changed. In other words, when they went from offline to online
50+
* or online to offline. If they are currently online then this could tell you how long they have been on in the
51+
* current playing session.
52+
* @return The date representing when their online status changed
53+
*/
4154
public Date getLastOnlineStatusChange() {
4255
if(lastOnline == null) {
4356
try {
@@ -50,14 +63,23 @@ public Date getLastOnlineStatusChange() {
5063
return lastOnline;
5164
}
5265

66+
/**
67+
* @return The number of days since this user's online status last changed
68+
*/
5369
public double getDaysSinceLastPlayed() {
5470
return StringUtils.getDaysSinceTime(getLastOnlineStatusChange());
5571
}
5672

73+
/**
74+
* @return The ID of the clan that this user belongs to
75+
*/
5776
public String getGroupId() {
5877
return groupId;
5978
}
6079

80+
/**
81+
* @return The date this user joined their current clan
82+
*/
6183
public Date getJoinDate() {
6284
return joinDate;
6385
}

0 commit comments

Comments
 (0)