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

feat: Avatar Outfit Cost on Profiles

Index a90b6f26 ce330458

Changed files
+139 -44
entrypoints
profile.content
store.content
utils
+1 -1
README.md
···
-
# PolyPlus-Rewrite
+
# Poly+ Rewrite
A WXT, Typescript, cleaner code quality version of Poly+.
+2 -2
entrypoints/home.content.ts
···
const placeData: Array<apiTypes.placeApiSchema> | "disabled" = await pullCache(
'favoritedPlaces',
async () => await api.batch('public', 'places/', places),
-
300000,
+
300000, // 5 minutes
false
);
···
const userData = await pullCache(
'bestFriends',
async () => await api.batch('public', 'users/', friends),
-
300000,
+
300000, // 5 minutes
false
);
+4 -1
entrypoints/profile.content/index.ts
···
const data = (await (await fetch(config.api.urls.public + "users/find?username=" + username)).json());
return data.id;
},
-
600000,
+
-1,
false
);
} else {
···
if (window.location.pathname.split('/')[1] == "u") {
// View
+
if (config.devBuild) console.log('[Poly+] Running view page functions: ', view);
+
view.displayId(userID);
+
if (values.outfitCost.enabled) view.outfitCost(userID);
};
});
}
+74
entrypoints/profile.content/view.ts
···
import config from "@/utils/config.json";
+
import * as apiTypes from "@/utils/api/types";
export async function displayId(userId: number) {
const statsCard = document.getElementById('user-stats-card');
···
});
statsCard.children[0].insertBefore(row, statsCard.querySelector('.mb-1:has(.fa-calendar)')!);
+
};
+
+
export async function outfitCost(userId: number) {
+
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 (!config.api.enabled) return "disabled";
+
return (await (await fetch(config.api.urls.public + "users/" + userId + "/avatar")).json());
+
},
+
30000, // 30 seconds
+
false
+
);
+
+
if (avatar == "disabled") {
+
calculateBtn.innerHTML = '<span class="text-secondary">Outfit cost unavailable, please try again later</span>';
+
throw new Error('[Poly+] API is disabled, cancelling avatar cost loading..');
+
};
+
+
if (avatar.isDefault) {
+
calculateBtn.innerHTML = '<span class="text-secondary">Default avatar</span>';
+
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(config.api.urls.public + "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 = `<span class="text-success">${ outfit.cost.toLocaleString() }</span>`;
+
};
+
+
if (config.api.enabled) {
+
calculateBtn.innerHTML = '<a class="text-decoration-underline text-success" style="text-decoration-color: rgb(15, 132, 79) !important;">$ calculate avatar cost</a>';
+
calculateBtn.children[0].addEventListener('click', calculate);
+
} else {
+
calculateBtn.innerHTML = '<span class="text-secondary">Outfit cost unavailable, please try again later</span>';
+
console.error("[Poly+] API is disabled, outfit cost calculation is unavailable.")
+
}
+
document.querySelector('.section-title:first-child')!.appendChild(calculateBtn);
};
+1 -2
entrypoints/store.content/discovery.ts
···
);
if (inventory == "disabled") {
-
console.error('[Poly+] API is disabled, cancelling item owned tags loading..');
-
return;
+
throw new Error('[Poly+] API is disabled, cancelling item owned tags loading..');
};
const owns = (item: HTMLElement) => {
+22 -16
entrypoints/store.content/view.ts
···
console.warn('[Poly+] Failure to find purchase button on page.');
};
+
const addPrice = function(item: HTMLElement) {
+
const price = item.getElementsByClassName('text-success')[0];
+
const purchaseBtn = item.querySelector('button[data-listing-id]');
+
+
if (price && purchaseBtn) {
+
const currency = bricksToCurrency(parseInt(purchaseBtn.getAttribute('data-price')!), "USD");
+
+
if (currency) {
+
const spanTag = document.createElement('span');
+
spanTag.classList.add('text-muted');
+
spanTag.style.fontSize = '0.7rem';
+
spanTag.style.fontWeight = 'lighter';
+
spanTag.innerText = ` (${currency})`;
+
price.appendChild(spanTag);
+
};
+
};
+
}
+
const resellers = document.getElementById('resellers-container');
if (resellers) {
+
for (const reseller of resellers.children) addPrice(reseller as HTMLElement);
+
const mutations = new MutationObserver((mutations) => {
for (const record of mutations) {
for (const node of record.addedNodes) {
-
const price = (node as HTMLElement).getElementsByClassName('text-success')[0];
-
const purchaseBtn = (node as HTMLElement).querySelector('button[data-listing-id]');
-
-
if (price && purchaseBtn) {
-
const currency = bricksToCurrency(parseInt(purchaseBtn.getAttribute('data-price')!), "USD");
-
-
if (currency) {
-
const spanTag = document.createElement('span');
-
spanTag.classList.add('text-muted');
-
spanTag.style.fontSize = '0.7rem';
-
spanTag.style.fontWeight = 'lighter';
-
spanTag.innerText = ` (${currency})`;
-
price.appendChild(spanTag);
-
};
-
};
+
addPrice(node as HTMLElement);
};
};
});
mutations.observe(resellers, { childList: true });
-
}
+
};
};
export function tryOn(user: userDetails) {
+5 -2
utils/storage.ts
···
hideNotificationBadges: { enabled: false },
storeOwnedTags: { enabled: true },
membershipThemes: { enabled: false, themeId: "plus" },
-
tryItems: { enabled: true }
+
tryItems: { enabled: true },
+
outfitCost: { enabled: true }
}
export type preferencesSchema = typeof defaultPreferences & {
···
favoritedPlaces: [],
bestFriends: [],
inventory: [],
-
userIDs: {}
+
userIDs: {},
+
avatars: {},
+
items: {}
},
version: 1
});
+4 -2
utils/types.ts
···
-
import { avatarApiSchema } from "./api/types";
+
import * as apiTypes from "./api/types";
export type userDetails = {
username: string,
userId: number,
bricks: number,
-
getAvatar: () => Promise<avatarApiSchema> | Promise<"disabled">
+
getAvatar: () => Promise<apiTypes.avatarApiSchema> | Promise<"disabled">
}
export interface cacheInterface {
···
bestFriends: never[],
inventory: never[],
userIDs: Record<string, number>,
+
avatars: Record<string, apiTypes.avatarApiSchema>,
+
items: Record<string, apiTypes.itemApiSchema>,
[key: string]: any;
};
+26 -18
utils/utilities.ts
···
) {
const cacheStorage: cacheInterface = await cache.getValue();
const metadata = (await cache.getMeta()) as { [key: string]: number };
+
const overlap = Date.now() - (metadata[key] || 0);
+
const shouldReplenish = !cacheStorage[key] ||
+
cacheStorage[key] == "disabled" ||
+
!metadata[key] ||
+
forceReplenish ||
+
(expiry !== -1 && overlap >= expiry);
-
if (
-
!cacheStorage[key] || !metadata[key] || forceReplenish ||
-
overlap >= expiry
-
) {
+
if (shouldReplenish) {
if (config.devBuild) {
console.info(
-
`[Poly+] "${key}" cache is stale replenishing...`,
-
timeAgo(overlap),
+
`[Poly+] "${key}" cache ${
+
expiry === -1 ? "doesn't exist" : "is stale"
+
} replenishing...`,
+
expiry !== -1 ? timeAgo(overlap) : "",
);
-
};
+
}
const replenishedCache = await replenish();
-
// Don't cache the response when the config file has APIs disabled
if (replenishedCache !== "disabled") {
cacheStorage[key] = replenishedCache;
···
const metadata = (await cache.getMeta()) as {
[key: string]: Record<string, number>;
};
-
+
if (!cacheStorage[store]) cacheStorage[store] = {};
if (!metadata[store]) metadata[store] = {};
const overlap = Date.now() - (metadata[store][key] || 0);
+
const shouldReplenish = !cacheStorage[store][key] ||
+
cacheStorage[store][key] == "disabled" ||
+
forceReplenish ||
+
(expiry !== -1 && overlap >= expiry);
-
if (!cacheStorage[store][key] || forceReplenish || overlap >= expiry) {
+
if (shouldReplenish) {
if (config.devBuild) {
console.info(
-
`[Poly+] "${key}" KV cache is stale replenishing...`,
-
timeAgo(overlap),
+
`[Poly+] "${key}" KV cache ${
+
expiry === -1 ? "doesn't exist" : "is stale"
+
} replenishing...`,
+
expiry !== -1 ? timeAgo(overlap) : "",
);
-
};
+
}
const replenishedCache = await replenish();
-
// Don't cache the response when the config file has APIs disabled
if (replenishedCache !== "disabled") {
cacheStorage[store][key] = replenishedCache;
···
bricks: parseInt(brickBalance.textContent!.replace(/,/g, "")),
getAvatar: async () => {
if (config.api.enabled) {
-
const avatar =
-
await (await fetch(
-
config.api.urls.public + "users/" + userId + "/avatar",
-
)).json();
+
const avatar = await (await fetch(
+
config.api.urls.public + "users/" + userId + "/avatar",
+
)).json();
return avatar as avatarApiSchema;
} else {
return "disabled";