Skip to content

Commit e589ac1

Browse files
committed
#22 - Don't refresh every time, documentation
1 parent 59171f0 commit e589ac1

File tree

11 files changed

+156
-80
lines changed

11 files changed

+156
-80
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright (c) 2024. dec4234
3+
* A standard open MIT license applies. Modififcation and usage permitted with credit. No warranties or express guarentees are given in any way.
4+
*
5+
* Github -> https://github.com/dec4234/JavaDestinyAPI
6+
*/
7+
8+
package net.dec4234.javadestinyapi.exceptions;
9+
10+
/**
11+
* Self explanatory. The access token needs to be regenerated using the refresh token.
12+
*/
13+
public class AccessTokenExpiredException extends APIException{
14+
}

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

Lines changed: 0 additions & 23 deletions
This file was deleted.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
/**
1111
* You either have insufficient permission to attempt this OAuth action, or you forgot to authorize yourself.
12+
* Alternatively, the access and/or refresh token has expired
1213
* See {@link net.dec4234.javadestinyapi.utils.framework.OAuthFlow}
1314
*/
1415
public class OAuthUnauthorizedException extends APIException {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright (c) 2024. dec4234
3+
* A standard open MIT license applies. Modififcation and usage permitted with credit. No warranties or express guarentees are given in any way.
4+
*
5+
* Github -> https://github.com/dec4234/JavaDestinyAPI
6+
*/
7+
8+
package net.dec4234.javadestinyapi.exceptions;
9+
10+
/**
11+
* The name is self-expanatory. The refresh token normally used to generate a new access token has expired since it
12+
* hasn't been refreshed in the past 90 days or it has been 1 year since the last oauth.
13+
* See {@link net.dec4234.javadestinyapi.utils.framework.OAuthFlow} to generate a new one.
14+
*/
15+
public class RefreshTokenExpiredException extends APIException {
16+
17+
}

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,56 +40,56 @@ public ClanManagement(Clan clan) {
4040
* Kicks this user from the clan
4141
*/
4242
public void kickPlayer(BungieUser bungieUser) throws APIException {
43-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/Kick/", "");
43+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/Kick/");
4444
}
4545

4646
/**
4747
* Bans the user from the clan
4848
*/
4949
public void banUser(BungieUser bungieUser) throws APIException {
50-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/Ban/", "");
50+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/Ban/");
5151
}
5252

5353
/**
5454
* Unbans this user from the clan, as long as they are banned, of course
5555
*/
5656
public void unbanUser(BungieUser bungieUser) throws APIException {
57-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/Unban/", "");
57+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/Unban/");
5858
}
5959

6060
/**
6161
* Invites the specified user to join the clan
6262
*/
6363
public void inviteUser(BungieUser bungieUser) throws APIException {
64-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/IndividualInvite/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/", "");
64+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/IndividualInvite/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/");
6565
}
6666

6767
/**
6868
* Cancels the invite for this user to join the clan
6969
*/
7070
public void cancelInvite(BungieUser bungieUser) throws APIException {
71-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/IndividualInviteCancel/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/", "");
71+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/IndividualInviteCancel/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/");
7272
}
7373

7474
/**
7575
* Approves this user's request to join the clan if and only if they have requested to join
7676
*/
7777
public void approvePendingMember(BungieUser bungieUser) throws APIException {
78-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/Approve/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/", "");
78+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/Approve/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/");
7979
}
8080

8181
/**
8282
* Approves all requests to join the clan
8383
*/
8484
public void approveAllPendingMembers() throws APIException {
85-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/ApproveAll/", "");
85+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/ApproveAll/");
8686
}
8787

8888
/**
8989
* Denies all pending requests to join the clan :)
9090
*/
9191
public void denyAllPendingMembers() throws APIException {
92-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/DenyAll/?components=200", "");
92+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/DenyAll/?components=200");
9393
}
9494

9595
/**
@@ -98,7 +98,7 @@ public void denyAllPendingMembers() throws APIException {
9898
* @param bungieUser The user who will be the new founder (leader) of the clan
9999
*/
100100
public void abdicateFoundership(BungieUser bungieUser) throws APIException {
101-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Admin/AbdicateFoundership/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/", "");
101+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Admin/AbdicateFoundership/" + bungieUser.getMembershipType() + "/" + bungieUser.getID() + "/");
102102
}
103103

104104
/**
@@ -112,7 +112,7 @@ public void addOptionalConversation(String chatName, ClanChatSecuritySetting cla
112112
jsonObject.addProperty("chatName", chatName);
113113
jsonObject.addProperty("chatSecurity", clanChatSecuritySetting.getSetting());
114114

115-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Admin/OptionalConversations/Add/", jsonObject.toString());
115+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Admin/OptionalConversations/Add/", jsonObject);
116116
}
117117

118118
/**
@@ -129,7 +129,7 @@ public void editOptionalConversation(String conversationID, boolean chatEnabled,
129129
jsonObject.addProperty("chatSecurity", clanChatSecuritySetting.getSetting());
130130
jsonObject.addProperty("chatEnabled", chatEnabled);
131131

132-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Admin/OptionalConversations/" + conversationID + "/Edit/", jsonObject.toString());
132+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Admin/OptionalConversations/" + conversationID + "/Edit/", jsonObject);
133133
}
134134

135135
/**
@@ -173,7 +173,7 @@ public List<BungieUser> getPendingMembers() throws APIException {
173173
public List<BungieUser> getInvitedMembers() throws APIException {
174174
List<BungieUser> temp = new ArrayList<>();
175175

176-
JsonArray ja = hu.urlRequestGETOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/Invited/?components=200").getAsJsonObject("Response").getAsJsonArray("results");
176+
JsonArray ja = hu.urlRequestGETOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/InvitedIndividuals/?components=200").getAsJsonObject("Response").getAsJsonArray("results");
177177

178178
for(JsonElement je : ja) {
179179
temp.add(new BungieUser(je.getAsJsonObject().getAsJsonObject("destinyUserInfo").get("membershipId").getAsString()));

src/main/java/net/dec4234/javadestinyapi/material/inventory/items/InventoryItem.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ public void setLockState(boolean state) throws APIException {
203203
jsonObject.addProperty("membershipType", getCharacterOwner().getMembershipType());
204204

205205

206-
httpUtils.urlRequestPOSTOauth(HttpUtils.URL_BASE + "/Destiny2/Actions/Items/SetLockState/", jsonObject.toString());
206+
httpUtils.urlRequestPOSTOauth(HttpUtils.URL_BASE + "/Destiny2/Actions/Items/SetLockState/", jsonObject);
207207
}
208208

209209
/**

src/main/java/net/dec4234/javadestinyapi/material/user/BungieUser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ public ActivityInfo getCurrentActivityInfo() throws APIException {
389389
* Request to join the specified clan
390390
*/
391391
public void requestToJoinClan(Clan clan) throws APIException {
392-
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/Apply/" + getMembershipType() + "/", "");
392+
hu.urlRequestPOSTOauth("https://www.bungie.net/Platform/GroupV2/" + clan.getClanID() + "/Members/Apply/" + getMembershipType() + "/");
393393
}
394394

395395
/**

src/main/java/net/dec4234/javadestinyapi/utils/HttpUtils.java

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
import com.google.gson.JsonSyntaxException;
1414
import net.dec4234.javadestinyapi.exceptions.APIException;
1515
import net.dec4234.javadestinyapi.exceptions.APIOfflineException;
16+
import net.dec4234.javadestinyapi.exceptions.AccessTokenExpiredException;
1617
import net.dec4234.javadestinyapi.exceptions.ConnectionException;
1718
import net.dec4234.javadestinyapi.exceptions.InvalidConditionException;
1819
import net.dec4234.javadestinyapi.exceptions.JsonParsingError;
20+
import net.dec4234.javadestinyapi.exceptions.OAuthUnauthorizedException;
21+
import net.dec4234.javadestinyapi.exceptions.RefreshTokenExpiredException;
1922
import net.dec4234.javadestinyapi.material.DestinyAPI;
2023
import net.dec4234.javadestinyapi.material.manifest.ManifestEntityTypes;
2124

@@ -50,19 +53,28 @@ public void setApiKey(String apiKey) {
5053
* Send a GET url request to the url provided, returns a JsonObject of the response
5154
*/
5255
public JsonObject urlRequestGET(String url) throws APIException {
53-
return getJsonObject(getStringResponse(getRequest(true, url, starter -> {
56+
return getJsonObject(getRequest(true, url, starter -> {
5457
starter.GET();
5558
return starter;
56-
})));
59+
}));
5760
}
5861

5962
public JsonObject urlRequestGETOauth(String url) throws APIException {
60-
setTokenViaRefresh();
61-
return getJsonObject(getStringResponse(getRequest(true, url, starter -> {
62-
starter.GET()
63-
.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken);
64-
return starter;
65-
})));
63+
try {
64+
return getJsonObject(getRequest(true, url, starter -> {
65+
starter.GET()
66+
.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken);
67+
return starter;
68+
}));
69+
} catch (AccessTokenExpiredException e) {
70+
setTokenViaRefresh();
71+
72+
return getJsonObject(getRequest(true, url, starter -> {
73+
starter.GET()
74+
.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken);
75+
return starter;
76+
}));
77+
}
6678
}
6779

