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: work in progress best friends recreation

Index b580c60e c756333a

+2 -1
README.md
···
# PolyPlus-Rewrite
-
A WXT, Typescript, cleaner code quality version of Poly+.
+
+
A WXT, Typescript, cleaner code quality version of Poly+.
+55 -7
entrypoints/home.content.ts
···
-
import { preferences, _favoritedPlaces, _bestFriends } from "@/storage";
-
import { pullCache } from "@/utilities";
+
import { preferences, _favoritedPlaces, _bestFriends } from "@/utils/storage";
+
import { placeApiSchema, userApiSchema } from "@/utils/types";
+
import { pullCache } from "@/utils/utilities";
export default defineContentScript({
matches: ['https://polytoria.com/', 'https://polytoria.com/home'],
···
.then((values) => {
console.log('Poly+ loaded!')
if (values.favoritedPlaces) favoritedPlaces();
-
if (values.bestFriends) console.log('do best friends');
+
if (values.bestFriends) bestFriends();
});
}
});
···
column.insertBefore(container, column.children[1]);
}
-
// fix cannot convert void to placeApiSchema eventually lol
-
//@ts-ignore
-
const placeData = await pullCache('favoritedPlaces', async () => {
+
const placeData: Array<placeApiSchema> = await pullCache('favoritedPlaces', async () => {
const res: any = {}
for (let id of places as number[]) {
const info = (await (await fetch('https://api.polytoria.com/v1/places/' + id)).json())
res[id] = info
}
return res
-
});
+
}, false);
const card = container.getElementsByClassName('scrollFadeContainer')[0]
for (let i = 0; i < places.length; i++) {
···
card.classList.add('d-flex');
Array.from(card.children).forEach((place) => {place.classList.remove('d-none')});
});
+
}
+
+
function bestFriends() {
+
const friendsRow = document.querySelector('.card:has(.friendsPopup) .d-flex')!;
+
+
const createHeadshot = async function(id: number) {
+
const user: userApiSchema = (await (await fetch('https://api.polytoria.com/v1/users/' + id)).json());
+
const headshot = document.createElement('div');
+
+
// ? surely a better way to do this but who cares
+
headshot.classList.add('friend-circle');
+
headshot.setAttribute('data-user-id', id.toString());
+
headshot.setAttribute('data-username', user.username);
+
headshot.setAttribute('data-is-online', 'false');
+
headshot.setAttribute('data-location', 'offline');
+
+
headshot.innerHTML = `
+
<img width="90" height="auto" src="${user.thumbnail.icon}" alt="${user.username}" class="img-fluid rounded-circle border border-2 ">
+
<div class="friend-name text-truncate mt-1">
+
<div style="font-size: 0.5rem; line-height: 0.5rem; display: inline-block;">
+
<span class="text-muted">
+
<i class="fas fa-dot-circle"></i>
+
</span>
+
</div>
+
${user.username}
+
</div>
+
`;
+
+
friendsRow.prepend(headshot);
+
return headshot;
+
};
+
+
_bestFriends.getValue()
+
.then(async (friends) => {
+
const userData = await pullCache('bestFriends', async () => {
+
const res: any = {}
+
for (let id of friends as number[]) {
+
const info = (await (await fetch('https://api.polytoria.com/v1/users/' + id)).json())
+
res[id] = info
+
}
+
return res
+
}, false);
+
+
for (const id of friends) {
+
let headshot = document.getElementById('friend-' + id);
+
if (!headshot) headshot = await createHeadshot(id);
+
friendsRow.prepend(headshot, friendsRow.children[0]);
+
};
+
});
}
+1 -1
storage.ts utils/storage.ts
···
// Sync
export const preferences = storage.defineItem('sync:preferences', { fallback: defaultPreferences });
export const _favoritedPlaces = storage.defineItem('sync:favoritedPlaces', { fallback: [9656] });
-
export const _bestFriends = storage.defineItem('sync:bestFriends', { fallback: [] });
+
export const _bestFriends = storage.defineItem('sync:bestFriends', { fallback: [2782] });
// Cache
export const cache = storage.defineItem('local:cache', {fallback: {
-23
types.ts
···
-
export interface placeApiSchema {
-
id: number,
-
name: string,
-
description: string,
-
creator: object,
-
thumbnail: string,
-
genre: string,
-
maxPlayers: number,
-
isActive: boolean,
-
visits: number,
-
uniqueVisits: number,
-
playing: number,
-
rating: object,
-
accessType: string,
-
accessPrice: number|null,
-
createdAt: string,
-
updatedAt: string
-
}
-
-
export interface cacheInterface {
-
favoritedPlaces: number[],
-
[key: string]: any;
-
}
-35
utilities.ts
···
-
import { cache } from "./storage";
-
import { cacheInterface } from "./types";
-
-
// maybe make this use Meta data on an actual container ?
-
export async function pullCache(key: string, replenish: Function, forceReplenish: boolean) {
-
const cacheStorage: cacheInterface = await cache.getValue();
-
console.log('cache: ', cacheStorage)
-
if (!cacheStorage[key] || forceReplenish) {
-
const replenishedCache = await replenish();
-
cacheStorage[key] = replenishedCache;
-
//@ts-ignore: why
-
cache.setValue(cacheStorage);
-
console.log('replenished cache: ', replenishedCache)
-
return replenishedCache;
-
}
-
return cacheStorage[key];
-
}
-
-
// mergeObjects function was written by ChatGPT cause I was lazy and it was a while ago
-
// type inferences set to any because I am again, lazy
-
export function mergeObjects(obj1: any, obj2: any) {
-
var mergedObj: any = {};
-
for (var key in obj1) {
-
mergedObj[key] = obj1[key];
-
}
-
-
// Merge the values from obj2 into the mergedObj, favoring obj2 for non-existing keys in obj1
-
for (var key in obj2) {
-
if (!obj1.hasOwnProperty(key)) {
-
mergedObj[key] = obj2[key];
-
}
-
}
-
-
return mergedObj;
-
}
+44
utils/types.ts
···
+
export type userApiSchema = {
+
id: number,
+
username: string,
+
description: string,
+
signature: string,
+
thumbnail: {
+
avatar: string,
+
icon: string
+
},
+
playing: null|{},
+
netWorth: number,
+
placeVisits: number,
+
profileViews: number,
+
forumPosts: number,
+
assetSales: number,
+
membershipType: string,
+
isStaff: boolean,
+
registeredAt: string,
+
lastSeenAt: string
+
}
+
+
export type placeApiSchema = {
+
id: number,
+
name: string,
+
description: string,
+
creator: object,
+
thumbnail: string,
+
genre: string,
+
maxPlayers: number,
+
isActive: boolean,
+
visits: number,
+
uniqueVisits: number,
+
playing: number,
+
rating: object,
+
accessType: string,
+
accessPrice: number|null,
+
createdAt: string,
+
updatedAt: string
+
}
+
+
export interface cacheInterface {
+
favoritedPlaces: number[],
+
[key: string]: any;
+
}
+48
utils/utilities.ts
···
+
import { cache } from "./storage";
+
import { cacheInterface } from "./types";
+
import * as currencyPackages from "./currencyPackages.json"
+
+
// ? maybe make this use Meta data on an actual container
+
export async function pullCache(key: string, replenish: Function, forceReplenish: boolean) {
+
const cacheStorage: cacheInterface = await cache.getValue();
+
if (!cacheStorage[key] || forceReplenish) {
+
const replenishedCache = await replenish();
+
cacheStorage[key] = replenishedCache;
+
//@ts-ignore: why
+
cache.setValue(cacheStorage);
+
return replenishedCache;
+
}
+
return cacheStorage[key];
+
}
+
+
export async function getUserDetails() {
+
const profileLink: HTMLLinkElement = document.querySelector('.navbar a.text-reset[href^="/users/"]')!;
+
const brickBalance = document.getElementsByClassName('brickBalanceCont')[0];
+
+
if (!profileLink || !brickBalance) return null;
+
return {
+
username: profileLink.innerText.trim(),
+
userId: parseInt(profileLink.href.split('/')[4]),
+
bricks: parseInt(brickBalance.textContent!.replace(/,/g, ""))
+
}
+
}
+
+
export function bricksToCurrency(bricks: number, currency: string): string | null {
+
const _currencyPackages = currencyPackages as Record<string, Array<Array<number>>>;
+
const packages = _currencyPackages[currency];
+
+
if (!packages) {
+
console.warn('[Poly+] Missing currency package data for selected currency!');
+
return null;
+
}
+
+
let totalValue = 0;
+
for (const [currencyValue, bricksValue] of packages) {
+
while (bricks >= bricksValue) {
+
bricks -= bricksValue;
+
totalValue += currencyValue;
+
}
+
}
+
+
return `~${totalValue.toFixed(2)} ${currency}`;
+
}