Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.1.1 - Bug Fixes #2

Merged
merged 4 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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