Skip to content

Commit

Permalink
Merge pull request #2 from Dance-Dog/dev
Browse files Browse the repository at this point in the history
v0.1.1 - Bug Fixes
  • Loading branch information
Dance-Dog authored Apr 2, 2020
2 parents c8f9987 + 581c411 commit f562c1b
Show file tree
Hide file tree
Showing 22 changed files with 646 additions and 377 deletions.
39 changes: 34 additions & 5 deletions Sample Reward Json/Notes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,43 @@

========== Field Meanings ==========
- rewards.*.rarity: Rarity of the reward (CardType) [all]
- "COMMON", "RARE", "EPIC", or "LEGENDARY" (in order of worst to best)
- This field is always present
- rewards.*.reward: Type of reward (RewardType) [all]
- Determines the main icon on the card (with the exception of housing_package; see below)
- This field is always present

- rewards.*.gameType: Game that the reward is for (GameType) [coins and tokens only]
- rewards.*.amount: Amount of type rewarded [all except housing_package and add_vanity]
- rewards.*.intlist: Unknown; has something to do with amount [unknown; likely same as amount]
- rewards.*.gameType: Game that the reward is for
- Only present for coins and tokens (not adsense_token)
- Determines the mini GameType icon

- rewards.*.amount: Amount of type rewarded
- Present on every reward except housing_package, add_vanity, and mystery_box (and potentially gift_box too; see intlist)
- rewards.*.intlist: An array of integers
- Only present for mystery_box (and potentially gift_box, but still unknown)
- The length of this array is displayed as the amount
- Each int in the array represents the 5-star value of a mystery box
- 0 = 1-star
- 4 = 5-star

- rewards.*.package: Specifies the specific reward within the reward type [housing_package only]
- Housing block IDs are always prefixed by "specialoccasion_reward_card_skull_"
- The 18 housing blocks are grouped into 3 groups of 6
- Each group is represented by a colored treasure chest (red/green/blue) for the type icon
- The group has nothing to do with rarity
- rewards.*.key: ID of the lobby cosmetic to reward [add_vanity only]
- For a suit piece, this looks like "suit_treasure_chestplate"
- For other cosmetics, this looks like "emote_moustache"

========== Reward Types ==========
- add_vanity [no sample yet, but see FORGED for assumed JSON]
- housing_package [see 6d302bf1]
- mystery_box [uses intlist; see a1cfa0d3]
- gift_box [no sample yet; unknown if uses intlist or amount]

========== I18n Notes ==========
- rewards.*.key for a suit piece looks like "suit_treasure_chestplate"
- coins [see 04f79703]
- tokens [see 11a346e4]
- dust [see 04f79703]
- experience [see 8c3d7a5f]
- adsense_token [see a25797d1]
- souls [see a25797d1]
38 changes: 38 additions & 0 deletions Sample Reward Json/a18258f7.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"rewards": [
{
"gameType": "SKYWARS",
"amount": 4000,
"rarity": "COMMON",
"reward": "coins"
},
{
"package": "specialoccasion_reward_card_skull_pocket_galaxy",
"rarity": "EPIC",
"reward": "housing_package"
},
{
"gameType": "BATTLEGROUND",
"amount": 6000,
"rarity": "RARE",
"reward": "coins"
}
],
"id": "a18258f7",
"skippable": true,
"dailyStreak": {
"value": 1,
"score": 10,
"highScore": 37,
"keeps": true,
"token": true
},
"ad": {
"video": "GNhbZ5guqO0",
"duration": 30,
"link": "https://store.hypixel.net/?utm_source=rewards-video&utm_medium=website&utm_content=GNhbZ5guqO0&utm_campaign=Rewards",
"buttonText": "Visit the Store"
},
"activeAd": 2,
"playwire": false
}
45 changes: 45 additions & 0 deletions Sample Reward Json/a1cfa0d3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"rewards": [
{
"intlist": [
4,
4,
4,
4,
4,
4
],
"rarity": "LEGENDARY",
"reward": "mystery_box"
},
{
"gameType": "TNTGAMES",
"amount": 4000,
"rarity": "COMMON",
"reward": "coins"
},
{
"gameType": "SKYWARS",
"amount": 8000,
"rarity": "RARE",
"reward": "coins"
}
],
"id": "a1cfa0d3",
"skippable": false,
"dailyStreak": {
"value": 0,
"score": 9,
"highScore": 9,
"keeps": true,
"token": false
},
"ad": {
"video": "GNhbZ5guqO0",
"duration": 30,
"link": "https://store.hypixel.net/?utm_source=rewards-video&utm_medium=website&utm_content=GNhbZ5guqO0&utm_campaign=Rewards",
"buttonText": "Visit the Store"
},
"activeAd": 2,
"playwire": false
}
36 changes: 36 additions & 0 deletions Sample Reward Json/a25797d1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"rewards": [
{
"amount": 100,
"rarity": "RARE",
"reward": "souls"
},
{
"amount": 1,
"rarity": "RARE",
"reward": "adsense_token"
},
{
"amount": 10,
"rarity": "COMMON",
"reward": "dust"
}
],
"id": "a25797d1",
"skippable": true,
"dailyStreak": {
"value": 0,
"score": 9,
"highScore": 37,
"keeps": true,
"token": false
},
"ad": {
"video": "GNhbZ5guqO0",
"duration": 30,
"link": "https://store.hypixel.net/?utm_source=rewards-video&utm_medium=website&utm_content=GNhbZ5guqO0&utm_campaign=Rewards",
"buttonText": "Visit the Store"
},
"activeAd": 2,
"playwire": false
}
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ compileJava {
}