6880
public JsonObject urlRequestPOST(String url, JsonObject body) throws APIException {
@@ -77,29 +89,10 @@ public JsonObject urlRequestPOST(String url, String body) throws APIException {
7789
System.out.println("Body: " + finalBody);
7890
}
7991

80-
return getJsonObject(getStringResponse(getRequest(true, url, starter -> {
92+
return getJsonObject(getRequest(true, url, starter -> {
8193
starter.setHeader("Content-Type", "application/json")
8294
.POST(HttpRequest.BodyPublishers.ofString(finalBody));
8395

84-
return starter;
85-
})));
86-
}
87-
88-
public String urlRequestPOSTOauth(String url, String body) throws APIException {
89-
setTokenViaRefresh();
90-
if (body.isEmpty()) { body = "{\"message\": \"\",}"; }
91-
92-
String finalBody = body;
93-
94-
if(DestinyAPI.isDebugEnabled()) {
95-
System.out.println("Body: " + finalBody);
96-
}
97-
98-
return getStringResponse(getRequest(true, url, starter -> {
99-
starter.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken)
100-
.setHeader("Content-Type", "application/json")
101-
.POST(HttpRequest.BodyPublishers.ofString(finalBody));
102-
10396
return starter;
10497
}));
10598
}
@@ -117,13 +110,28 @@ public JsonObject urlRequestPOSTOauth(String url, JsonObject body) throws APIExc
117110
System.out.println("Body: " + finalBody);
118111
}
119112

120-
return getJsonObject(getStringResponse(getRequest(true, url, starter -> {
121-
starter.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken)
122-
.setHeader("Content-Type", "application/json")
123-
.POST(HttpRequest.BodyPublishers.ofString(finalBody));
113+
try {
114+
return getJsonObject(getRequest(true, url, starter -> {
115+
starter.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken)
116+
.setHeader("Content-Type", "application/json")
117+
.POST(HttpRequest.BodyPublishers.ofString(finalBody));
118+
119+
return starter;
120+
}));
121+
} catch (AccessTokenExpiredException e) {
122+
setTokenViaRefresh();
123+
return getJsonObject(getRequest(true, url, starter -> {
124+
starter.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken)
125+
.setHeader("Content-Type", "application/json")
126+
.POST(HttpRequest.BodyPublishers.ofString(finalBody));
127+
128+
return starter;
129+
}));
130+
}
131+
}
124132

125-
return starter;
126-
})));
133+
public JsonObject urlRequestPOSTOauth(String url) throws APIException {
134+
return urlRequestPOSTOauth(url, new JsonObject());
127135
}
128136

