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 { cache } from "./storage";
2import { cacheInterface } from "./types";
3import * as currencyPackages from "./currencyPackages.json"
4
5export async function pullCache(key: string, replenish: Function, expiry: number, forceReplenish: boolean) {
6 const cacheStorage: cacheInterface = await cache.getValue();
7 const metadata = await cache.getMeta() as { [key: string]: number; };
8
9 const overlap = Date.now() - metadata[key];
10 if (!cacheStorage[key] || forceReplenish || (overlap >= expiry)) {
11 console.info('[Poly+] "' + key + '" cache is stale replenishing...', timeAgo(overlap));
12
13 const replenishedCache = await replenish();
14
15 // Don't cache the response when the config file has APIs disabled
16 if (replenishedCache != "disabled") {
17 cacheStorage[key] = replenishedCache;
18 metadata[key] = Date.now();
19
20 cache.setValue(cacheStorage);
21 cache.setMeta(metadata);
22 } else {
23 return "disabled";
24 };
25 };
26
27 return cacheStorage[key];
28};
29
30export async function expireCache(key: string) {
31 console.info('[Poly+] Forcefully expiring "' + key + '" cache...');
32
33 const metadata = await cache.getMeta() as { [key: string]: number };
34 metadata[key] = 0;
35 cache.setMeta(metadata);
36};
37
38function timeAgo(overlap: number) {
39 const units = [
40 { label: 'day', value: 24 * 60 * 60 * 1000 },
41 { label: 'hour', value: 60 * 60 * 1000 },
42 { label: 'min', value: 60 * 1000 },
43 { label: 'sec', value: 1000 },
44 ];
45
46 for (const { label, value } of units) {
47 const count = Math.floor(overlap / value);
48 if (count > 0) {
49 return `${count} ${label}${count > 1 ? 's' : ''} ago`;
50 }
51 overlap %= value;
52 }
53
54 return 'just now';
55};
56
57export async function getUserDetails() {
58 const profileLink: HTMLLinkElement = document.querySelector('.navbar a.text-reset[href^="/users/"]')!;
59 const brickBalance = document.getElementsByClassName('brickBalanceCount')[0];
60
61 if (!profileLink || !brickBalance) return null;
62 return {
63 username: profileLink.innerText.trim(),
64 userId: parseInt(profileLink.href.split('/')[4]),
65 bricks: parseInt(brickBalance.textContent!.replace(/,/g, ""))
66 }
67};
68
69export function bricksToCurrency(bricks: number, currency: string): string | null {
70 if (isNaN(bricks) || bricks == 0) return null;
71
72 const _currencyPackages = currencyPackages as Record<string, Array<Array<number>>>;
73 const packages = _currencyPackages[currency].toSorted((a, b) => b[1] - a[1]);
74
75 if (!packages) {
76 console.warn('[Poly+] Missing currency package data for selected currency!');
77 return null;
78 }
79
80 let totalValue = 0;
81 for (const [currencyValue, bricksValue] of packages) {
82 while (bricks >= bricksValue) {
83 bricks -= bricksValue;
84 totalValue += currencyValue;
85 }
86 }
87
88 if (bricks > 0) {
89 const cheapestPackage = packages[packages.length - 1];
90 const [currencyValue, bricksValue] = cheapestPackage;
91 const unitPrice = currencyValue / bricksValue;
92 totalValue += bricks * unitPrice;
93 }
94
95 return `~${totalValue.toFixed(2)} ${currency}`;
96};