import config from "@/utils/config.json"; import * as apiTypes from "@/utils/api/types"; import { getAPI } from "@/utils/api"; /** * Adds a row to the user statistics card on the profile page allowing the user to quickly view & copy another user's ID. * @param userId The ID of the user. */ export async function displayId(userId: number) { const statsCard = document.getElementById("user-stats-card"); if (!statsCard) return; // ? Incase the user is blocked, which means the stats card won't be present const row = document.createElement("div"); row.classList.add("mb-1"); row.innerHTML = ` Player ID ${userId} `; const copyBtn = row.getElementsByTagName("a")[0]; copyBtn.addEventListener("click", () => { navigator.clipboard.writeText(userId as unknown as string) .then(() => { const icon: HTMLElement = copyBtn.children[0] as HTMLElement; copyBtn.classList.add("text-success"); icon.setAttribute("class", "fa-duotone fa-circle-check"); icon.style.marginLeft = "3px"; setTimeout(() => { copyBtn.classList.remove("text-success"); icon.setAttribute("class", "fad fa-copy"); icon.style.marginLeft = "5px"; }, 1500); }) .catch(() => { alert("Failure to copy user ID to clipboard."); }); }); statsCard.children[0].insertBefore( row, statsCard.querySelector(".mb-1:has(.fa-calendar)")!, ); } /** * Adds a button next to the "Avatar" card heading, which, on click, adds up all the items the user who owns this profile is wearing. * @param userId The ID of the user. */ export async function outfitCost(userId: number) { const publicAPI = getAPI("public"); const calculateBtn = document.createElement("small"); calculateBtn.classList.add("fw-normal"); calculateBtn.style.letterSpacing = "0px"; const calculate = async function () { const outfit = { cost: 0, collectibles: 0, offsale: 0, timed: 0, }; const avatar: apiTypes.avatarApiSchema | "disabled" = await pullKVCache( "avatars", userId.toString(), async () => { if (!publicAPI.enabled) return "disabled"; return (await (await fetch( publicAPI.url + "users/" + userId + "/avatar", )).json()); }, 30000, // 30 seconds false, ); if (avatar == "disabled") { calculateBtn.innerHTML = 'Outfit cost unavailable, please try again later'; throw new Error( "[Poly+] API is disabled, cancelling avatar cost loading..", ); } if (avatar.isDefault) { calculateBtn.innerHTML = 'Default avatar'; console.warn( "[Poly+] User has default avatar, cancelling avatar cost loading..", ); return; } for ( const asset of avatar.assets.filter((asset) => asset.type != "profileTheme" ) ) { // ? API won't be disabled at this step, since there was already an API check previously const item: apiTypes.itemApiSchema = await pullKVCache( "items", asset.id.toString(), async () => { return (await (await fetch( publicAPI.url + "store/" + asset.id, )).json()); }, 600000, false, ); if (item.isLimited) { outfit.collectibles++; outfit.cost += item.averagePrice!; } else if (!item.price) { outfit.offsale++; } else if (item.onSaleUntil != null) { outfit.timed++; } else { outfit.cost += item.price!; } } console.log("[Poly+] Outfit breakdown: ", outfit); calculateBtn.innerHTML = ` approx. ${outfit.cost.toLocaleString()} brick(s)`; }; if (publicAPI.enabled) { calculateBtn.innerHTML = '$ calculate avatar cost'; calculateBtn.children[0].addEventListener("click", calculate); } else { calculateBtn.innerHTML = 'Outfit cost unavailable, please try again later'; console.error( "[Poly+] API is disabled, outfit cost calculation is unavailable.", ); } document.querySelector(".section-title:first-child")!.appendChild( calculateBtn, ); }