129137
/**
@@ -146,13 +154,13 @@ public String setTokenViaRefresh() throws APIException {
146154

147155
String requestBody = "grant_type=refresh_token&refresh_token=" + DestinyAPI.getRefreshToken();
148156

149-
JsonObject response = getJsonObject(getStringResponse(getRequest(false, url, starter -> {
157+
JsonObject response = getJsonObject(getRequest(false, url, starter -> {
150158
starter.setHeader("Content-Type", "application/x-www-form-urlencoded")
151159
.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString((DestinyAPI.getClientId() + ":" + DestinyAPI.getClientSecret()).getBytes()))
152160
.POST(HttpRequest.BodyPublishers.ofString(requestBody));
153161

154162
return starter;
155-
})));
163+
}));
156164

157165
if(response.has("error_description") && response.get("error_description").getAsString().equals("ApplicationTokenKeyIdDoesNotExist")) {
158166
throw new InvalidConditionException("The refresh token is invalid, you likely need to generate new tokens");
@@ -182,13 +190,13 @@ public void setTokenViaAuth(String oAuthCode) throws APIException {
182190

183191
String requestBody = "grant_type=authorization_code&code=" + oAuthCode;
184192

185-
JsonObject jsonObject = getJsonObject(getStringResponse(getRequest(false, url, starter -> {
193+
JsonObject jsonObject = getJsonObject(getRequest(false, url, starter -> {
186194
starter.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString((DestinyAPI.getClientId() + ":" + DestinyAPI.getClientSecret()).getBytes()))
187195
.setHeader("Content-Type", "application/x-www-form-urlencoded")
188196
.POST(HttpRequest.BodyPublishers.ofString(requestBody));
189197

190198
return starter;
191-
})));
199+
}));
192200

193201
String accessToken = jsonObject.get("access_token").getAsString();
194202
String refreshToken = jsonObject.get("refresh_token").getAsString();
@@ -198,18 +206,41 @@ public void setTokenViaAuth(String oAuthCode) throws APIException {
198206
HttpUtils.bearerToken = accessToken;
199207
}
200208

201-
private JsonObject getJsonObject(String stringResponse) throws APIException {
209+
private JsonObject getJsonObject(HttpRequest httpRequest) throws APIException {
210+
HttpClient httpClient = HttpClient.newHttpClient();
211+
String responseString;
212+
213+
try { // TODO: are we even taking advantage of async? this seems pointless to just block right away
214+
responseString = httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()).thenApplyAsync(HttpResponse::body).get();
215+
216+
if (DestinyAPI.isDebugEnabled()) {
217+
System.out.println(httpRequest.method() + " " + httpRequest.uri().toString());
218+
System.out.println(responseString);
219+
}
220+
} catch (InterruptedException | ExecutionException e) {
221+
throw new ConnectionException(e);
222+
}
223+
202224
JsonObject jsonObject;
203225

204226
try {
205-
jsonObject = new JsonParser().parse(stringResponse).getAsJsonObject();
227+
jsonObject = new JsonParser().parse(responseString).getAsJsonObject();
206228
} catch (JsonSyntaxException e) {
207229
throw new JsonParsingError(e);
208230
}
209231

210-
// API Offline Check
211-
if(jsonObject.has("ErrorCode") && jsonObject.get("ErrorCode").getAsInt() == 5) {
212-
throw new APIOfflineException(jsonObject.get("Message").getAsString());
232+
// Check for API errors - https://bungie-net.github.io/multi/schema_Exceptions-PlatformErrorCodes.html#schema_Exceptions-PlatformErrorCodes
233+
if(jsonObject.has("ErrorCode")) {
234+
switch (jsonObject.get("ErrorCode").getAsInt()) { //TODO: lots of errors we could catch here
235+
case 5: // APIOffline
236+
throw new APIOfflineException(jsonObject.get("Message").getAsString());
237+
case 99: // WebAuthRequired
238+
throw new OAuthUnauthorizedException("OAuth - access denied. Try authenticating.");
239+
case 2111 | 2115: // AccessTokenHasExpired, OAuthAccessTokenExpired
240+
throw new AccessTokenExpiredException();
241+
case 2118: // RefreshTokenExpired -- need to reauth using oauth
242+
throw new RefreshTokenExpiredException();
243+
}
213244
}
214245

215246
return jsonObject;

src/main/java/net/dec4234/javadestinyapi/utils/framework/JDAOAuth.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
package net.dec4234.javadestinyapi.utils.framework;
1010

11+
/**
12+
* Contains all of the functions needed for the API to adequately store OAuth tokens.
13+
* <br>
14+
* Note: OAuth could allow potenially dangerous actions such as full control over your clan (if you are an admin) as
15+
* well as your inventory. Use at your own risk, and use good data management and protection practices.
16+
*/
1117
public interface JDAOAuth {
1218

1319
String getAccessToken();

0 commit comments

Comments
 (0)