Adds [<10], [10-99], [100-999], [>1000], Not dungeon and D1-4 filter buttons to Collections
// ==UserScript==
// @name Filter Collections
// @namespace http://tampermonkey.net/
// @version 2025-12-12
// @description Adds [<10], [10-99], [100-999], [>1000], Not dungeon and D1-4 filter buttons to Collections
// @license MIT
// @author sentientmilk
// @match https://www.milkywayidle.com/*
// @icon https://www.milkywayidle.com/favicon.svg
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
/*
Changelog
=========
v2025-12-06
- Initial version
v2025-12-06-2
- FIXED: "<10" button was showing 10s
v2025-12-06-3
- Made checkbox label clickable
v2025-12-11
- Save and restore "Show Uncollected Items" state when opening Collections again
- FIXED: Items that can be in 2 dungeons (like Pestilent Shot) were attributed to only a single dungeon
v2025-12-11-2
- FIXED: Not showin non dungeon drops
v2025-12-12
- Show collection badges on skilling actions
- FIXED: Sometimes no/almost no items were shown after opeming Collections again
TODO
====================
*/
(function() {
async function waitFnRepeatedFor (selector, callback) {
let notified = false;
return new Promise((resolve) => {
let lastEl = null
function check () {
const el = selector();
if (el && el != lastEl) {
lastEl = el;
notified = false;
}
setTimeout(check, 1000/30); // Schedule first to allow the callback to throw
if (el && !notified) {
notified = true;
resolve(el);
if (callback) {
callback(el);
}
} else if (el && notified) {
// Skip, wait for cond to be false again
} else {
notified = false;
}
}
check();
});
}
function unformatNumber (s) {
if (s.endsWith("M")) {
return parseFloat(s) * 1000 * 1000;
} else if (s.endsWith("K")) {
return parseFloat(s) * 1000;
} else if (("" + parseFloat(s)) == s) {
return parseFloat(s);
}
}
function f (n) {
if (typeof n != "number") {
return "NaN";
} else if (n == 0) {
return "" + n;
} else if (Math.abs(n) < 1) {
return n.toFixed(2);
} else if (Math.abs(n) < 10*1000) {
if (n % 1 == 0) {
return "" + n;
} else {
return n.toFixed(1);
}
} else if (Math.abs(n) <= 1*1000*1000) {
const k = n/1000;
if (k % 1 == 0) {
return k + "K";
} else {
return k.toFixed(1) + "K";
}
} else if (Math.abs(n) > 1*1000*1000) {
const m = n/(1000*1000);
if (m % 1 == 0) {
return m + "M";
} else if (m % 0.1 == 0) {
return m.toFixed(1) + "M";
} else {
return m.toFixed(2) + "M";
}
} else {
return "" + n;
}
}
const dungeonsItems = {
"d1": new Set([
"chimerical_chest",
"chimerical_refinement_chest",
"chimerical_token",
"chimerical_quiver",
"chimerical_quiver_refined",
"griffin_leather",
"manticore_sting",
"jackalope_antler",
"dodocamel_plume",
"griffin_talon",
"chimerical_refinement_shard",
"chimerical_essence",
"shield_bash",
"crippling_slash",
"pestilent_shot",
"griffin_tunic",
"griffin_chaps",
"manticore_shield",
"jackalope_staff",
"dodocamel_gauntlets",
"griffin_bulwark",
]),
"d2": new Set([
"sinister_chest",
"sinister_refinement_chest",
"sinister_token",
"sinister_cape",
"sinister_cape_refined",
"acrobats_ribbon",
"magicians_cloth",
"chaotic_chain",
"cursed_ball",
"sinister_refinement_shard",
"sinister_essence",
"penetrating_strike",
"pestilent_shot",
"smoke_burst",
"acrobatic_hood",
"magicians_hat",
"chaotic_flail",
"cursed_bow",
]),
"d3": new Set([
"enchanted_chest",
"enchanted_refinement_chest",
"enchanted_token",
"enchanted_cloak",
"enchanted_cloak_refined",
"royal_cloth",
"knights_ingot",
"bishops_scroll",
"regal_jewel",
"sundering_jewel",
"enchanted_refinement_shard",
"enchanted_essence",
"crippling_slash",
"penetrating_shot",
"retribution",
"mana_spring",
"knights_aegis",
"bishops_codex",
"royal_water_robe_top",
"royal_water_robe_bottoms",
"royal_nature_robe_top",
"royal_nature_robe_bottoms",
"royal_fire_robe_top",
"royal_fire_robe_bottoms",
"furious_spear",
"regal_sword",
"sundering_crossbow",
]),
"d4": new Set([
"pirate_chest",
"pirate_refinement_chest",
"pirate_token",
"marksman_brooch",
"corsair_crest",
"damaged_anchor",
"maelstrom_plating",
"kraken_leather",
"kraken_fang",
"pirate_refinement_shard",
"pirate_essence",
"shield_bash",
"fracturing_impact",
"life_drain",
"marksman_bracers",
"corsair_helmet",
"anchorbound_plate_body",
"anchorbound_plate_legs",
"maelstrom_plate_body",
"maelstrom_plate_legs",
"kraken_tunic",
"kraken_chaps",
"rippling_trident",
"blooming_trident",
"blazing_trident",
]),
};
function checkbox ({ label, className, checked }) {
return `<div class="AchievementsPanel_checkboxControl__3e6CJ ${className}">
<label class="MuiFormControlLabel-root MuiFormControlLabel-labelPlacementEnd Checkbox_checkbox__dP0DH css-1jaw3da">
<span class="MuiButtonBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall PrivateSwitchBase-root MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall ${checked ? "Mui-checked" : ""} MuiCheckbox-root MuiCheckbox-colorPrimary MuiCheckbox-sizeSmall css-zun73v">`
+ (checked ? `<svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall css-1k33q06" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="CheckBoxIcon">
<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"></path>
</svg>` : "")
+ (!checked ? `<svg class="MuiSvgIcon-root MuiSvgIcon-fontSizeSmall css-1k33q06" focusable="false" aria-hidden="true" viewBox="0 0 24 24" data-testid="CheckBoxOutlineBlankIcon">
<path d="M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"></path>
</svg>` : "")
+ `</span>
<span class="MuiTypography-root MuiTypography-body1 MuiFormControlLabel-label css-9l3uo3">${label}</span>
</label>
</div>`;
}
let flags = [
{ label: "<10", className: "c10", from: 0, to: 9, checked: true },
{ label: "10-99", className: "c99", from: 10, to: 99, checked: true },
{ label: "100-999", className: "c999", from: 100, to: 999, checked: true },
{ label: "1000+", className: "c1000", from: 1000, to: Infinity, checked: true },
{ label: "Not dungeon", className: "nod", checked: true },
{ label: "D1", className: "d1", dungeon: "d1", checked: true },
{ label: "D2", className: "d2", dungeon: "d2", checked: true },
{ label: "D3", className: "d3", dungeon: "d3", checked: true },
{ label: "D4", className: "d4", dungeon: "d4", checked: true },
];
const characterID = unsafeWindow.location.search.split("=")[1];
let collections = GM_getValue("collections_" + characterID, {});
function rerenderCollectionsFiltering (panelEl) {
const catsEl = panelEl.parentElement.querySelector(".AchievementsPanel_categories__34hno");
catsEl.querySelectorAll(".Collection_collectionContainer__3ZlUO").forEach((el) => {
const itemId = el.querySelector("use").getAttribute("href").split("#")[1];
const n = unformatNumber(el.querySelector(".Collection_count__3oj-t")?.textContent ?? "0");
const f = flags.find((f) => (f.from || f.to) && f.from <= n && n <= f.to);
el.dataset.count = f.className;
collections[itemId] = n;
let isDungeonItem = false;
for (let d in dungeonsItems) {
if (dungeonsItems[d].has(itemId)) {
el.classList.add(d + "-ucf-userscript");
isDungeonItem = true;
}
}
if (!isDungeonItem) {
el.classList.add("nod" + "-ucf-userscript");
}
});
GM_setValue("collections_" + characterID, collections);
let containerEl = panelEl.querySelector(".ucf-userscript");
if (!containerEl) {
panelEl.insertAdjacentHTML("beforeend", `<div class="ucf-userscript" style="display: flex"></div>`);
containerEl = panelEl.querySelector(".ucf-userscript");
}
function updateShowClass (f) {
if (f.checked) {
catsEl.classList.add(`show-${f.className}-ucf-userscript`);
} else {
catsEl.classList.remove(`show-${f.className}-ucf-userscript`);
}
}
containerEl.innerHTML = flags.map(checkbox).join("");
flags.forEach((f) => {
containerEl.querySelector("." + f.className).onclick = (event) => {
event.stopPropagation();
f.checked = !f.checked;
rerenderCollectionsFiltering(panelEl);
};
updateShowClass(f);
});
catsEl.classList.add("ucf-userscript");
}
let showUncollected = false;
function saveShowUncollected () {
showUncollected = document.querySelector(".AchievementsPanel_collections__qA6CY .AchievementsPanel_controls__3bGFT > .AchievementsPanel_checkboxControl__3e6CJ > label > span").classList.contains("Mui-checked")
}
function restoreShowUncollected () {
if (showUncollected) {
document.querySelector(".AchievementsPanel_collections__qA6CY .AchievementsPanel_controls__3bGFT > .AchievementsPanel_checkboxControl__3e6CJ input").click();
}
}
function isCollectionsOnScreen () {
return document.querySelector(".TabPanel_tabPanel__tXMJF:not(.TabPanel_hidden__26UM3) .AchievementsPanel_collections__qA6CY .Collection_collectionContainer__3ZlUO");
}
function addCollectionsFiltering () {
const panelEl = document.querySelector(".TabPanel_tabPanel__tXMJF:not(.TabPanel_hidden__26UM3) .AchievementsPanel_collections__qA6CY .AchievementsPanel_controls__3bGFT");
restoreShowUncollected();
saveShowUncollected();
rerenderCollectionsFiltering(panelEl);
panelEl.querySelector(".AchievementsPanel_refreshButton__3RYCh").onclick = () => {
setTimeout(() => {
rerenderCollectionsFiltering(panelEl);
}, 500);
};
panelEl.parentElement.querySelector(".AchievementsPanel_controls__3bGFT > .AchievementsPanel_checkboxControl__3e6CJ").addEventListener("click", () => {
setTimeout(() => {
saveShowUncollected();
}, 500);
}, true);
panelEl.parentElement.querySelector(".AchievementsPanel_controls__3bGFT > .AchievementsPanel_checkboxControl__3e6CJ").onclick = () => {
requestAnimationFrame(() => {
rerenderCollectionsFiltering(panelEl);
});
};
}
const actionToItem = {
"cow": "milk",
"verdant_cow": "verdant_milk",
"azure_cow": "azure_milk",
"burble_cow": "burble_milk",
"crimson_cow": "crimson_milk",
"unicow": "rainbow_milk",
"holy_cow": "holy_milk",
"tree": "log",
"birch_tree": "birch_log",
"cedar_tree": "cedar_log",
"purpleheart_tree": "purpleheart_log",
"ginkgo_tree": "ginkgo_log",
"redwood_tree": "redwood_log",
"arcane_tree": "arcane_log",
};
function isSkillingScreen () {
return document.querySelector(".TabPanel_tabPanel__tXMJF:not(.TabPanel_hidden__26UM3) .SkillActionGrid_skillActionGrid__1tJFk");
}
function tierColorClass (n) {
if (n == 0) {
return "Collection_tierGray__279Mp";
} else if (n < 10) {
return "Collection_tierWhite__2m0_1";
} else if (n < 100) {
return "Collection_tierGreen__ExgCi";
} else if (n < 1000) {
return "Collection_tierBlue__3uYl-";
} else if (n < 10000) {
return "Collection_tierPurple__13F_l";
} else if (n < 100000) {
return "Collection_tierRed__3dV_1";
} else {
return "Collection_tierRainbow__1eS_P";
}
}
function addSkillingCollectionCounts (containerEl) {
const itemsEls = containerEl.querySelectorAll(".SkillAction_skillAction__1esCp");
itemsEls.forEach((el) => {
let itemId = el.querySelector("use").getAttribute("href").split("#")[1];
if (itemId in actionToItem) {
itemId = actionToItem[itemId];
}
if (itemId in collections) {
const n = collections[itemId];
el.querySelector(".ucf-userscript")?.remove();
el.querySelector(".SkillAction_name__2VPXa").insertAdjacentHTML("beforeend", `
<span class="ucf-userscript Collection_collection__3H6c8 ${tierColorClass(n)}">
<span class="Collection_count__3oj-t">${f(n)}</span>
</span>
`.replace(/[\t\n]+/g, ""));
}
});
}
document.body.insertAdjacentHTML("beforeend", `<style class="ucf-userscript">`
+ `.AchievementsPanel_categories__34hno.ucf-userscript .Collection_collectionContainer__3ZlUO { display: none; }\n`
+ `.ucf-userscript.Collection_collection__3H6c8 { border-radius: var(--radius-sm); margin-left: 4px; padding: 2px; }\n`
+ flags.map((f) => {
return ["nod", ...Object.keys(dungeonsItems)].map((d) => {
return `.AchievementsPanel_categories__34hno.ucf-userscript.show-${f.className}-ucf-userscript.show-${d}-ucf-userscript .Collection_collectionContainer__3ZlUO[data-count="${f.className}"].${d}-ucf-userscript { display: initial; }\n`
}).join("");
}).join("")
+ ` </style>`);
waitFnRepeatedFor(isCollectionsOnScreen, addCollectionsFiltering);
waitFnRepeatedFor(isSkillingScreen, addSkillingCollectionCounts);
})();