Skip to content
This repository was archived by the owner on Aug 12, 2025. It is now read-only.
17 changes: 16 additions & 1 deletion src/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,16 @@ export function renderRaceTier(completeTiers) {
return "●".repeat(completeTiers) + "○".repeat(incompleteTiers);
}

/**
* returns a string with 5 dots "●" for completed tiers and "○" for incomplete tiers
* @param {number} completeTiers
* @returns {string} 5 dots
*/
export function renderAchievementTier(completeTiers) {
const incompleteTiers = Math.max(0, 5 - completeTiers);
return "●".repeat(completeTiers) + "○".repeat(incompleteTiers);
}

/**
* checks whether a string should be proceeded by a or by an
* @param {string} string
Expand Down Expand Up @@ -533,6 +543,7 @@ export async function updateRank(uuid, db) {
plusColor: null,
socials: {},
achievements: {},
achievementsOneTime: {},
claimed_items: {},
};

Expand All @@ -558,6 +569,10 @@ export async function updateRank(uuid, db) {
rank.achievements = player.achievements;
}

if (player?.achievementsOneTime != undefined) {
rank.achievementsOneTime = player.achievementsOneTime;
}

const claimable = {
claimed_potato_talisman: "Potato Talisman",
claimed_potato_basket: "Potato Basket",
Expand Down Expand Up @@ -602,7 +617,7 @@ export async function getRank(uuid, db, cacheOnly = false) {
hypixelPlayer = await _updateRank;
}

hypixelPlayer ??= { achievements: {} };
hypixelPlayer ??= { achievements: {}, achievementsOneTime: {} };

return hypixelPlayer;
}
Expand Down
52 changes: 52 additions & 0 deletions src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -1931,6 +1931,7 @@ export const getStats = async (
misc.auctions_sell = {};
misc.auctions_buy = {};
misc.claimed_items = {};
misc.achievements = {};

if ("ender_crystals_destroyed" in userProfile.stats) {
misc.dragons["ender_crystals_destroyed"] = userProfile.stats["ender_crystals_destroyed"];
Expand All @@ -1943,6 +1944,57 @@ export const getStats = async (
misc.claimed_items = hypixelProfile.claimed_items;
}

const _tiered = { sum: 0, total: 0, completed: {}, uncompleted: {} };
const _oneTime = { sum: 0, total: 0, completed: {}, uncompleted: {} };
for await (const tmp of db.collection("achievements").find()) {
if (tmp?.tiered) {
_tiered.total += tmp.achievement.tiers.map((a) => a.points).reduce((a, b) => a + b, 0);

let tier = 0;
for (const req in tmp.achievement.tiers) {
if (tmp.achievement.tiers[req].amount <= hypixelProfile.achievements["skyblock_" + tmp.id.toLowerCase()]) {
tier++;
_tiered.sum += Number(tmp.achievement.tiers[req].points);
}
}

const achievement = {
name: tmp.achievement.name,
description: tmp.achievement.description.replaceAll("%s", "x"),
level: hypixelProfile.achievements["skyblock_" + tmp.id.toLowerCase()] || 0,
tier: tier,
};

if (tier >= 5) {
_tiered.completed["skyblock_" + tmp.id.toLowerCase()] = achievement;
} else {
_tiered.uncompleted["skyblock_" + tmp.id.toLowerCase()] = achievement;
}
}

if (tmp?.one_time) {
_oneTime.total += tmp.achievement.points;
const achievement = {
name: tmp.achievement.name,
description: tmp.achievement.description,
reward: tmp.achievement.points,
};

if (
hypixelProfile.achievementsOneTime &&
hypixelProfile.achievementsOneTime.includes("skyblock_" + tmp.id.toLowerCase())
) {
_oneTime.completed["skyblock_" + tmp.id.toLowerCase()] = achievement;
_oneTime.sum += Number(tmp.achievement.points);
} else {
_oneTime.uncompleted["skyblock_" + tmp.id.toLowerCase()] = achievement;
}
}
}

misc.achievements.tiered = _tiered;
misc.achievements.oneTime = _oneTime;

const burrows = [
"mythos_burrows_dug_next",
"mythos_burrows_dug_combat",
Expand Down
1 change: 1 addition & 0 deletions src/master.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ await import("./scripts/init-collections.js");
await Promise.all([
import("./scripts/cap-leaderboards.js"),
import("./scripts/clear-favorite-cache.js"),
import("./scripts/update-achievements.js"),
import("./scripts/update-bazaar.js"),
import("./scripts/update-items.js"),
import("./scripts/update-featured-profiles.js"),
Expand Down
2 changes: 2 additions & 0 deletions src/scripts/init-collections.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ await Promise.all([
db.collection("profileCache").createIndex({ profile_id: 1 }, { unique: true }),

db.collection("featuredProfiles").createIndex({ total: -1 }),

db.collection("achievements").createIndex({ id: 1 }, { unique: true }),
]);
42 changes: 42 additions & 0 deletions src/scripts/update-achievements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { db } from "../mongo.js";
import axios from "axios";
import "axios-debug-log";

const Hypixel = axios.create({
baseURL: "https://api.hypixel.net/",
});

async function updateAchievements() {
try {
const response = await Hypixel.get("resources/achievements");

const achievements = [];
const { one_time, tiered } = response.data.achievements.skyblock;

for (const achId in one_time) {
achievements.push({
id: achId,
one_time: true,
achievement: one_time[achId],
});
}

for (const achId in tiered) {
achievements.push({
id: achId,
tiered: true,
achievement: tiered[achId],
});
}

achievements.forEach(async (item) => {
await db.collection("achievements").updateOne({ id: item.id }, { $set: item }, { upsert: true });
});
} catch (e) {
console.error(e);
}

setTimeout(updateAchievements, 1000 * 60 * 60 * 6);
}

updateAchievements();
54 changes: 53 additions & 1 deletion views/stats.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -2478,7 +2478,6 @@ const metaDescription = getMetaDescription()
<% } %>
</p>
<% } %>

<% if ('uncategorized' in calculated.misc && Object.keys(calculated.misc.uncategorized).length > 0) { %>
<div class="category-header"><div class="category-icon"><div class="item-icon icon-421_0"></div></div><span>Uncategorized</span></div>
<p class="stat-raw-values">
Expand All @@ -2488,6 +2487,59 @@ const metaDescription = getMetaDescription()
<% } %>
</p>
<% } %>
<% if ('achievements' in calculated.misc) { %>
<% const list = calculated.misc.achievements; %>
<div class="category-header"><div class="category-icon"><div class="item-icon icon-340_0"></div></div><span>achievements</span></div>
<p class="stat-raw-values stat-achievements">
<% if('tiered' in list){ %>
<span class="stat-name<%= (list.tiered.sum >= list.tiered.total ?" golden-text":"") %>">Tiered <abbr title="Achievement Points">AP</abbr>: </span><span class="stat-value<%= (list.tiered.sum >= list.tiered.total ?" golden-text":"") %>"><%= list.tiered.sum %> / <%= list.tiered.total %></span><span class="grey-text"> (<%= Math.floor(list.tiered.sum / list.tiered.total * 100) %>%)</span><br>
<% } %>
<% if('oneTime' in list){ %>
<span class="stat-name<%= (list.oneTime.sum >= list.oneTime.total ?" golden-text":"") %>">One Time <abbr title="Achievement Points">AP</abbr>: </span><span class="stat-value<%= (list.oneTime.sum >= list.oneTime.total ?" golden-text":"") %>"><%= list.oneTime.sum %> / <%= list.oneTime.total %></span><span class="grey-text"> (<%= Math.floor(list.oneTime.sum / list.oneTime.total * 100) %>%)</span><br>
<% } %>
</p>

<div class="achievements-container narrow-info-container-wrapper">
<div class="narrow-info-container achievements-tiered">
<div class="narrow-info-header">Tiered</div>
<div class="narrow-info-content">
<% if('tiered' in list && 'uncompleted' in list.tiered && 'completed' in list.tiered){ %>
<% for(const key in list.tiered.uncompleted){ %>
<div class="narrow-info-flexsb">
<div class="achievements-stat" data-tippy-content='<%= list.tiered.uncompleted[key].description %>'><%= list.tiered.uncompleted[key].name %><span class="grey-text"> (<%= list.tiered.uncompleted[key].level %>)</span></div>
<div><span class="stat-value"><%= helper.renderAchievementTier(list.tiered.uncompleted[key].tier) %></span></div>
</div>
<% } %>
<% for(const key in list.tiered.completed){ %>
<div class="narrow-info-flexsb">
<div class="achievements-stat golden-text" data-tippy-content='<%= list.tiered.completed[key].description %>'><%= list.tiered.completed[key].name %><span class="grey-text"> (<%= list.tiered.completed[key].level %>)</span></div>
<div><span class="stat-value"><%= helper.renderAchievementTier(list.tiered.completed[key].tier) %></span></div>
</div>
<% } %>
<% } %>
</div>
</div>
<div class="narrow-info-container achievements-oneTime">
<div class="narrow-info-header">One Time</div>
<div class="narrow-info-content">
<% if('oneTime' in list && 'uncompleted' in list.oneTime && 'completed' in list.oneTime){ %>
<% for(const key in list.oneTime.uncompleted){ %>
<div class="narrow-info-flexsb">
<div class="achievements-stat" data-tippy-content='<%= list.oneTime.uncompleted[key].description %>'><%= list.oneTime.uncompleted[key].name %></div>
<div class="grey-text">&nbsp;<%= list.oneTime.uncompleted[key].reward %></div>
</div>
<% } %>
<% for(const key in list.oneTime.completed){ %>
<div class="narrow-info-flexsb">
<div class="achievements-stat golden-text" data-tippy-content='<%= list.oneTime.completed[key].description %>'><%= list.oneTime.completed[key].name %></div>
<div class="grey-text">&nbsp;<%= list.oneTime.completed[key].reward %></div>
</div>
<% } %>
<% } %>
</div>
</div>
</div>
<% } %>
</div>
</div>
<% } %>
Expand Down