archivesBaseName = 'RewardClaim'
version = '0.1'
version = '0.1.1'
group = 'me.dancedog.rewardclaim'
minecraft {
version = '1.8.9-11.15.1.1722'
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/me/dancedog/rewardclaim/Mod.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
public class Mod {

public static final String MODID = "rewardclaim";
public static final String VERSION = "0.1";
public static final String VERSION = "0.1.1";
static final String MODNAME = "RewardClaim";

@Getter
Expand Down Expand Up @@ -51,7 +51,7 @@ public static void printWarning(String message, Throwable t, boolean inChat) {

if (inChat && Minecraft.getMinecraft().thePlayer != null) {
ChatComponentText chatMessage = new ChatComponentText("[" + MODNAME + "] " + message);
chatMessage.getChatStyle().setItalic(true).setColor(EnumChatFormatting.RED);
chatMessage.getChatStyle().setBold(true).setColor(EnumChatFormatting.RED);
Minecraft.getMinecraft().thePlayer.addChatMessage(chatMessage);
}
}
Expand Down
30 changes: 16 additions & 14 deletions src/main/java/me/dancedog/rewardclaim/RewardListener.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package me.dancedog.rewardclaim;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
Expand Down Expand Up @@ -31,7 +29,7 @@ public class RewardListener {
"§r§6Click the link to visit our website and claim your reward: §r§bhttp://rewards\\.hypixel\\.net/claim-reward/([A-Za-z0-9]+)§r");

private long lastRewardOpenedMs = new Date().getTime();
private final AtomicReference<JsonObject> rawRewardSessionData = new AtomicReference<>();
private final AtomicReference<RewardSession> sessionData = new AtomicReference<>();

/**
* Fetches & scrapes the reward page in a separate thread. The resulting json is then stored in
Expand All @@ -45,11 +43,12 @@ private void fetchRewardSession(String sessionId) {
try {
URL url = new URL("https://rewards.hypixel.net/claim-reward/" + sessionId);
Response response = new Request(url, Method.GET, null).execute();

if (response.getStatusCode() >= 200 && response.getStatusCode() < 300) {
Document document = Jsoup.parse(response.getBody());
JsonObject rawRewardData = RewardScraper.parseRewardPage(document);
rawRewardData.addProperty("_cookie", response.getNewCookies());
rawRewardSessionData.set(rawRewardData);
RewardSession session = RewardScraper
.parseSessionFromRewardPage(document, response.getNewCookies());
sessionData.set(session);
} else {
Mod.printWarning("Server sent back a " + response.getStatusCode()
+ " status code. Received the following body:\n" + response.getBody(), null, false);
Expand All @@ -67,16 +66,18 @@ private void fetchRewardSession(String sessionId) {
*/
@SubscribeEvent
public void onClientTick(ClientTickEvent event) {
if (rawRewardSessionData.get() != null) {
JsonElement error = rawRewardSessionData.get().get("error");
if (error != null) {
Mod.printWarning("Failed to get reward: " + error.getAsString(), null, true);
if (Minecraft.getMinecraft().theWorld == null) {
return;
}

RewardSession currentSessionData = sessionData.getAndSet(null);
if (currentSessionData != null) {
if (currentSessionData.getError() != null) {
Mod.printWarning("Failed to get reward: " + currentSessionData.getError(), null, true);
return;
}
RewardSession session = SessionDataParser.parseRewardSessionData(rawRewardSessionData.get());
rawRewardSessionData.set(null);
Minecraft.getMinecraft()
.displayGuiScreen(new GuiScreenRewardSession(session));
.displayGuiScreen(new GuiScreenRewardSession(currentSessionData));
}
}

Expand All @@ -103,7 +104,8 @@ public void onChatReceived(ClientChatReceivedEvent event) {
@SubscribeEvent
public void onGuiInit(GuiOpenEvent event) {
// Check for the reward book notification up to 10 seconds after the reward's chat link was received
if (event.gui instanceof GuiScreenBook
if (Minecraft.getMinecraft().thePlayer != null
&& event.gui instanceof GuiScreenBook
&& (System.currentTimeMillis() - lastRewardOpenedMs) <= 10000) {
event.setCanceled(true);
lastRewardOpenedMs = 0;
Expand Down
84 changes: 53 additions & 31 deletions src/main/java/me/dancedog/rewardclaim/RewardScraper.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package me.dancedog.rewardclaim;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.dancedog.rewardclaim.model.RewardSession;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

Expand All @@ -18,53 +17,76 @@ class RewardScraper {

private static final Pattern CSRF_TOKEN_PATTERN = Pattern
.compile("window\\.securityToken = ['\"](.*?)['\"]");
private static final Pattern REWARD_JSON_PATTERN = Pattern
private static final Pattern SESSION_JSON_PATTERN = Pattern
.compile("window\\.appData = ['\"](.*?})['\"]");
private static final JsonParser jsonParser = new JsonParser();

/**
* Read the reward session json from a daily reward page
* Parse the session data from a daily reward page
*
* @param document Document to get the session json from
* @return JsonObject of the session data on the provided document, or null if
* @throws JsonParseException If the document's session data was not valid json
* @throws IllegalStateException If the document's session data was not a JsonObject
* @param document Document representing the reward page
* @param cookie Cookie to be passed into the returned session (used to make claim request)
* @return The session parsed from the provided page
*/
static JsonObject parseRewardPage(Document document) {
static RewardSession parseSessionFromRewardPage(Document document, String cookie) {
JsonObject rawSessionData;

if (document == null || document.body() == null) {
return createErrorJson("Document was null");
}
Element infoScript = document.body().selectFirst("script");
if (infoScript == null) {
return createErrorJson("Unable to locate reward data");
rawSessionData = createErrorJson("Document was null");

} else {
// The "props" script contains information useful the daily reward react app
// This includes the rewards data, csrf token, i18n messages, etc
Element propsScript = document.body().selectFirst("script");
if (propsScript == null) {
rawSessionData = createErrorJson("Unable to locate reward data");

} else {
String propsScriptContents = propsScript.data();
rawSessionData = getRawSessionData(propsScriptContents);
rawSessionData.addProperty("_csrf", getCsrfToken(propsScriptContents));
}
}
JsonObject data = new JsonObject();
return new RewardSession(rawSessionData, cookie);
}

// Get the JSON data for the reward session
String infoScriptContents = infoScript.data();
Matcher rewardJsonMatcher = REWARD_JSON_PATTERN.matcher(infoScriptContents);
/**
* Extract the reward session's json data from the prop script's contents
*
* @param propsScriptContents String contents of the prop script element
* @return JsonObject of the session's data, or a JsonObject containing an error message if none
* was found
*/
private static JsonObject getRawSessionData(String propsScriptContents) {
Matcher rewardJsonMatcher = SESSION_JSON_PATTERN.matcher(propsScriptContents);
if (rewardJsonMatcher.find()) {
JsonObject parsedRewardJson = jsonParser.parse(rewardJsonMatcher.group(1)).getAsJsonObject();
JsonElement errorMessage = parsedRewardJson.get("error");
if (errorMessage != null) {
return createErrorJson(errorMessage.getAsString());
}
data.add("session_data", parsedRewardJson);
return jsonParser.parse(rewardJsonMatcher.group(1)).getAsJsonObject();
} else {
return createErrorJson("Unable to locate reward data");
}
}

// Get the CSRF token needed to claim the reward
Matcher csrfTokenMatcher = CSRF_TOKEN_PATTERN.matcher(infoScriptContents);
/**
* Extract the csrf token from the prop script's contents
*
* @param propsScriptContents String contents of the prop script element
* @return Csrf token string, or null if none was found
*/
private static String getCsrfToken(String propsScriptContents) {
Matcher csrfTokenMatcher = CSRF_TOKEN_PATTERN.matcher(propsScriptContents);
if (csrfTokenMatcher.find()) {
data.addProperty("csrf_token", csrfTokenMatcher.group(1));
} else {
return createErrorJson("Unable to locate csrf token");
return csrfTokenMatcher.group(1);
}

return data;
return null;
}

/**
* Utility method to create a JsonObject containing an error message (same format that rewards
* page uses)
*
* @param errorMsg
* @return JsonObject containing the message
*/
private static JsonObject createErrorJson(String errorMsg) {
JsonObject json = new JsonObject();
json.addProperty("error", errorMsg);
Expand Down
Loading

0 comments on commit f562c1b

Please sign in to comment.