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: reimplement Store Owned Tags

Index 0a9114c1 d8df51f2

Changed files
+136 -16
entrypoints
account.content
store.content
public
utils
+2 -2
entrypoints/account.content/friends.ts
···
acceptAll.addEventListener('click', async () => {
setDisabled(true);
-
const ids = await api.iterate('internal', 'friends/requests?page=', 1);
+
const ids = await api.iterate('internal', 'friends/requests?page=', null, 1);
const payloads = ids.map((request: any) => ({
method: 'POST',
headers: {
···
declineAll.addEventListener('click', async () => {
setDisabled(true);
-
const ids = await api.iterate('internal', 'friends/requests?page=', 1);
+
const ids = await api.iterate('internal', 'friends/requests?page=', null, 1);
const payloads = ids.map((request: any) => ({
method: 'POST',
headers: {
+78 -1
entrypoints/store.content/discovery.ts
···
-
import { bricksToCurrency } from "@/utils/utilities";
+
import { pullCache, bricksToCurrency } from "@/utils/utilities";
+
import * as api from "@/utils/api";
export function irlBrickPrice() {
const grid = document.getElementById('assets')!;
···
for (const node of record.addedNodes) {
const item = node as HTMLElement;
if ((item as HTMLElement).classList.contains('itemCardCont')) addPrice(item);
+
};
+
};
+
});
+
+
mutations.observe(grid, { childList: true });
+
};
+
+
export async function ownedTags(userId: number) {
+
const grid = document.getElementById('assets')!;
+
const inventory = await pullCache(
+
'inventory',
+
async () => await api.iterate(
+
'public',
+
'users/' + userId + '/inventory?page=',
+
{
+
data: "inventory",
+
metadata: "meta"
+
},
+
1,
+
5
+
),
+
300000,
+
false
+
);
+
+
const owns = (item: HTMLElement) => {
+
const itemId = parseInt(item.getAttribute('href')!.split('/')[2]);
+
+
if (inventory.find((item: {
+
asset: {
+
id: number
+
}
+
}) => item.asset.id == itemId)) {
+
return true;
+
};
+
+
return false;
+
};
+
+
const addTag = (item: HTMLElement) => {
+
const tag = document.createElement('span');
+
tag.classList.add('badge', 'bg-primary');
+
tag.setAttribute('style', `
+
position: absolute;
+
font-size: 0.9rem;
+
top: 0px;
+
left: 0px;
+
padding: 5.5px;
+
border-top-left-radius: var(--bs-border-radius-lg) !important;
+
border-top-right-radius: 0px;
+
border-bottom-left-radius: 0px;
+
font-size: 0.65rem;
+
`);
+
tag.innerHTML = "<i class='fas fa-star'></i>";
+
+
const image = item.getElementsByTagName('img')[0]!;
+
image.parentElement!.appendChild(tag);
+
};
+
+
for (const item of document.getElementsByClassName('itemCardCont')) {
+
const link: HTMLElement = item.getElementsByTagName('a')[0]!;
+
if (owns(link)) {
+
addTag(item as HTMLElement);
+
};
+
};
+
+
const mutations = new MutationObserver((mutations) => {
+
for (const record of mutations) {
+
for (const node of record.addedNodes) {
+
const item = node as HTMLElement;
+
if ((item as HTMLElement).classList.contains('itemCardCont')) {
+
const link: HTMLElement = item.getElementsByTagName('a')[0]!;
+
if (owns(link)) {
+
addTag(item as HTMLElement);
+
};
+
}
};
};
});
+9
entrypoints/store.content/index.ts
···
+
import { getUserDetails } from "@/utils/utilities";
import { preferences } from "@/utils/storage";
import * as discovery from "./discovery";
import * as view from "./view";
···
if (!window.location.pathname.split('/')[2]) {
// Discovery
if (values.irlBrickPrice.enabled) discovery.irlBrickPrice();
+
+
if (values.storeOwnedTags.enabled) {
+
getUserDetails()
+
.then((user) => {
+
if (!user) return;
+
discovery.ownedTags(user.userId);
+
});
+
}
} else {
// View
if (values.irlBrickPrice.enabled) view.irlBrickPrice();
+1 -1
public/json/preferences.json
···
{
"name": "Hide Notification Badges",
"desc": "Hide the annoying red circles on the sidebar!",
-
"setting": "hideNotifications",
+
"setting": "hideNotificationBadges",
"tags": ["social"]
},
{
+21 -8
utils/api.ts
···
return res;
};
-
export async function iterate(type: "public" | "internal", path: string, startPage: number, endingPage?: number): Promise<any> {
+
export async function iterate(
+
type: "public" | "internal",
+
path: string,
+
keys: {
+
data: string,
+
metadata: string
+
} | null,
+
startPage: number,
+
endingPage?: number
+
): Promise<any> {
let res: Array<any> = [];
-
const firstPage: {
-
meta: Record<string, any>,
-
data: Array<any>
-
} = await (await fetch(config.api.urls[type] + path + 1)).json();
-
res.push(...firstPage.data);
+
const firstPage: { [key: string]: any } = await (await fetch(config.api.urls[type] + path + 1)).json();
+
+
if (!keys) {
+
keys = {
+
data: "data",
+
metadata: "meta"
+
};
+
};
+
res.push(...firstPage[keys.data]);
-
if (!endingPage) endingPage = firstPage.meta.lastPage;
+
if (!endingPage) endingPage = firstPage[keys.metadata].lastPage;
for (let index = startPage + 1; index < endingPage!; index++) {
const page = await (await fetch(config.api.urls[type] + path + index)).json();
-
res.push(...page.data);
+
res.push(...page[keys.data]);
};
return res;
};
+2 -1
utils/storage.ts
···
forumMentions: { enabled: true },
improvedFriendLists: { enabled: true },
irlBrickPrice: { enabled: true },
-
hideNotificationBadges: { enabled: false }
+
hideNotificationBadges: { enabled: false },
+
storeOwnedTags: { enabled: true }
}
export type preferencesSchema = typeof defaultPreferences & {
+23 -3
utils/utilities.ts
···
const cacheStorage: cacheInterface = await cache.getValue();
const metadata = await cache.getMeta() as { [key: string]: number; };
-
if (!cacheStorage[key] || forceReplenish || (Date.now() - metadata[key] >= expiry)) {
-
console.info('[Poly+] "' + key + '" cache is stale replenishing...');
+
const overlap = Date.now() - metadata[key];
+
if (!cacheStorage[key] || forceReplenish || (overlap >= expiry)) {
+
console.info('[Poly+] "' + key + '" cache is stale replenishing...', timeAgo(overlap));
const replenishedCache = await replenish();
cacheStorage[key] = replenishedCache;
···
const metadata = await cache.getMeta() as { [key: string]: number };
metadata[key] = 0;
cache.setMeta(metadata);
-
}
+
};
+
+
function timeAgo(overlap: number) {
+
const units = [
+
{ label: 'day', value: 24 * 60 * 60 * 1000 },
+
{ label: 'hour', value: 60 * 60 * 1000 },
+
{ label: 'min', value: 60 * 1000 },
+
{ label: 'sec', value: 1000 },
+
];
+
+
for (const { label, value } of units) {
+
const count = Math.floor(overlap / value);
+
if (count > 0) {
+
return `${count} ${label}${count > 1 ? 's' : ''} ago`;
+
}
+
overlap %= value;
+
}
+
+
return 'just now';
+
};
export async function getUserDetails() {
const profileLink: HTMLLinkElement = document.querySelector('.navbar a.text-reset[href^="/users/"]')!;