A rewrite of Poly+, my quality-of-life browser extension for Polytoria. Built entirely fresh using the WXT extension framework, Typescript, and with added better overall code quality.
extension
1import config from "@/utils/config.json"; 2import * as apiTypes from "@/utils/api/types"; 3 4export async function displayId(userId: number) { 5 const statsCard = document.getElementById('user-stats-card'); 6 if (!statsCard) return; // ? Incase the user is blocked, which means the stats card won't be present 7 8 const row = document.createElement('div'); 9 row.classList.add('mb-1'); 10 row.innerHTML = ` 11 <b> 12 <i class="fa fa-hashtag text-center d-inline-block" style="width:1.3em"></i> 13 Player ID 14 </b> 15 <span class="float-end"> 16 ${userId} 17 <a id="copy" href="#copy"> 18 <i class="fad fa-copy" style="margin-left: 5px;"></i> 19 </a> 20 </span> 21 `; 22 23 const copyBtn = row.getElementsByTagName('a')[0]; 24 copyBtn.addEventListener('click', () => { 25 navigator.clipboard.writeText(userId as unknown as string) 26 .then(() => { 27 const icon: HTMLElement = copyBtn.children[0] as HTMLElement; 28 copyBtn.classList.add('text-success'); 29 icon.setAttribute('class', 'fa-duotone fa-circle-check'); 30 icon.style.marginLeft = '3px'; 31 32 setTimeout(() => { 33 copyBtn.classList.remove('text-success'); 34 icon.setAttribute('class', 'fad fa-copy'); 35 icon.style.marginLeft = '5px'; 36 }, 1500); 37 }) 38 .catch(() => { 39 alert('Failure to copy user ID to clipboard.'); 40 }); 41 }); 42 43 statsCard.children[0].insertBefore(row, statsCard.querySelector('.mb-1:has(.fa-calendar)')!); 44}; 45 46export async function outfitCost(userId: number) { 47 const calculateBtn = document.createElement('small'); 48 calculateBtn.classList.add('fw-normal'); 49 calculateBtn.style.letterSpacing = '0px'; 50 51 const calculate = async function() { 52 const outfit = { 53 cost: 0, 54 collectibles: 0, 55 offsale: 0, 56 timed: 0 57 }; 58 59 const avatar: apiTypes.avatarApiSchema | "disabled" = await pullKVCache( 60 'avatars', 61 userId.toString(), 62 async () => { 63 if (!config.api.enabled) return "disabled"; 64 return (await (await fetch(config.api.urls.public + "users/" + userId + "/avatar")).json()); 65 }, 66 30000, // 30 seconds 67 false 68 ); 69 70 if (avatar == "disabled") { 71 calculateBtn.innerHTML = '<span class="text-secondary">Outfit cost unavailable, please try again later</span>'; 72 throw new Error('[Poly+] API is disabled, cancelling avatar cost loading..'); 73 }; 74 75 if (avatar.isDefault) { 76 calculateBtn.innerHTML = '<span class="text-secondary">Default avatar</span>'; 77 console.warn('[Poly+] User has default avatar, cancelling avatar cost loading..'); 78 return; 79 }; 80 81 for (const asset of avatar.assets.filter((asset) => asset.type != "profileTheme")) { 82 // ? API won't be disabled at this step, since there was already an API check previously 83 const item: apiTypes.itemApiSchema = await pullKVCache( 84 'items', 85 asset.id.toString(), 86 async () => { 87 return (await (await fetch(config.api.urls.public + "store/" + asset.id)).json()); 88 }, 89 600000, 90 false 91 ); 92 93 if (item.isLimited) { 94 outfit.collectibles++; 95 outfit.cost += item.averagePrice!; 96 } else if (!item.price) { 97 outfit.offsale++; 98 } else if (item.onSaleUntil != null) { 99 outfit.timed++; 100 } else { 101 outfit.cost += item.price!; 102 }; 103 }; 104 105 console.log('[Poly+] Outfit breakdown: ', outfit); 106 calculateBtn.innerHTML = `<span class="text-success">${ outfit.cost.toLocaleString() }</span>`; 107 }; 108 109 if (config.api.enabled) { 110 calculateBtn.innerHTML = '<a class="text-decoration-underline text-success" style="text-decoration-color: rgb(15, 132, 79) !important;">$ calculate avatar cost</a>'; 111 calculateBtn.children[0].addEventListener('click', calculate); 112 } else { 113 calculateBtn.innerHTML = '<span class="text-secondary">Outfit cost unavailable, please try again later</span>'; 114 console.error("[Poly+] API is disabled, outfit cost calculation is unavailable.") 115 } 116 document.querySelector('.section-title:first-child')!.appendChild(calculateBtn); 117};