├── .github └── workflows │ ├── main.yml │ └── maven.yml ├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main └── java └── net └── dec4234 └── javadestinyapi ├── exceptions ├── APIException.java ├── APIOfflineException.java ├── AccessTokenExpiredException.java ├── ConnectionException.java ├── InvalidConditionException.java ├── JsonParsingError.java ├── OAuthUnauthorizedException.java └── RefreshTokenExpiredException.java ├── material ├── DestinyAPI.java ├── clan │ ├── Clan.java │ ├── ClanChatSecuritySetting.java │ ├── ClanManagement.java │ ├── ClanMember.java │ └── GroupType.java ├── inventory │ ├── CollectionsManager.java │ ├── InventoryManager.java │ ├── items │ │ ├── Armor.java │ │ ├── DestinyItem.java │ │ ├── InventoryBucket.java │ │ ├── InventoryItem.java │ │ ├── ItemPerk.java │ │ └── ItemPlug.java │ └── loadouts │ │ └── Loadout.java ├── manifest │ ├── DestinyManifest.java │ └── ManifestEntityTypes.java ├── social │ ├── SocialFriend.java │ └── SocialFriendsList.java └── user │ ├── BungieUser.java │ ├── DestinyCharacter.java │ ├── DestinyPlatform.java │ ├── DestinyProfile.java │ ├── UserCredential.java │ └── UserCredentialType.java ├── responses └── user │ └── SanitizedUsernamesResponse.java ├── stats ├── activities │ ├── Activity.java │ ├── ActivityHistoryReview.java │ ├── ActivityIdentifier.java │ ├── ActivityInfo.java │ ├── ActivityMode.java │ └── ActivityParticipant.java └── character │ └── UserStats.java └── utils ├── HttpRequestModifier.java ├── HttpUtils.java ├── StringUtils.java ├── fast ├── ASyncPull.java ├── ASyncPuller.java └── Pagination.java └── framework ├── ContentFramework.java ├── ContentInterface.java ├── JDAOAuth.java ├── JsonOAuthManager.java ├── JsonObjectModifier.java ├── OAuthFlow.java └── OAuthManager.java /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: release version 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Build and Test 16 | uses: qcastel/github-actions-maven-cmd@master 17 | with: 18 | maven-args: "clean install -e" 19 | 20 | - name: Release 21 | uses: qcastel/github-actions-maven-release@master 22 | env: 23 | JAVA_HOME: /usr/lib/jvm/java-11-openjdk/ 24 | with: 25 | git-release-bot-name: "bot-idhub" 26 | git-release-bot-email: "bot@idhub.io" 27 | 28 | maven-args: "clean install -e" 29 | 30 | ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} 31 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 11 20 | uses: actions/setup-java@v2 21 | with: 22 | java-version: '11' 23 | distribution: 'adopt' 24 | - name: Build with Maven 25 | run: mvn -B package --file pom.xml 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /out/ 3 | /src/main/java/Testing.java 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 dec4234 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
5 | 6 | # JavaDestinyAPI 7 | This library is used as an interface to the API for the game Destiny 2, created by Bungie. 8 | 9 | The API is a work in-progress, contributions are welcome. Please note that development is on-going and the latest versions may be prone to bugs. If you find any please create an issue to report it. 10 | ## Getting Started 11 | This project can be currently accessed through the jitpack repository, which allows any github repo to be used as a dependency 12 | ```xml 13 |
24 | * This class is currently incomplete. If you would like to contribute, please create a pull request on GitHub
25 | */
26 | public class InventoryItem extends DestinyItem {
27 |
28 | private static HttpUtils httpUtils = DestinyAPI.getHttpUtils();
29 |
30 | private DestinyCharacter characterOwner;
31 | private ItemLocation itemLocation = ItemLocation.CHARACTER_INVENTORY;
32 |
33 | private String instanceId, bucketHash, overrideStyleItemHash;
34 | private InventoryBucket inventoryBucket;
35 |
36 | private int quantity, bindStatus, location, transferStatus, state, dismantlePermission;
37 | private boolean lockable = false, isWrapper;
38 |
39 | public InventoryItem(String instanceId, DestinyCharacter characterOwner) throws APIException {
40 | this(httpUtils.urlRequestGET(HttpUtils.URL_BASE + "/Destiny2/" + characterOwner.getMembershipType() + "/Profile/" + characterOwner.getMembershipID() + "/Item/" + instanceId + "/?components=305").getAsJsonObject("Response").getAsJsonObject("item").getAsJsonObject("data"));
41 |
42 | this.characterOwner = characterOwner;
43 | }
44 |
45 | public InventoryItem(String hashID, String instanceId, DestinyCharacter characterOwner) {
46 | super(hashID);
47 | this.instanceId = instanceId;
48 | this.characterOwner = characterOwner;
49 | }
50 |
51 | /**
52 | * Used if an item is in a profile-level inventory such as the "Inventory" or "Mods Page" as well as the vault
53 | */
54 | public InventoryItem(String hashID, String instanceId, DestinyCharacter characterOwner, ItemLocation itemLocation) {
55 | super(hashID);
56 | this.instanceId = instanceId;
57 |
58 | this.characterOwner = characterOwner;
59 | this.itemLocation = itemLocation;
60 | }
61 |
62 | public InventoryItem(String hashID, String instanceId, DestinyCharacter characterOwner, int quantity, int bindStatus, int location, String bucketHash, int transferStatus, boolean lockable, int state, int dismantlePermission, boolean isWrapper) {
63 | super(hashID);
64 | this.characterOwner = characterOwner;
65 | this.instanceId = instanceId;
66 |
67 | this.quantity = quantity;
68 | this.bindStatus = bindStatus;
69 | this.location = location;
70 | this.bucketHash = bucketHash;
71 | this.inventoryBucket = InventoryBucket.fromHash(bucketHash);
72 | this.transferStatus = transferStatus;
73 | this.lockable = lockable;
74 | this.state = state;
75 | this.dismantlePermission = dismantlePermission;
76 | this.isWrapper = isWrapper;
77 | }
78 |
79 | public InventoryItem(String hashID, String instanceId, DestinyCharacter characterOwner, int quantity, int bindStatus, int location, String bucketHash, int transferStatus, boolean lockable, int state, int dismantlePermission, String overrideStyleItemHash, boolean isWrapper) {
80 | super(hashID);
81 | this.instanceId = instanceId;
82 | this.characterOwner = characterOwner;
83 |
84 | this.quantity = quantity;
85 | this.bindStatus = bindStatus;
86 | this.location = location;
87 | this.bucketHash = bucketHash;
88 | this.transferStatus = transferStatus;
89 | this.lockable = lockable;
90 | this.state = state;
91 | this.dismantlePermission = dismantlePermission;
92 | this.overrideStyleItemHash = overrideStyleItemHash;
93 | this.isWrapper = isWrapper;
94 | }
95 |
96 | private InventoryItem(JsonObject jsonObject) {
97 | super(jsonObject.get("itemHash").getAsString());
98 |
99 |
100 | }
101 |
102 | /**
103 | * Gets the plugs on the item such as selected perks and mods
104 | *
105 | * @return Returns a list of ItemPlugs
106 | */
107 | public List
12 | * Used for accessing things in the Destiny Manifest
13 | * Sorted by category, then by alphabetical order
14 | */
15 | public enum ManifestEntityTypes {
16 |
17 | // Relating to items/character's inventory
18 | ARTIFACT("DestinyArtifactDefinition"),
19 | BREAKERTYPE("DestinyBreakerTypeDefinition"),
20 | COLLECTIBLE("DestinyCollectibleDefinition"),
21 | EQUIPMENTSLOT("DestinyEquipmentSlotDefinition"),
22 | INVENTORYBUCKET("DesintInventoryBucketDefinition"),
23 | /** Any item that can go in a user's invenory such as armor, weapons, and planetary materials */
24 | INVENTORYITEM("DestinyInventoryItemDefinition"),
25 | ITEMCATEGORY("DestinyItemCategoryDefinition"),
26 | ITEMSTAT("DestinyStatDefinition"),
27 | ITEMSTATGROUP("DestinyStatGroupDefinition"),
28 | ITEMTIER("DestinyItemTierTypeDefinition"),
29 | MATERIALREQUIREMENTSET("DestinyMaterialRequirementSetDefinition"),
30 | POWERCAP("DestinyPowerCapDefinition"),
31 | RECORD("DestinyRecordDefinition"),
32 | REWARDSOURCE("DestinyRewardSourceDefinition"),
33 | SANDBOXPERK("DestinySandboxPerkDefinition"),
34 | TALENTGRID("DestinyTalentGridDefinition"),
35 |
36 | // Relating to users
37 | CLASS("DestinyClassDefinition"),
38 | GENDER("DestinyGenderDefinition"),
39 | MILESTONE("DestinyMilestoneDefinition"),
40 | PROGRESSION("DestinyProgressionDefinition"),
41 | RACE("DestinyRaceDefinition"),
42 |
43 | // Vendors/world
44 | ACTIVITY("DestinyActivityDefinition"),
45 | ACTIVITYGRAPH("DestinyActivityGraphDefinition"),
46 | ACTIVITYMODE("DestinyActivityModeDefinition"),
47 | ACTIVITYMODIFIER("DestinyActivityModifierDefinition"),
48 | ACTIVITYTYPE("DestinyActivityTypeDefinition"),
49 | DAMAGETYPE("DestinyDamageTypeDefinition"),
50 | DESTINATION("DestinyDestinationDefinition"),
51 | FACTION("DestinyFactionDefinition"),
52 | LOCATION("DestinyLocationDefinition"),
53 | OBJECTIVE("DestinyObjectiveDefinition"),
54 | PLACE("DestinyPlaceDefinition"),
55 | VENDOR("DestinyVendorDefinition"),
56 | VENDORGROUP("DestinyVendorGroupDefinition"),
57 |
58 | // Misc
59 | CHECKLIST("DestinyChecklistDefinition"),
60 | ENERGYTYPE("DestinyEnergyTypeDefinition"),
61 | HISTORICALSTATS("DestinyHistoricalStatsDefinition"),
62 | PRESENTATIONNODE("DestinyPresentationNodeDefinition"),
63 | LORE("DestinyLoreDefinition"),
64 | METRIC("DestinyMetricDefinition"),
65 | PLUGSET("DestinyPlugSetDefinition"),
66 | REPORTREASONCATEGORY("DestinyReportReasonCategoryDefinition"),
67 | SEASON("DestinySeasonDefinition"),
68 | SEASONPASS("DestinySeasonPassDefinition"),
69 | SOCKETCATEGORY("DestinySocketCategoryDefinition"),
70 | SOCKETTYPE("DestinySocketTypeDefinition"),
71 | TAGMETADATA("TagMetadataDefinition"),
72 | TRAIT("DestinyTraitDefinition"),
73 | TRAITCATEGORY("DestinyTraitCategoryDefinition"),
74 | UNLOCK("DestinyUnlockDefinition");
75 |
76 | String bungieEntityValue;
77 |
78 | private ManifestEntityTypes(String bungieEntityValue) {
79 | this.bungieEntityValue = bungieEntityValue;
80 | }
81 |
82 | public String getBungieEntityValue() {
83 | return bungieEntityValue;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/material/social/SocialFriend.java:
--------------------------------------------------------------------------------
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.material.social;
9 |
10 | import com.google.gson.JsonObject;
11 |
12 | import java.util.Date;
13 |
14 | public class SocialFriend {
15 |
16 | // Outer
17 | private String id, bungieGlobalDisplayName;
18 | private int membershipType, bungieGlobalDisplayNameCode, onlineStatus, onlineTitle, relationship;
19 |
20 | // Bungie Net User
21 | private String bungieNetId, uniqueName, displayName, successMessageFlags, about, locale, profilePicturePath, profileThemeName, userTitleDisplay, statusText;
22 | private int profilePicture, profileTheme, userTitle;
23 | private boolean isDeleted, showActivity, localeInheritDefault, showGroupMessaging;
24 | private Date firstAccess, lastUpdate, statusDate;
25 | private String steamDisplayName, blizzardDisplayName, stadiaDisplayName, xboxDisplayName, psnDisplayName; // Null by default unless found in the response, could have any combo
26 |
27 |
28 | // Context
29 | private boolean isFollowing, isIgnored;
30 | private int ignoreFlags;
31 |
32 | public SocialFriend(JsonObject jsonObject) {
33 | JsonObject bungieNetUser = jsonObject.getAsJsonObject("bungieNetUser");
34 |
35 | id = jsonObject.get("lastSeenAsMembershipId").getAsString();
36 | membershipType = jsonObject.get("lastSeenAsBungieMembershipType").getAsInt();
37 | bungieGlobalDisplayName = jsonObject.get("bungieGlobalDisplayName").getAsString();
38 | bungieGlobalDisplayNameCode = jsonObject.get("bungieGlobalDisplayNameCode").getAsInt();
39 |
40 | onlineStatus = jsonObject.get("onlineStatus").getAsInt();
41 | onlineTitle = jsonObject.get("onlineTitle").getAsInt();
42 | relationship = jsonObject.get("relationship").getAsInt();
43 |
44 | bungieNetId = bungieNetUser.get("membershipId").getAsString();
45 | uniqueName = bungieNetUser.get("displayName").getAsString();
46 | profilePicture = bungieNetUser.get("profilePicture").getAsInt();
47 | profileTheme = bungieNetUser.get("profileTheme").getAsInt();
48 | }
49 | }
50 |
51 | /*
52 | {"Response":{"friends":[{"lastSeenAsMembershipId":"4611686018467335984","lastSeenAsBungieMembershipType":3,"bungieGlobalDisplayName":"Wertesse","bungieGlobalDisplayNameCode":4231,"onlineStatus":0,"onlineTitle":0,"relationship":1,"bungieNetUser":{"membershipId":"4047006","uniqueName":"Wertesse#4231","displayName":"Wertesse","profilePicture":70533,"profileTheme":6,"userTitle":0,"successMessageFlags":"8","isDeleted":false,"about":"0b6f70c8","firstAccess":"2013-10-09T19:45:31.861Z","lastUpdate":"2021-11-12T01:14:22.989Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"psnDisplayName":"Wertesse","xboxDisplayName":"Wertesse","showActivity":true,"locale":"en","localeInheritDefault":true,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/bungieday_11.jpg","profileThemeName":"d2cover","userTitleDisplay":"Newbie","statusText":"","statusDate":"0001-01-01T00:00:00Z","blizzardDisplayName":"Wertesse","steamDisplayName":"Wertesse","stadiaDisplayName":"Wertesse","cachedBungieGlobalDisplayName":"Wertesse","cachedBungieGlobalDisplayNameCode":4231}},{"lastSeenAsMembershipId":"4611686018475577660","lastSeenAsBungieMembershipType":3,"bungieGlobalDisplayName":"MagicTaco","bungieGlobalDisplayNameCode":6492,"onlineStatus":0,"onlineTitle":0,"relationship":1,"bungieNetUser":{"membershipId":"11420910","uniqueName":"MagicTaco#6492","displayName":"MagicTaco","profilePicture":70589,"profileTheme":1103,"userTitle":0,"successMessageFlags":"8","isDeleted":false,"about":"","firstAccess":"2015-08-11T03:07:39.073Z","lastUpdate":"2022-02-28T21:36:24.871Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"showActivity":true,"locale":"en","localeInheritDefault":false,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/bungie_day_15_02.jpg","profileThemeName":"d2_03","userTitleDisplay":"Newbie","statusText":"","statusDate":"0001-01-01T00:00:00Z","blizzardDisplayName":"Magictaco55","steamDisplayName":"MagicTaco","cachedBungieGlobalDisplayName":"MagicTaco","cachedBungieGlobalDisplayNameCode":6492}},{"lastSeenAsMembershipId":"4611686018511450218","lastSeenAsBungieMembershipType":3,"bungieGlobalDisplayName":"DIOSCABINET","bungieGlobalDisplayNameCode":2143,"onlineStatus":0,"onlineTitle":0,"relationship":1,"bungieNetUser":{"membershipId":"27343616","uniqueName":"DIOSCABINET#2143","displayName":"DIOSCABINET","profilePicture":70069,"profileTheme":1130,"userTitle":0,"successMessageFlags":"0","isDeleted":false,"about":"","firstAccess":"2021-01-07T10:28:55.813Z","lastUpdate":"2021-03-02T13:11:01.154Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"xboxDisplayName":"DIOSCABINET","showActivity":true,"locale":"en","localeInheritDefault":false,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/h3_icon.gif","profileThemeName":"d2_30","userTitleDisplay":"Newbie","statusText":"","statusDate":"0001-01-01T00:00:00Z","steamDisplayName":"DIOSCABINET","cachedBungieGlobalDisplayName":"DIOSCABINET","cachedBungieGlobalDisplayNameCode":2143}},{"lastSeenAsMembershipId":"4611686018467307956","lastSeenAsBungieMembershipType":3,"bungieGlobalDisplayName":"RarePepe","bungieGlobalDisplayNameCode":3185,"onlineStatus":1,"onlineTitle":2,"relationship":1,"bungieNetUser":{"membershipId":"14950176","uniqueName":"RarePepe#3185","displayName":"RarePepe","profilePicture":0,"profileTheme":0,"userTitle":0,"successMessageFlags":"0","isDeleted":false,"about":"","firstAccess":"2017-07-14T16:00:53.342Z","lastUpdate":"2019-08-20T20:12:33.326Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"showActivity":true,"locale":"en","localeInheritDefault":false,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/default_avatar.gif","profileThemeName":"d2cover","userTitleDisplay":"Newbie","statusText":"","statusDate":"0001-01-01T00:00:00Z","blizzardDisplayName":"RarePePe","steamDisplayName":"RarePepe","cachedBungieGlobalDisplayName":"RarePepe","cachedBungieGlobalDisplayNameCode":3185}},{"lastSeenAsMembershipId":"4611686018429577316","lastSeenAsBungieMembershipType":1,"bungieGlobalDisplayName":"MeatballGG","bungieGlobalDisplayNameCode":1711,"onlineStatus":1,"onlineTitle":2,"relationship":1,"bungieNetUser":{"membershipId":"13319567","uniqueName":"MeatballGG#1711","displayName":"MeatballGG","profilePicture":70637,"profileTheme":1103,"userTitle":0,"successMessageFlags":"0","isDeleted":false,"about":"","firstAccess":"2016-05-20T23:49:50.334Z","lastUpdate":"2021-09-16T18:31:28.274Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"psnDisplayName":"ItalagMD","xboxDisplayName":"ItalagPhD","showActivity":true,"locale":"en","localeInheritDefault":false,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/cc06.jpg","profileThemeName":"d2_03","userTitleDisplay":"Newbie","statusText":"","statusDate":"0001-01-01T00:00:00Z","steamDisplayName":"MeatballGG","stadiaDisplayName":"ItalagPhD#2924","twitchDisplayName":"italagphd","cachedBungieGlobalDisplayName":"MeatballGG","cachedBungieGlobalDisplayNameCode":1711}},{"lastSeenAsMembershipId":"4611686018430111996","lastSeenAsBungieMembershipType":1,"bungieGlobalDisplayName":"Etho","bungieGlobalDisplayNameCode":880,"onlineStatus":0,"onlineTitle":0,"relationship":1,"bungieNetUser":{"membershipId":"12955985","uniqueName":"Etho#880","displayName":"Etho","profilePicture":70652,"profileTheme":111,"userTitle":0,"successMessageFlags":"32","isDeleted":false,"about":"","firstAccess":"2016-02-20T16:54:10.195Z","lastUpdate":"2020-06-04T20:01:39.915Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"psnDisplayName":"heavenly_sl","xboxDisplayName":"ER 3RR","showActivity":true,"locale":"en","localeInheritDefault":false,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/cc22.jpg","profileThemeName":"d2cover","userTitleDisplay":"Newbie","statusText":"To much core not enough muscle","statusDate":"2017-01-10T22:46:47.29Z","steamDisplayName":"Etho","cachedBungieGlobalDisplayName":"Etho","cachedBungieGlobalDisplayNameCode":880}},{"lastSeenAsMembershipId":"4611686018503453178","lastSeenAsBungieMembershipType":3,"bungieGlobalDisplayName":"MyLittleCocky","bungieGlobalDisplayNameCode":2781,"onlineStatus":0,"onlineTitle":0,"relationship":1,"bungieNetUser":{"membershipId":"25599365","uniqueName":"25599365","displayName":"MyLittleCocky","profilePicture":0,"profileTheme":0,"userTitle":0,"successMessageFlags":"0","isDeleted":false,"about":"","firstAccess":"2020-05-15T06:15:15.214Z","lastUpdate":"2020-05-15T06:15:15.214Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"xboxDisplayName":"FR MOULD","showActivity":true,"locale":"en","localeInheritDefault":false,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/default_avatar.gif","profileThemeName":"d2cover","userTitleDisplay":"Newbie","statusText":"","statusDate":"0001-01-01T00:00:00Z","steamDisplayName":"MyLittleCocky"}},{"lastSeenAsMembershipId":"4611686018473535234","lastSeenAsBungieMembershipType":3,"bungieGlobalDisplayName":"Hydro_Neck","bungieGlobalDisplayNameCode":3789,"onlineStatus":0,"onlineTitle":0,"relationship":1,"bungieNetUser":{"membershipId":"18117827","uniqueName":"Hydro_Neck#3789","displayName":"Hydro_Neck","profilePicture":70700,"profileTheme":1125,"userTitle":0,"successMessageFlags":"0","isDeleted":false,"about":"","firstAccess":"2018-05-27T02:40:00.451Z","lastUpdate":"2019-09-05T04:48:38.525Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"psnDisplayName":"Hydro_Neck","xboxDisplayName":"KnavishHorizon1","showActivity":true,"locale":"en","localeInheritDefault":false,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/cc72.jpg","profileThemeName":"d2_25","userTitleDisplay":"Newbie","statusText":"","statusDate":"0001-01-01T00:00:00Z","blizzardDisplayName":"HydroNeck#1482","steamDisplayName":"Hydro_Neck","twitchDisplayName":"hydro_neck","cachedBungieGlobalDisplayName":"Hydro_Neck","cachedBungieGlobalDisplayNameCode":3789}},{"lastSeenAsMembershipId":"4611686018470828611","lastSeenAsBungieMembershipType":3,"bungieGlobalDisplayName":"SJ","bungieGlobalDisplayNameCode":7821,"onlineStatus":0,"onlineTitle":0,"relationship":1,"bungieNetUser":{"membershipId":"16619921","uniqueName":"16619921","displayName":"SJD","profilePicture":70516,"profileTheme":1119,"userTitle":0,"successMessageFlags":"0","isDeleted":false,"about":"","firstAccess":"2017-10-24T23:44:02.897Z","lastUpdate":"2021-09-04T20:56:14.075Z","context":{"isFollowing":false,"ignoreStatus":{"isIgnored":false,"ignoreFlags":0}},"showActivity":false,"locale":"en","localeInheritDefault":false,"showGroupMessaging":true,"profilePicturePath":"/img/profile/avatars/Destiny25.jpg","profileThemeName":"d2_19","userTitleDisplay":"Newbie","statusText":"","statusDate":"0001-01-01T00:00:00Z","blizzardDisplayName":"SJD","steamDisplayName":"SJ","cachedBungieGlobalDisplayName":""}}]},"ErrorCode":1,"ThrottleSeconds":0,"ErrorStatus":"Success","Message":"Ok","MessageData":{}}
53 | */
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/material/social/SocialFriendsList.java:
--------------------------------------------------------------------------------
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.material.social;
9 |
10 | import com.google.gson.JsonElement;
11 | import com.google.gson.JsonObject;
12 | import net.dec4234.javadestinyapi.exceptions.APIException;
13 | import net.dec4234.javadestinyapi.material.DestinyAPI;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | import static net.dec4234.javadestinyapi.utils.HttpUtils.URL_BASE;
19 |
20 | public class SocialFriendsList {
21 |
22 | public List
31 | * There is a lot of character-specific information that is captured within this class.
32 | */
33 | public class DestinyCharacter extends ContentFramework {
34 |
35 | private BungieUser bungieUser;
36 |
37 | private String characterID, minutesPlayedTotal;
38 | private Date lastPlayed;
39 | private int minutesPlayedThisSession, lightLevel = -1;
40 |
41 | private Gender gender;
42 | private DestinyClass d2class;
43 | private Race race;
44 |
45 | private String emblemPath, emblemBackgroundPath, emblemHash;
46 |
47 | private List
232 | * This is an OAUTH action that required inventory reading permission.
233 | * @return A list of inventory items, equipped or unequipped
234 | */
235 | public List
266 | * This function is currently incomplete. If you would like to contribute, please create a pull request on GitHub
267 | * @return A list of loadouts on this character
268 | */
269 | public List
160 | * This prints a list of hashes and the names to put inside {@link ActivityIdentifier}
161 | * @param activityIdentifier Finds all unknown hashes that share the same mode as this activity. Like Raid/Strike/etc.
162 | */
163 | public void getUndiscoveredActivityHashes(ActivityIdentifier activityIdentifier) throws APIException {
164 | BungieUser bungieUser = new BungieUser("4611686018467284386"); // just use a tryhard player that does every activity (Datto)
165 |
166 | for (DestinyCharacter destinyCharacter : bungieUser.getCharacters()) {
167 | for (int i = 0; i < 250; i++) {
168 | JsonObject jo = httpUtils.urlRequestGET("https://www.bungie.net/Platform/Destiny2/" + bungieUser.getMembershipType() + "/Account/" + bungieUser.getID() + "/Character/" + destinyCharacter.getCharacterID() + "/Stats/Activities/?page=" + i + "&count=250&mode=" + activityIdentifier.getMode().getBungieValue());
169 |
170 | if (!jo.getAsJsonObject("Response").has("activities")) {
171 | break;
172 | }
173 |
174 | JsonArray ja = jo.getAsJsonObject("Response").getAsJsonArray("activities");
175 | for (JsonElement je : ja) {
176 | JsonObject jo1 = je.getAsJsonObject();
177 | String hash = jo1.getAsJsonObject("activityDetails").get("referenceId").getAsString();
178 | if (ActivityIdentifier.fromHash(hash) == null) {
179 | JsonObject jo2 = httpUtils.manifestGET(ManifestEntityTypes.ACTIVITY, hash);
180 | if (jo2.has("Response") && jo2.getAsJsonObject("Response").has("displayProperties") && jo2.getAsJsonObject("Response").getAsJsonObject("displayProperties").has("name")) {
181 | jo2 = jo2.getAsJsonObject("Response").getAsJsonObject("displayProperties");
182 | System.out.println(jo2.get("name").getAsString() + " - " + hash);
183 | }
184 | }
185 |
186 | }
187 | }
188 |
189 | }
190 | }
191 |
192 | private JsonArray[] getArrays(ActivityIdentifier activityIdentifier, BungieUser bungieUser, DestinyCharacter destinyCharacter) throws APIException {
193 | List
13 | * Note that each hash represents something different, like the different rotations or challenges of Leviathan.
14 | * I have not investigated this very deeply but that's what I suspect. You can probably use the manifest to find out
15 | * more information.
16 | */
17 | public enum ActivityIdentifier {
18 |
19 | // STRIKES
20 | ARMS_DEALER("AD", ActivityMode.STRIKE, "442671778", "2080275457", "2378719026", "2724706103", "2378719025", "770196931", "3240321863", "1258914202", "1679518121"),
21 | LAKE_OF_SHADOWS("LOS", ActivityMode.STRIKE, "2318521576", "3711627564", "3725993747", "2630091891", "4134816102"),
22 |
23 | THE_DISGRACED("TD", ActivityMode.STRIKE, "1684420962", "174131855"),
24 | FALLEN_SABER("FS", ActivityMode.STRIKE, "3597990372", "3777220691"),
25 | DEVILS_LAIR("DL", ActivityMode.STRIKE, "969982762"),
26 |
27 | SAVATHUNS_SONG("SS", ActivityMode.STRIKE, "2359594803", "1101792305", "3191123858", "649648599", "1542611209"),
28 |
29 | INVERTED_SPIRE("IS", ActivityMode.STRIKE, "3704910925", "1563393783", "286562305", "1107473294", "1743518003", "338662534", "2753180142", "1743518000", "467266668"),
30 | EXODUS_CRASH("EC", ActivityMode.STRIKE, "2459768558", "1549614516", "4260306233", "1930116823", "2479262829", "1930116820", "2971335647"),
31 | INSIGHT_TERMINUS("IT", ActivityMode.STRIKE, "3751421841", "291911094", "3735153516", "3735153519"),
32 | PROVING_GROUND("PG", ActivityMode.STRIKE, "546528643", "1754609040"),
33 |
34 | THE_PYRAMIDION("P", ActivityMode.STRIKE, "1035135049", "1603374112", "1332567112", "2704613535", "1332567115", "981383202", "2799837309", "4261351281"),
35 | FESTERING_CORE("FC", ActivityMode.STRIKE, "1035850837", "3596828104"),
36 |
37 | TREE_OF_PROBABILITIES("TOP", ActivityMode.STRIKE, "2678510381", "1263901594", "561345572", "561345575", "840678113", "4085493024", "2684121894"),
38 | A_GARDEN_OF_WORLD("AGOW", ActivityMode.STRIKE, "656703508", "3676029623", "2230236215", "2230236212", "689927878", "117447065", "2579344189", "743963294"),
39 |
40 | STRANGE_TERRAIN("ST", ActivityMode.STRIKE, "2992505404", "861639649", "3801775390", "2248296964", "861639650"),
41 | WILL_OF_THE_THOUSANDS("WOTT", ActivityMode.STRIKE, "1198216109", "3944547192", "3510043585", "1317492847", "1891220709", "3944547195"),
42 |
43 | WARDEN_OF_NOTHING("WON", ActivityMode.STRIKE, "1360385764", "1360385767", "1134446996", "1493405720"),
44 | THE_HOLLOWED_LAIR("THL", ActivityMode.STRIKE, "663301842", "1475539136", "1475539139", "955874134"),
45 | BROODHOLD("BRO", ActivityMode.STRIKE, "1666283939", "3813623455"),
46 |
47 | THE_CORRUPTED("TC", ActivityMode.STRIKE, "3374205762", "723056533", "224295651"),
48 |
49 | THE_SCARLET_KEEP("TSK", ActivityMode.STRIKE, "1775791936", "3879143309", "3643233460", "2047723007", "346345236"),
50 |
51 | THE_GLASSWAY("TG", ActivityMode.STRIKE, "2226120409", "3965479856", "3329390423"),
52 |
53 | QUEST_EXODUS_CRASH("QEC", ActivityMode.STRIKE, "940394831"),
54 |
55 | // NIGHTFALLS
56 | ORDEAL_ADEPT("OA", ActivityMode.ALL_STRIKES, "887176540", "3265488360", "1203950596", "1753547897"),
57 | ORDEAL_HERO("OH", ActivityMode.ALL_STRIKES, "3849697859", "887176543", "3265488363", "2136458567", "265186830", "1203950599", "3293630131", "1358381371"),
58 | ORDEAL_LEGEND("OL", ActivityMode.ALL_STRIKES, "3849697858", "1302909045", "2136458566", "265186831", "2599001913", "1495545954", "3233498448"),
59 | ORDEAL_MASTER("OM", ActivityMode.ALL_STRIKES, "3233498455", "2136458561", "2136458567"),
60 | ORDEAL_GRANDMASTER("OGM", ActivityMode.ALL_STRIKES, "2136458560", "1753547901", "265186825"),
61 |
62 | ARMS_DEALER_NF("ADNF", ActivityMode.NIGHTFALL, "145302664", "3145298904"),
63 | ARMS_DEALER_NF_PRESTIGE("ADNFP", ActivityMode.ALL_STRIKES, "601540706"),
64 | QUEST_ARMS_DEALER_PRESTIGE("QADP", ActivityMode.HEROIC_NIGHTFALL, "1207505828"),
65 | LAKE_OF_SHADOWS_NF("LOSNF", ActivityMode.ALL_STRIKES, "3372160277"),
66 |
67 | SAVATHUNS_SONG_NF("SSNF", ActivityMode.NIGHTFALL, "1975064760", "3280234344"),
68 | SAVATHUNS_SONG_NF_PRESTIGE("SSNFP", ActivityMode.ALL_STRIKES, "585071442"),
69 |
70 | INVERTED_SPIRE_NF("ISNF", ActivityMode.NIGHTFALL, "3368226533", "4259769141"),
71 | INVERTED_SPIRE_NF_PRESTIGE("ISNFP", ActivityMode.NIGHTFALL, "3050465729"),
72 | EXODUS_CRASH_NF("ECNF", ActivityMode.ALL_STRIKES, "1282886582"),
73 | INSIGHT_TERMINUS_NF("ITNF", ActivityMode.ALL_STRIKES, "1034003646"),
74 |
75 | THE_PYRAMIDION_NF("TPNF", ActivityMode.NIGHTFALL, "926940962", "3289589202"),
76 | THE_PYRAMIDION_NF_PRESTIGE("TPNFP", ActivityMode.HEROIC_NIGHTFALL, "1129066976"),
77 | UNIDENTIFIED_FRAME_THE_PYRAMIDION("UFTP", ActivityMode.HEROIC_NIGHTFALL, "1431348899"),
78 |
79 | TREE_OF_PROBABILITIES_NF("TOPNF", ActivityMode.NIGHTFALL, "2046332536"),
80 | TREE_OF_PROBABILITIES_NF_PRESTIGE("TOPNFP", ActivityMode.NIGHTFALL, "2416546450"),
81 | A_GARDEN_WORLD_NF("AGWNF", ActivityMode.ALL_STRIKES, "2322829199", "936308438"),
82 | A_GARDEN_WORLD_NF_PRESTIGE("AGWP", ActivityMode.ALL_STRIKES, "2688061647"),
83 |
84 | STRANGE_TERRAIN_NF("STNF", ActivityMode.ALL_STRIKES, "2179568029", "4279557030", "522318687"),
85 | STRANGE_TERRAIN_NF_PRESTIGE("STNFP", ActivityMode.ALL_STRIKES, "1794007817"),
86 | WILL_OF_THE_THOUSANDS_NF("WOTT", ActivityMode.NIGHTFALL, "3907468134", "272852450"),
87 | WILL_OF_THE_THOUSANDS_NF_PRESTIGE("WOTTP", ActivityMode.NIGHTFALL, "2383858990"),
88 |
89 | THE_HOLLOWED_LAIR_NF("THLNF", ActivityMode.ALL_STRIKES, "3701132453"),
90 | WARDEN_OF_NOTHING_NF("WONNF", ActivityMode.ALL_STRIKES, "3108813009"),
91 |
92 | THE_CORRUPTED_NF("TCNF", ActivityMode.NIGHTFALL, "3388474648", "3034843176"),
93 |
94 | // LOST SECTORS
95 | BUNKER_E15_LEGEND("BE15L", ActivityMode.ALL_STRIKES, "1648125541"),
96 | BUNKER_E15_MASTER("BE15M", ActivityMode.ALL_STRIKES, "1648125538"),
97 |
98 | K1_CREW_QUARTERS_LEGEND("K1CQL", ActivityMode.ALL_STRIKES, "184186581"),
99 |
100 | CONCEALED_VOID_LEGEND("CVL", ActivityMode.ALL_STRIKES, "912873277"),
101 |
102 | // SEASONAL CRAP
103 | THE_COIL_MATCHMADE("COIL", ActivityMode.NIGHTFALL, "443581538"),
104 | THE_COIL_PRIVATE("COIL", ActivityMode.NIGHTFALL, "4233082192"),
105 |
106 | // EVENTS
107 | GUARDIAN_GAMES_COMPETITIVE_NIGHTFALL("GGCNF", ActivityMode.NIGHTFALL, "717738476", "1207998557", "1664895903", "1823573320", "1298659439", "2038344197", "3787810787", "4189303825"),
108 | GUARDIAN_GAMES_TRAINING_PLAYLIST("GGCNF", ActivityMode.NIGHTFALL, "14936429", "1413951275", "1705059120", "2550911054", "552852420", "2758636626"),
109 |
110 | // RAIDS
111 | LEVIATHAN("LEV", "https://www.bungie.net/img/destiny_content/pgcr/raid_gluttony.jpg", ActivityMode.RAID, "2693136600", "2693136601", "2693136602", "2693136603", "2693136604", "2693136605"),
112 | LEVIATHAN_PRESTIGE("LEVP", "https://www.bungie.net/img/destiny_content/pgcr/raid_gluttony.jpg", ActivityMode.RAID, "1685065161", "3446541099", "3879860661", "417231112", "2449714930", "757116822"),
113 | EATER_OF_WORLDS("EOW", "https://www.bungie.net/img/destiny_content/pgcr/raids_leviathan_eater_of_worlds.jpg", ActivityMode.RAID, "3089205900"),
114 | EATER_OF_WORLDS_PRESTIGE("EOWP", "https://www.bungie.net/img/destiny_content/pgcr/raids_leviathan_eater_of_worlds.jpg", ActivityMode.RAID, "809170886"),
115 | SPIRE_OF_STARS("SOS", "https://www.bungie.net/img/destiny_content/pgcr/raid_greed.jpg", ActivityMode.RAID, "119944200"),
116 | SPIRE_OF_STARS_PRESTIGE("SOSP", "https://www.bungie.net/img/destiny_content/pgcr/raid_greed.jpg", ActivityMode.RAID, "3213556450"),
117 |
118 | LAST_WISH("LW", "https://www.bungie.net/img/destiny_content/pgcr/raid_beanstalk.jpg", ActivityMode.RAID, "2122313384"),
119 | SCOURGE_OF_THE_PAST("SOTP", "https://www.bungie.net/img/destiny_content/pgcr/raids.1305rh0093145r13t5hn10tnz.raid_sunset.jpg", ActivityMode.RAID, "548750096"),
120 | CROWN_OF_SORROW("COS", "https://www.bungie.net/img/destiny_content/pgcr/raid_eclipse.jpg", ActivityMode.RAID, "3333172150"),
121 |
122 | GARDEN_OF_SALVATION("GOS", "https://www.bungie.net/img/destiny_content/pgcr/raid_garden_of_salvation.jpg", ActivityMode.RAID, "2659723068", "3458480158"),
123 |
124 | DEEP_STONE_CRYPT("DSC", "https://www.bungie.net/img/destiny_content/pgcr/europa-raid-deep-stone-crypt.jpg", ActivityMode.RAID, "910380154"),
125 |
126 | VAULT_OF_GLASS("VOG", "https://www.bungie.net/pubassets/pkgs/150/150569/FrontpageBanner_1920x590.jpg?cv=3983621215&av=1926358162", ActivityMode.RAID, "3881495763"),
127 | VAULT_OF_GLASS_MASTER("VOGM", "https://www.bungie.net/pubassets/pkgs/150/150569/FrontpageBanner_1920x590.jpg?cv=3983621215&av=1926358162", ActivityMode.RAID, "1681562271", "3022541210"),
128 | VAULT_OF_GLASS_CHALLENGE_MODE("VOGCM", "https://www.bungie.net/pubassets/pkgs/150/150569/FrontpageBanner_1920x590.jpg?cv=3983621215&av=1926358162", ActivityMode.RAID, "1485585878"),
129 |
130 | VOW_OF_THE_DISCIPLE("VOTD", "https://www.bungie.net/pubassets/pkgs/157/157111/FrontPageBanner_1920x590.jpg", ActivityMode.RAID, "1441982566"),
131 | VOW_OF_THE_DISCIPLE_MASTER("VOTDM", "https://www.bungie.net/pubassets/pkgs/157/157111/FrontPageBanner_1920x590.jpg", ActivityMode.RAID, "4217492330"),
132 |
133 | KINGS_FALL("KF", "https://www.bungie.net/pubassets/pkgs/170/170501/ArticleBanner_997x500.jpg", ActivityMode.RAID, "1374392663"),
134 | KINGS_FALL_MASTER("KFM", "https://www.bungie.net/pubassets/pkgs/170/170501/ArticleBanner_997x500.jpg", ActivityMode.RAID, "2964135793", "3257594522"),
135 | KINGS_FALL_LEGEND("KFL", "https://www.bungie.net/pubassets/pkgs/170/170501/ArticleBanner_997x500.jpg", ActivityMode.RAID, "1063970578"),
136 |
137 | ROOT_OF_NIGHTMARES("RON", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt745cb94aef1d49fd/640a30abe16bc77f58332cc4/FrontPageBanner_PC_1920x590.jpg", ActivityMode.RAID, "2381413764"),
138 | ROOT_OF_NIGHTMARES_MASTER("RONM", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt745cb94aef1d49fd/640a30abe16bc77f58332cc4/FrontPageBanner_PC_1920x590.jpg", ActivityMode.RAID, "2918919505"),
139 |
140 | CROTAS_END("CE", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt8023dab456754205/64f0c82d8c8fe96dfe98bbf8/16x9Standard_1920x1080.jpg", ActivityMode.RAID, "4179289725"),
141 | CROTAS_END_MASTER("CEM", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt8023dab456754205/64f0c82d8c8fe96dfe98bbf8/16x9Standard_1920x1080.jpg", ActivityMode.RAID, "1507509200"),
142 | CROTAS_END_LEGEND("CEL", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt8023dab456754205/64f0c82d8c8fe96dfe98bbf8/16x9Standard_1920x1080.jpg", ActivityMode.RAID, "156253568"),
143 |
144 | PANTHEON_ATRAKS("PANA", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt5394fcdccef3f5ac/66290d35a9b0ab6193b925f0/thisweekatbungie-7_28_22.png", ActivityMode.RAID, "4169648179"),
145 | PANTHEON_ORYX("PANO", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt5394fcdccef3f5ac/66290d35a9b0ab6193b925f0/thisweekatbungie-7_28_22.png", ActivityMode.RAID, ""),
146 | PANTHEON_RHULK("PANR", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt5394fcdccef3f5ac/66290d35a9b0ab6193b925f0/thisweekatbungie-7_28_22.png", ActivityMode.RAID, ""),
147 | PANTHEON_NEZAREC("PANN", "https://images.contentstack.io/v3/assets/blte410e3b15535c144/blt5394fcdccef3f5ac/66290d35a9b0ab6193b925f0/thisweekatbungie-7_28_22.png", ActivityMode.RAID, "");
148 |
149 | private String identifier;
150 | private String[] hashes;
151 | private ActivityMode mode;
152 | private String pgcrImage;
153 |
154 | ActivityIdentifier(String identifier, ActivityMode mode, String... hashes) {
155 | this.identifier = identifier;
156 | this.hashes = hashes;
157 | this.mode = mode;
158 | }
159 |
160 | ActivityIdentifier(String identifier, String pgcrImage, ActivityMode mode, String... hashes) {
161 | this.identifier = identifier;
162 | this.pgcrImage = pgcrImage;
163 | this.hashes = hashes;
164 | this.mode = mode;
165 | }
166 |
167 | public String getIdentifier() {
168 | return identifier;
169 | }
170 |
171 | public String getPgcrImage() { return pgcrImage; }
172 |
173 | /**
174 | * Get the directorActivityHash of the Activity
175 | * Can be used to compare different activities
176 | */
177 | public String[] getHashes() {
178 | return hashes;
179 | }
180 |
181 | public ActivityMode getMode() { return mode; }
182 |
183 | public static ActivityIdentifier fromShorthand(String shortHand) {
184 | for(ActivityIdentifier activityIdentifier : ActivityIdentifier.values()) {
185 | if(activityIdentifier.getIdentifier().equals(shortHand)) {
186 | return activityIdentifier;
187 | }
188 | }
189 |
190 | return null;
191 | }
192 |
193 | public static ActivityIdentifier fromHash(String hash) {
194 | for(ActivityIdentifier activityIdentifier : ActivityIdentifier.values()) {
195 | for(String s : activityIdentifier.getHashes()) {
196 | if(s.equals(hash)) {
197 | return activityIdentifier;
198 | }
199 | }
200 | }
201 |
202 | return null;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/stats/activities/ActivityInfo.java:
--------------------------------------------------------------------------------
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.stats.activities;
9 |
10 | import com.google.gson.JsonObject;
11 | import net.dec4234.javadestinyapi.exceptions.APIException;
12 | import net.dec4234.javadestinyapi.material.manifest.ManifestEntityTypes;
13 | import net.dec4234.javadestinyapi.utils.framework.ContentFramework;
14 |
15 | public class ActivityInfo extends ContentFramework {
16 |
17 | private String name, hash, completionUnlockHash, destinationHash, placeHash, activityTypeHash, activityModeHash, description, icon, pgcrImage, releaseIcon;
18 | private int modeType, activityModeCategory, releaseTime, activityLightLevel, tier, minParty, maxParty, maxPlayers;
19 | private ActivityMode activityMode;
20 | private boolean hasIcon, isPlaylist, inheritFromFreeRoam, suppressOtherRewards, isMatchmade, requiresGuardianOath, isPvP;
21 |
22 | public ActivityInfo(String hash) throws APIException {
23 | super(ManifestEntityTypes.ACTIVITY, hash, source -> {
24 | return source.getAsJsonObject("Response");
25 | });
26 |
27 | // Intialize values of the Activity Mode
28 | this.name = getDisplayProperties().get("name").getAsString();
29 | this.description = getDisplayProperties().get("description").getAsString();
30 |
31 | this.hasIcon = getDisplayProperties().get("hasIcon").getAsBoolean();
32 |
33 | if(hasIcon) {
34 | icon = getDisplayProperties().get("icon").getAsString();
35 | }
36 |
37 | // this.pgcrImage = getJO().get("pgcrImage").getAsString();
38 | this.releaseIcon = getJO().get("releaseIcon").getAsString();
39 |
40 | // Hashes
41 | this.hash = hash;
42 | this.completionUnlockHash = getJO().get("completionUnlockHash").getAsString();
43 | this.destinationHash = getJO().get("destinationHash").getAsString();
44 | this.placeHash = getJO().get("placeHash").getAsString();
45 | this.activityTypeHash = getJO().get("activityTypeHash").getAsString();
46 | // this.activityModeHash = getJO().get("activityModeHash").getAsString();
47 |
48 | // Numbers
49 | if(getJO().has("directActivityModeType")) {
50 | this.modeType = getJO().get("directActivityModeType").getAsInt();
51 | this.activityMode = ActivityMode.fromBungieValue(modeType);
52 | }
53 |
54 | if(getJO().has("activityModeCategory")) {
55 | this.activityModeCategory = getJO().get("activityModeCategory").getAsInt();
56 | }
57 | this.releaseTime = getJO().get("releaseTime").getAsInt();
58 | this.tier = getJO().get("tier").getAsInt();
59 | this.activityLightLevel = getJO().get("activityLightLevel").getAsInt();
60 |
61 | // Random booleans
62 | this.isPlaylist = getJO().get("isPlaylist").getAsBoolean();
63 | this.inheritFromFreeRoam = getJO().get("inheritFromFreeRoam").getAsBoolean();
64 | this.suppressOtherRewards = getJO().get("suppressOtherRewards").getAsBoolean();
65 | this.isPvP = getJO().get("isPvP").getAsBoolean();
66 |
67 | // Matchmaking
68 | this.isMatchmade = getMatchmaking().get("isMatchmade").getAsBoolean();
69 | this.minParty = getMatchmaking().get("minParty").getAsInt();
70 | this.maxParty = getMatchmaking().get("maxParty").getAsInt();
71 | this.maxPlayers = getMatchmaking().get("maxPlayers").getAsInt();
72 | this.requiresGuardianOath = getMatchmaking().get("requiresGuardianOath").getAsBoolean();
73 | }
74 |
75 | public JsonObject getDisplayProperties() throws APIException {
76 | return getJO().getAsJsonObject("displayProperties");
77 | }
78 |
79 | public JsonObject getMatchmaking() throws APIException {
80 | return getJO().getAsJsonObject("matchmaking");
81 | }
82 |
83 | public String getName() {
84 | return name;
85 | }
86 |
87 | public String getHash() {
88 | return hash;
89 | }
90 |
91 | public String getCompletionUnlockHash() {
92 | return completionUnlockHash;
93 | }
94 |
95 | public String getDestinationHash() {
96 | return destinationHash;
97 | }
98 |
99 | public String getPlaceHash() {
100 | return placeHash;
101 | }
102 |
103 | public String getActivityTypeHash() {
104 | return activityTypeHash;
105 | }
106 |
107 | public String getActivityModeHash() {
108 | return activityModeHash;
109 | }
110 |
111 | public String getDescription() {
112 | return description;
113 | }
114 |
115 | public String getIcon() {
116 | return icon;
117 | }
118 |
119 | public String getPgcrImage() {
120 | return pgcrImage;
121 | }
122 |
123 | public String getReleaseIcon() {
124 | return releaseIcon;
125 | }
126 |
127 | public int getModeType() {
128 | return modeType;
129 | }
130 |
131 | public int getActivityModeCategory() {
132 | return activityModeCategory;
133 | }
134 |
135 | public int getReleaseTime() {
136 | return releaseTime;
137 | }
138 |
139 | public int getActivityLightLevel() {
140 | return activityLightLevel;
141 | }
142 |
143 | public int getTier() {
144 | return tier;
145 | }
146 |
147 | public int getMinParty() {
148 | return minParty;
149 | }
150 |
151 | public int getMaxParty() {
152 | return maxParty;
153 | }
154 |
155 | public int getMaxPlayers() {
156 | return maxPlayers;
157 | }
158 |
159 | public ActivityMode getActivityMode() {
160 | return activityMode;
161 | }
162 |
163 | public boolean isHasIcon() {
164 | return hasIcon;
165 | }
166 |
167 | public boolean isPlaylist() {
168 | return isPlaylist;
169 | }
170 |
171 | public boolean isInheritFromFreeRoam() {
172 | return inheritFromFreeRoam;
173 | }
174 |
175 | public boolean isSuppressOtherRewards() {
176 | return suppressOtherRewards;
177 | }
178 |
179 | public boolean isMatchmade() {
180 | return isMatchmade;
181 | }
182 |
183 | public boolean isRequiresGuardianOath() {
184 | return requiresGuardianOath;
185 | }
186 |
187 | public boolean isPvP() {
188 | return isPvP;
189 | }
190 | }
191 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/stats/activities/ActivityMode.java:
--------------------------------------------------------------------------------
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.stats.activities;
9 |
10 | /**
11 | * A list of activity mode numbers according to bungie.net
12 | * Some numbers are randomly skipped or "reserved", thus they are not listed here
13 | * https://bungie-net.github.io/multi/schema_Destiny-Definitions-DestinyActivityDefinition.html
14 | *
15 | * https://bungie-net.github.io/multi/schema_Destiny-HistoricalStats-Definitions-DestinyActivityModeType.html#schema_Destiny-HistoricalStats-Definitions-DestinyActivityModeType
16 | */
17 | public enum ActivityMode {
18 |
19 | NONE(0),
20 | STORY(2),
21 | STRIKE(3),
22 | RAID(4),
23 | ALL_PVP(5),
24 | PATROL(6),
25 | ALL_PVE(7),
26 | CONTROL(10),
27 | CLASH(12),
28 | CRIMSON_DOUBLES(15),
29 | NIGHTFALL(16),
30 | HEROIC_NIGHTFALL(17),
31 | ALL_STRIKES(18),
32 | IRON_BANNER(19),
33 | ALL_MAYHEM(25),
34 | SUPREMACY(31),
35 | PRIVATE_MATCHES_ALL(32),
36 | SURVIVAL(37),
37 | COUNTDOWN(38),
38 | TRIALS_OF_THE_NINE(39),
39 | SOCIAL(40),
40 | TRIALS_COUNTDOWN(41),
41 | TRIALS_SURVIVAL(42),
42 | IBCONTROL(43),
43 | IBCLASH(44),
44 | IBSUPREMACY(45),
45 | SCOREDNF(46),
46 | SCOREDHEROICNF(47),
47 | RUMBLE(48),
48 | ALL_DOUBLES(49),
49 | DOUBLES(50),
50 | PM_CLASH(51),
51 | PM_CONTROL(52),
52 | PM_SUPREMACY(53),
53 | PM_COUNTDOWN(54),
54 | PM_SURVIVAL(55),
55 | PM_MAYHEM(56),
56 | PM_RUMBLE(57),
57 | HEROIC_ADVENTURE(58),
58 | SHOWDOWN(59),
59 | LOCKDOWN(60),
60 | SCORCHED(61),
61 | SCORCHED_TEAM(62),
62 | GAMBIT(63),
63 | ALL_PVE_COMP(64),
64 | BREAKTHROUGH(65),
65 | BLACK_ARMORY_RUN(66),
66 | SALVAGE(67),
67 | IRON_BANNER_SALVAGE(68),
68 | PVP_COMP(69),
69 | PVP_QUICKPLAY(70),
70 | CLASH_QUICKPLAY(71),
71 | CLASH_COMP(72),
72 | CONTROL_QUICKPLAY(73),
73 | CONTROL_COMP(74),
74 | GAMBIT_PRIME(75),
75 | RECKONING(76),
76 | MENAGERIE(77),
77 | VEX_OFFENSIVE(78),
78 | NIGHTMARE_HUNT(79),
79 | ELIMINATION(80),
80 | MOMENTUM(81),
81 | DUNGEON(82),
82 | SUNDIAL(83),
83 | TRIALS_OF_OSIRIS(84),
84 | DARES(85),
85 | OFFENSIVE(86);
86 |
87 |
88 | private int bungieValue;
89 |
90 | private ActivityMode(int bungieValue) {
91 | this.bungieValue = bungieValue;
92 | }
93 |
94 | public int getBungieValue() {
95 | return bungieValue;
96 | }
97 |
98 | public static ActivityMode fromBungieValue(int bungieValue) {
99 | for(ActivityMode activityMode : ActivityMode.values()) {
100 | if(activityMode.getBungieValue() == bungieValue) {
101 | return activityMode;
102 | }
103 | }
104 |
105 | return null;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/stats/activities/ActivityParticipant.java:
--------------------------------------------------------------------------------
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.stats.activities;
9 |
10 | import com.google.gson.JsonObject;
11 | import net.dec4234.javadestinyapi.material.user.BungieUser;
12 |
13 | public class ActivityParticipant {
14 |
15 | private JsonObject jo;
16 | private BungieUser bungieUser;
17 | private String membershipId, characterId;
18 | private int score, assists, deaths, kills, opponentsDefeated, activityDuration, completionReason;
19 | private double kdr, kda, completed, efficiency, startSeconds, timePlayed;
20 | JsonObject values;
21 |
22 | public ActivityParticipant(JsonObject entry) {
23 | jo = entry;
24 | }
25 |
26 | private void assignValues() {
27 |
28 | }
29 |
30 | /**
31 | * Get the membership ID of this user
32 | */
33 | public String getMembershipId() {
34 | if (membershipId != null) return membershipId;
35 | membershipId = jo.getAsJsonObject("player").getAsJsonObject("destinyUserInfo").get("membershipId").getAsString();
36 | return membershipId;
37 | }
38 |
39 | /**
40 | * Gets the bungie user of this
41 | = */
42 | public BungieUser getBungieUser() {
43 | if (bungieUser != null) return bungieUser;
44 | bungieUser = new BungieUser(getMembershipId());
45 | return bungieUser;
46 | }
47 |
48 | /**
49 | * Get the character ID of the player
50 | = */
51 | public String getCharacterId() {
52 | if (characterId != null) return characterId;
53 | characterId = jo.get("characterId").getAsString();
54 | return characterId;
55 | }
56 |
57 | /**
58 | * Gets the score of the user in this activity
59 | */
60 | public int getScore() {
61 | if (score != 0) return score;
62 | score = jo.getAsJsonObject("score").getAsJsonObject("basic").get("value").getAsInt();
63 | return score;
64 | }
65 |
66 | /**
67 | * Get the assists of the user
68 | - */
69 | public int getAssists() {
70 | if (assists != 0) return assists;
71 | assists = jo.getAsJsonObject("assists").getAsJsonObject("basic").get("value").getAsInt();
72 | return assists;
73 | }
74 |
75 | /**
76 | * Get if this user completed the activity
77 | - */
78 | public double getCompleted() {
79 | if(completed != 0) return completed;
80 | completed = jo.getAsJsonObject("completed").getAsJsonObject("basic").get("value").getAsDouble();
81 | return completed;
82 | }
83 |
84 | /**
85 | * Get the number of deaths for this user
86 | */
87 | public int getDeaths() {
88 | if(deaths != 0) return deaths;
89 | deaths = jo.getAsJsonObject("deaths").getAsJsonObject("basic").get("value").getAsInt();
90 | return deaths;
91 | }
92 |
93 | /**
94 | * Get the number of kills for this user
95 | */
96 | public int getKills() {
97 | if(kills != 0) return kills;
98 | kills = jo.getAsJsonObject("kills").getAsJsonObject("basic").get("value").getAsInt();
99 | return kills;
100 | }
101 |
102 | /**
103 | * Gets the number of opponents this user defeated
104 | - */
105 | public int getOpponentsDefeated() {
106 | if(opponentsDefeated != 0) return opponentsDefeated;
107 | opponentsDefeated = jo.getAsJsonObject("opponentsDefeated").getAsJsonObject("basic").get("value").getAsInt();
108 | return opponentsDefeated;
109 | }
110 |
111 | /**
112 | * Get the efficiency of this user
113 | */
114 | public double getEfficiency() {
115 | if(efficiency != 0) return efficiency;
116 | efficiency = jo.getAsJsonObject("efficiency").getAsJsonObject("basic").get("value").getAsDouble();
117 | return efficiency;
118 | }
119 |
120 | /**
121 | * Gets the Kill-Death Ratio of this user
122 | */
123 | public double getKdr() {
124 | if(kdr != 0) return kdr;
125 | kdr = jo.getAsJsonObject("killsDeathsRatio").getAsJsonObject("basic").get("value").getAsDouble();
126 | return kdr;
127 | }
128 |
129 | /**
130 | * Gets the Kill-Deaths-Assists of this user
131 | */
132 | public double getKda() {
133 | if(kda != 0) return kda;
134 | kda = jo.getAsJsonObject("killsDeathsAssists").getAsJsonObject("basic").get("value").getAsDouble();
135 | return kda;
136 | }
137 |
138 | /**
139 | * Gets the duration, in seconds, of this activity
140 | */
141 | public int getActivityDuration() {
142 | if(activityDuration != 0) return activityDuration;
143 | activityDuration = jo.getAsJsonObject("activityDurationSeconds").getAsJsonObject("basic").get("value").getAsInt();
144 | return activityDuration;
145 | }
146 |
147 | /**
148 | * Gets the reason why this activity was completed
149 | */
150 | public int getCompletionReason() {
151 | if(completionReason != 0) return completionReason;
152 | completionReason = jo.getAsJsonObject("completionReason").getAsJsonObject("basic").get("value").getAsInt();
153 | return completionReason;
154 | }
155 |
156 | /**
157 | * Gets the time the user joined the activity?
158 | */
159 | public double getStartSeconds() {
160 | if(startSeconds != 0) return startSeconds;
161 | startSeconds = jo.getAsJsonObject("startSeconds").getAsJsonObject("basic").get("value").getAsDouble();
162 | return startSeconds;
163 | }
164 |
165 | /**
166 | * Gets how long this user played in this activity
167 | */
168 | public double getTimePlayed() {
169 | if(timePlayed != 0) return timePlayed;
170 | timePlayed = jo.getAsJsonObject("timePlayedSeconds").getAsJsonObject("basic").get("value").getAsDouble();
171 | return timePlayed;
172 | }
173 |
174 | public JsonObject getJO() {
175 | return jo;
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/stats/character/UserStats.java:
--------------------------------------------------------------------------------
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.stats.character;
9 |
10 | import com.google.gson.JsonObject;
11 | import net.dec4234.javadestinyapi.exceptions.APIException;
12 | import net.dec4234.javadestinyapi.material.DestinyAPI;
13 | import net.dec4234.javadestinyapi.material.user.BungieUser;
14 | import net.dec4234.javadestinyapi.material.user.DestinyCharacter;
15 | import net.dec4234.javadestinyapi.utils.HttpUtils;
16 |
17 | import java.text.DecimalFormat;
18 |
19 | public class UserStats {
20 |
21 | HttpUtils hu = DestinyAPI.getHttpUtils();
22 | BungieUser bungieUser;
23 | DestinyCharacter destinyCharacter;
24 | JsonObject jo;
25 | JsonObject allPve;
26 | DecimalFormat df = new DecimalFormat("##.00");
27 |
28 | private int activitiesCleared, activitiesEntered, assists, totalKillDistance, kills, deaths;
29 | private double pgaAssists, pgaKills, averageKillDistance, pgaSecondsPlayed, pgaDeaths;
30 | private long secondsPlayed;
31 |
32 | /**
33 | * Gets stats for this user's entire account
34 | */
35 | public UserStats(BungieUser bungieUser) throws APIException {
36 | this.bungieUser = bungieUser;
37 | jo = hu.urlRequestGET("https://www.bungie.net/Platform/Destiny2/" + bungieUser.getMembershipType() + "/Account/" + bungieUser.getID() + "/Stats/").getAsJsonObject("Response").getAsJsonObject("mergedAllCharacters").getAsJsonObject("results");
38 | allPve = jo.getAsJsonObject("allPvE").getAsJsonObject("allTime");
39 | }
40 |
41 | /**
42 | * Gets stats for this user's specific character
43 | */
44 | public UserStats(BungieUser bungieUser, DestinyCharacter destinyCharacter) throws APIException {
45 | this.bungieUser = bungieUser;
46 | jo = hu.urlRequestGET("https://www.bungie.net/Platform/Destiny2/" + bungieUser.getMembershipType() + "/Account/" + bungieUser.getID() + "/Character/" + destinyCharacter.getCharacterID() + "/Stats/").getAsJsonObject("Response");
47 | allPve = jo.getAsJsonObject("allPvE").getAsJsonObject("allTime");
48 | }
49 |
50 | public int getActivitiesCleared() {
51 | if(activitiesCleared != 0) return activitiesCleared;
52 | activitiesCleared = getBasicPVE("activitiesCleared").get("value").getAsInt();
53 | return activitiesCleared;
54 | }
55 |
56 | public int getActivitiesEntered() {
57 | if(activitiesEntered != 0) return activitiesEntered;
58 | activitiesEntered = getBasicPVE("activitiesEntered").get("value").getAsInt();
59 | return activitiesEntered;
60 | }
61 |
62 | public int getAssists() {
63 | if(assists != 0) return assists;
64 | assists = getBasicPVE("assists").get("value").getAsInt();
65 | return assists;
66 | }
67 |
68 | public double getPgaAssists() {
69 | if(pgaAssists != 0) return pgaAssists;
70 | pgaAssists = getPGAPVE("assists").get("displayValue").getAsDouble();
71 | return pgaAssists;
72 | }
73 |
74 | public int getTotalKillDistance() {
75 | if(totalKillDistance != 0) return totalKillDistance;
76 | totalKillDistance = getBasicPVE("totalKillDistance").get("value").getAsInt();
77 | return totalKillDistance;
78 | }
79 |
80 | public int getKills() {
81 | if(kills != 0) return kills;
82 | kills = getBasicPVE("kills").get("value").getAsInt();
83 | return kills;
84 | }
85 |
86 | public double getPgaKills() {
87 | if(pgaKills != 0) return pgaKills;
88 | pgaKills = getPGAPVE("kills").get("displayValue").getAsDouble();
89 | return pgaKills;
90 | }
91 |
92 | public double getAverageKillDistance() {
93 | if(averageKillDistance != 0) return averageKillDistance;
94 | averageKillDistance = getBasicPVE("averageKillDistance").get("displayValue").getAsDouble();
95 | return averageKillDistance;
96 | }
97 |
98 | public long getSecondsPlayed() {
99 | if(secondsPlayed != 0) return secondsPlayed;
100 | secondsPlayed = getBasicPVE("secondsPlayed").get("value").getAsLong();
101 | return secondsPlayed;
102 | }
103 |
104 | public double getPgaSecondsPlayed() {
105 | if(pgaSecondsPlayed != 0) return pgaSecondsPlayed;
106 | pgaSecondsPlayed = Double.parseDouble(df.format(getPGAPVE("secondsPlayed").get("value").getAsDouble()));
107 | return pgaSecondsPlayed;
108 | }
109 |
110 | public int getDeaths() {
111 | if(deaths != 0) return deaths;
112 | deaths = getBasicPVE("deaths").get("value").getAsInt();
113 | return deaths;
114 | }
115 |
116 | public double getPgaDeaths() {
117 | if(pgaDeaths != 0) return pgaDeaths;
118 | pgaDeaths = Double.parseDouble(df.format(getPGAPVE("deaths").get("value").getAsDouble()));
119 | return pgaDeaths;
120 | }
121 |
122 |
123 |
124 |
125 | // util methods
126 | public JsonObject getJsonObject() {
127 | return jo;
128 | }
129 |
130 | private JsonObject getBasicPVE(String name) {
131 | return allPve.getAsJsonObject(name).getAsJsonObject("basic");
132 | }
133 |
134 | private JsonObject getPGAPVE(String name) {
135 | return allPve.getAsJsonObject(name).getAsJsonObject("pga");
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/utils/HttpRequestModifier.java:
--------------------------------------------------------------------------------
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.utils;
9 |
10 | import java.net.http.HttpRequest;
11 |
12 | @FunctionalInterface
13 | public interface HttpRequestModifier {
14 |
15 | HttpRequest.Builder modifyRequest(HttpRequest.Builder starter);
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/utils/HttpUtils.java:
--------------------------------------------------------------------------------
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.utils;
9 |
10 | import com.google.gson.JsonObject;
11 | import com.google.gson.JsonParser;
12 | import com.google.gson.JsonSyntaxException;
13 | import net.dec4234.javadestinyapi.exceptions.APIException;
14 | import net.dec4234.javadestinyapi.exceptions.APIOfflineException;
15 | import net.dec4234.javadestinyapi.exceptions.AccessTokenExpiredException;
16 | import net.dec4234.javadestinyapi.exceptions.ConnectionException;
17 | import net.dec4234.javadestinyapi.exceptions.JsonParsingError;
18 | import net.dec4234.javadestinyapi.exceptions.OAuthUnauthorizedException;
19 | import net.dec4234.javadestinyapi.exceptions.RefreshTokenExpiredException;
20 | import net.dec4234.javadestinyapi.material.DestinyAPI;
21 | import net.dec4234.javadestinyapi.material.manifest.ManifestEntityTypes;
22 |
23 | import java.net.URI;
24 | import java.net.http.HttpClient;
25 | import java.net.http.HttpRequest;
26 | import java.net.http.HttpResponse;
27 | import java.time.Duration;
28 | import java.util.Base64;
29 | import java.util.concurrent.ExecutionException;
30 |
31 | /**
32 | * A net.dec4234.javadestinyapi.utils class used by JavaDestinyAPI to collect information from bungie.net
33 | * It is important that the DestinyAPI class is initialized prior to this class
34 | */
35 | public class HttpUtils {
36 |
37 | public static final String URL_BASE = "https://www.bungie.net/Platform";
38 |
39 | private String apiKey;
40 | private static String bearerToken;
41 |
42 | public HttpUtils(String apiKey) {
43 | this.apiKey = apiKey;
44 | }
45 |
46 | public void setApiKey(String apiKey) {
47 | this.apiKey = apiKey;
48 | }
49 |
50 | /**
51 | * Send a GET url request to the url provided, returns a JsonObject of the response
52 | */
53 | public JsonObject urlRequestGET(String url) throws APIException {
54 | return getJsonObject(getRequest(true, url, starter -> {
55 | starter.GET();
56 | return starter;
57 | }));
58 | }
59 |
60 | public JsonObject urlRequestGETOauth(String url) throws APIException {
61 | if(bearerToken == null) {
62 | bearerToken = DestinyAPI.getAccessToken();
63 | }
64 | if(bearerToken == null || DestinyAPI.getLastRefreshTime() == 0 || DestinyAPI.getLastRefreshTime() + (45 * 60 * 1000) < System.currentTimeMillis()) {
65 | setTokenViaRefresh();
66 | }
67 |
68 | try {
69 | return getJsonObject(getRequest(true, url, starter -> {
70 | starter.GET()
71 | .setHeader("Authorization", "Bearer " + HttpUtils.bearerToken);
72 | return starter;
73 | }));
74 | } catch (AccessTokenExpiredException e) {
75 | setTokenViaRefresh();
76 |
77 | return getJsonObject(getRequest(true, url, starter -> {
78 | starter.GET()
79 | .setHeader("Authorization", "Bearer " + HttpUtils.bearerToken);
80 | return starter;
81 | }));
82 | }
83 | }
84 |
85 | public JsonObject urlRequestPOST(String url, JsonObject body) throws APIException {
86 | return urlRequestPOST(url, body.toString());
87 | }
88 |
89 | public JsonObject urlRequestPOST(String url, String body) throws APIException {
90 | if (body.isEmpty()) { body = "{\"message\": \"\",}"; }
91 | String finalBody = body;
92 |
93 | if(DestinyAPI.isDebugEnabled()) {
94 | System.out.println("Body: " + finalBody);
95 | }
96 |
97 | return getJsonObject(getRequest(true, url, starter -> {
98 | starter.setHeader("Content-Type", "application/json")
99 | .POST(HttpRequest.BodyPublishers.ofString(finalBody));
100 |
101 | return starter;
102 | }));
103 | }
104 |
105 | public JsonObject urlRequestPOSTOauth(String url, JsonObject body) throws APIException {
106 | if(bearerToken == null) {
107 | bearerToken = DestinyAPI.getAccessToken();
108 | }
109 | if(bearerToken == null || DestinyAPI.getLastRefreshTime() == 0 || DestinyAPI.getLastRefreshTime() + (45 * 60 * 1000) < System.currentTimeMillis()) {
110 | setTokenViaRefresh();
111 | }
112 |
113 | if (body.toString().isEmpty()) {
114 | body = new JsonObject();
115 | body.addProperty("message", "");
116 | }
117 |
118 | final String finalBody = body.toString();
119 |
120 | if(DestinyAPI.isDebugEnabled()) {
121 | System.out.println("Body: " + finalBody);
122 | }
123 |
124 | try {
125 | return getJsonObject(getRequest(true, url, starter -> {
126 | starter.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken)
127 | .setHeader("Content-Type", "application/json")
128 | .POST(HttpRequest.BodyPublishers.ofString(finalBody));
129 |
130 | return starter;
131 | }));
132 | } catch (AccessTokenExpiredException e) {
133 | setTokenViaRefresh();
134 | return getJsonObject(getRequest(true, url, starter -> {
135 | starter.setHeader("Authorization", "Bearer " + HttpUtils.bearerToken)
136 | .setHeader("Content-Type", "application/json")
137 | .POST(HttpRequest.BodyPublishers.ofString(finalBody));
138 |
139 | return starter;
140 | }));
141 | }
142 | }
143 |
144 | public JsonObject urlRequestPOSTOauth(String url) throws APIException {
145 | return urlRequestPOSTOauth(url, new JsonObject());
146 | }
147 |
148 | /**
149 | * Make a request to the Bungie manifest to reveal information about a hash-identified item.
150 | *
151 | * Deprecated in favor of {@link net.dec4234.javadestinyapi.material.manifest.DestinyManifest#manifestGET(ManifestEntityTypes, String)}
152 | */
153 | @Deprecated
154 | public JsonObject manifestGET(ManifestEntityTypes entityType, String hashIdentifier) throws APIException {
155 | return urlRequestGET("https://www.bungie.net/Platform/Destiny2/Manifest/" + entityType.getBungieEntityValue() + "/" + hashIdentifier + "/");
156 | }
157 |
158 | /**
159 | * Gets an access token using the refresh token in storage and replaces the old refresh token with the new one
160 | *
161 | * @return Returns the new access token
162 | */
163 | public String setTokenViaRefresh() throws APIException {
164 | if(DestinyAPI.getRefreshToken() == null) {
165 | throw new OAuthUnauthorizedException("Refresh token is null. You must set new access and refresh tokens OR your OAuthManager is misconfigured and they can't be accessed.");
166 | }
167 |
168 | String url = "https://www.bungie.net/Platform/App/OAuth/Token/";
169 |
170 | String requestBody = "grant_type=refresh_token&refresh_token=" + DestinyAPI.getRefreshToken();
171 |
172 | JsonObject response = getJsonObject(getRequest(false, url, starter -> {
173 | starter.setHeader("Content-Type", "application/x-www-form-urlencoded")
174 | .setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString((DestinyAPI.getClientId() + ":" + DestinyAPI.getClientSecret()).getBytes()))
175 | .POST(HttpRequest.BodyPublishers.ofString(requestBody));
176 |
177 | return starter;
178 | }));
179 |
180 | if(response.has("error_description") && response.get("error_description").getAsString().equals("ApplicationTokenKeyIdDoesNotExist")) {
181 | throw new RefreshTokenExpiredException();
182 | }
183 |
184 | if(!response.has("access_token")) {
185 | return null;
186 | }
187 |
188 | String at = response.get("access_token").getAsString();
189 | String rt = response.get("refresh_token").getAsString();
190 | bearerToken = at;
191 | DestinyAPI.setAccessToken(at);
192 | DestinyAPI.setRefreshToken(rt);
193 | DestinyAPI.setLastRefreshTime();
194 |
195 | if(DestinyAPI.isDebugEnabled()) {
196 | System.out.println("TOKENS REFRESHED");
197 | }
198 |
199 | return at;
200 | }
201 |
202 | /**
203 | * Requires an OAuthCode to be manually set inside the DestinyAPI.setOAuthCode()
204 | */
205 | public void setTokenViaAuth() throws APIException {
206 | setTokenViaAuth(DestinyAPI.getOauthCode());
207 | }
208 |
209 | public void setTokenViaAuth(String oAuthCode) throws APIException {
210 | if(oAuthCode == null) {
211 | throw new OAuthUnauthorizedException("OAuth code is null. You must generate an OAuth code using the OAuth process.");
212 | }
213 |
214 | String url = "https://www.bungie.net/Platform/App/OAuth/Token/";
215 |
216 | String requestBody = "grant_type=authorization_code&code=" + oAuthCode;
217 |
218 | JsonObject jsonObject = getJsonObject(getRequest(false, url, starter -> {
219 | starter.setHeader("Authorization", "Basic " + Base64.getEncoder().encodeToString((DestinyAPI.getClientId() + ":" + DestinyAPI.getClientSecret()).getBytes()))
220 | .setHeader("Content-Type", "application/x-www-form-urlencoded")
221 | .POST(HttpRequest.BodyPublishers.ofString(requestBody));
222 |
223 | return starter;
224 | }));
225 |
226 | String accessToken = jsonObject.get("access_token").getAsString();
227 | String refreshToken = jsonObject.get("refresh_token").getAsString();
228 |
229 | DestinyAPI.setRefreshToken(refreshToken);
230 | DestinyAPI.setAccessToken(accessToken);
231 | DestinyAPI.setLastRefreshTime();
232 |
233 | HttpUtils.bearerToken = accessToken;
234 | }
235 |
236 | public boolean hasValidOAuthTokens() throws APIException {
237 | boolean value = DestinyAPI.hasOauthManager() && DestinyAPI.getAccessToken() != null;
238 |
239 | if(value) {
240 | try {
241 | if(DestinyAPI.getHttpUtils().setTokenViaRefresh() == null) {
242 | value = false;
243 | }
244 | } catch (AccessTokenExpiredException | RefreshTokenExpiredException | OAuthUnauthorizedException e) {
245 | value = false;
246 | }
247 | }
248 |
249 | return value;
250 | }
251 |
252 | private JsonObject getJsonObject(HttpRequest httpRequest) throws APIException {
253 | HttpClient httpClient = HttpClient.newHttpClient();
254 | String responseString;
255 |
256 | try { // TODO: are we even taking advantage of async? this seems pointless to just block right away
257 | responseString = httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()).thenApplyAsync(HttpResponse::body).get();
258 |
259 | if (DestinyAPI.isDebugEnabled()) {
260 | String type = httpRequest.method() + " " + httpRequest.uri().toString();
261 | if(httpRequest.headers().firstValue("Authorization").isPresent()) {
262 | type = httpRequest.method() + " OAUTH " + httpRequest.uri().toString();
263 | }
264 | System.out.println(type);
265 | if(DestinyAPI.doPrintHeaders()) {
266 | httpRequest.headers().map().forEach((s, strings) -> {
267 | if(!s.equals("Content-Type")) {
268 | System.out.println(s + " " + strings.toString());
269 | }
270 | });
271 | }
272 | System.out.println(responseString);
273 | }
274 | } catch (InterruptedException | ExecutionException e) {
275 | throw new ConnectionException(e);
276 | }
277 |
278 | JsonObject jsonObject;
279 |
280 | try {
281 | jsonObject = JsonParser.parseString(responseString).getAsJsonObject();
282 | } catch (JsonSyntaxException e) {
283 | throw new JsonParsingError(e);
284 | }
285 |
286 | // Check for API errors - https://bungie-net.github.io/multi/schema_Exceptions-PlatformErrorCodes.html#schema_Exceptions-PlatformErrorCodes
287 | if(jsonObject.has("ErrorCode")) {
288 | switch (jsonObject.get("ErrorCode").getAsInt()) { //TODO: lots of errors we could catch here
289 | case 5: // APIOffline
290 | throw new APIOfflineException(jsonObject.get("Message").getAsString());
291 | case 99: // WebAuthRequired
292 | throw new OAuthUnauthorizedException("OAuth - access denied. Try authenticating.");
293 | case 2111 | 2115: // AccessTokenHasExpired, OAuthAccessTokenExpired
294 | throw new AccessTokenExpiredException();
295 | case 2118: // RefreshTokenExpired -- need to reauth using oauth
296 | throw new RefreshTokenExpiredException();
297 | }
298 | }
299 |
300 | return jsonObject;
301 | }
302 |
303 | private String getStringResponse(HttpRequest httpRequest) throws ConnectionException {
304 | HttpClient httpClient = HttpClient.newHttpClient();
305 | try {
306 | String responseString = httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()).thenApplyAsync(HttpResponse::body).get();
307 |
308 | if (DestinyAPI.isDebugEnabled()) {
309 | System.out.println(httpRequest.method() + " " + httpRequest.uri().toString());
310 | System.out.println("Response: " + responseString);
311 | }
312 | return responseString;
313 | } catch (InterruptedException | ExecutionException e) {
314 | throw new ConnectionException(e);
315 | }
316 | }
317 |
318 | private HttpRequest getRequest(boolean standardRequest, String url, HttpRequestModifier httpRequestModifier) {
319 | HttpRequest.Builder builder = httpRequestModifier.modifyRequest(HttpRequest.newBuilder());
320 |
321 | builder.uri(URI.create(url)).timeout(Duration.ofMinutes(3));
322 |
323 | if(standardRequest) {
324 | builder.setHeader("X-API-KEY", apiKey);
325 | }
326 |
327 | return builder.build();
328 | }
329 | }
330 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/utils/StringUtils.java:
--------------------------------------------------------------------------------
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.utils;
9 |
10 | import java.io.IOException;
11 | import java.net.URLEncoder;
12 | import java.nio.charset.StandardCharsets;
13 | import java.text.DateFormat;
14 | import java.text.DecimalFormat;
15 | import java.text.ParseException;
16 | import java.text.SimpleDateFormat;
17 | import java.util.Date;
18 | import java.util.TimeZone;
19 |
20 | public class StringUtils {
21 |
22 | /**
23 | * @param zuluTimeInString The Zulu time, in the form of a String, you wish to convert
24 | * @return Returns a date object representing the Zulu time provided
25 | */
26 | public static Date valueOfZTime(String zuluTimeInString) {
27 | String temp = zuluTimeInString;
28 | if(temp.length() == 24) { // Sometimes the date will be in an extra precise format, which is truncated here
29 | temp = temp.substring(0, temp.length() - 5);
30 | temp = temp + "Z";
31 | }
32 | DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:sss'Z'");
33 | df.setTimeZone(TimeZone.getTimeZone("Zulu"));
34 | try {
35 | return df.parse(temp);
36 | } catch (ParseException e) { // TODO: log error here
37 | return null;
38 | }
39 | }
40 |
41 | /**
42 | * Get the number of days since the time date provided
43 | */
44 | public static double getDaysSinceTime(Date date) {
45 | DecimalFormat df = new DecimalFormat("0.##");
46 | return Double.parseDouble(df.format((new Date().getTime() - date.getTime()) / 1000.0 / 60.0 / 60.0 / 24.0));
47 | }
48 |
49 | /**
50 | * Encode a string to be suitable for use in a url
51 | *
52 | * Specific characters need to be encoded in order to have a successful request
53 | */
54 | public static String httpEncode(String input) {
55 | return URLEncoder.encode(input, StandardCharsets.UTF_8);
56 | }
57 |
58 | public static void executeCommandLine(String command) {
59 | try {
60 | Process process = Runtime.getRuntime().exec(command);
61 | } catch (IOException e) {
62 | e.printStackTrace();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/utils/fast/ASyncPull.java:
--------------------------------------------------------------------------------
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.utils.fast;
9 |
10 | import java.util.List;
11 |
12 | public interface ASyncPull {
13 |
14 | List> run(List> source);
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/net/dec4234/javadestinyapi/utils/fast/ASyncPuller.java:
--------------------------------------------------------------------------------
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.utils.fast;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | public class ASyncPuller {
14 |
15 | /**
16 | * Split the stream list into multiple parts, handle using the asyncpull consumer method
17 | * Useful for accelerating processing of large or taxing operations
18 | *
19 | * @param stream The source of info to be split
20 | * @param partsToSplitInto The amount of threads to be created, the stream list will be split as much as possible based on this
21 | * @param aSyncPull What you want to happen to the info when in its in an ASync thread
22 | */
23 | public static List