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 { preferences, _favoritedPlaces, _bestFriends } from "@/utils/storage";
2import { placeApiSchema, userApiSchema } from "@/utils/types";
3import { pullCache } from "@/utils/utilities";
4
5export default defineContentScript({
6 matches: ['https://polytoria.com/', 'https://polytoria.com/home'],
7 main() {
8 preferences.getValue()
9 .then((values) => {
10 if (values.favoritedPlaces) favoritedPlaces();
11 if (values.bestFriends) bestFriends();
12 });
13 }
14});
15
16function favoritedPlaces() {
17 _favoritedPlaces.getValue()
18 .then(async (places) => {
19 const container = document.createElement('div');
20 container.innerHTML = `
21 <div class="row reqFadeAnim px-2 px-lg-0">
22 <div class="col">
23 <h6 class="dash-ctitle2">Jump right back into your favorite places</h6>
24 <h5 class="dash-ctitle">Favorited Places</h5>
25 </div>
26 </div>
27 <div class="card card-dash mcard mb-3">
28 <div class="card-body p-0 m-1 scrollFadeContainer">
29 <div class="text-center p-5">
30 <div class="spinner-border text-muted" role="status">
31 <span class="visually-hidden">Loading...</span>
32 </div>
33 </div>
34 </div>
35 </div>
36 `;
37
38 const column = document.getElementsByClassName('col-lg-8')[0];
39 if (document.getElementsByClassName('home-event-container')[0] === undefined) {
40 column.insertBefore(container, column.children[0]);
41 } else {
42 column.insertBefore(container, column.children[1]);
43 }
44
45 const placeData: Array<placeApiSchema> = await pullCache('favoritedPlaces', async () => {
46 const res: any = {}
47 for (let id of places as number[]) {
48 const info = (await (await fetch('https://api.polytoria.com/v1/places/' + id)).json())
49 res[id] = info
50 }
51 return res
52 }, false);
53
54 const card = container.getElementsByClassName('scrollFadeContainer')[0]
55 for (let i = 0; i < places.length; i++) {
56 const id = places.toSorted((a, b) => b - a)[i]
57 const details = placeData[id]
58
59 const scrollCard = document.createElement('a')
60 scrollCard.classList.value = 'd-none'
61 scrollCard.href = '/places/' + id
62 scrollCard.innerHTML = `
63 <div class="scrollFade card me-2 place-card force-desktop text-center mb-2" style="opacity: 1;">
64 <div class="card-body">
65 <div class="ratings-header">
66 <img src="${details.thumbnail}" class="place-card-image" style="position: relative;">
67 <div class="p+pinned_games_playing" style="position: absolute;background: linear-gradient(to bottom, #000000f7, transparent, transparent, transparent);width: 100%;height: 100%;top: 0;left: 0;border-radius: 15px;padding-top: 12px;color: gray;font-size: 0.8rem;">
68 <i class="fa-duotone fa-users"></i>
69 <span>
70 ${details.playing}
71 Playing
72 </span>
73 </div>
74 </div>
75 <div>
76 <div class="mt-2 mb-1 place-card-title">
77 ${details.name}
78 </div>
79 </div>
80 </div>
81 </div>
82 `
83
84 if (!details.isActive) {
85 const PlayerCountText = scrollCard.getElementsByClassName('p+pinned_games_playing')[0];
86 PlayerCountText.children[0].classList.value = 'text-warning fa-duotone fa-lock';
87 PlayerCountText.children[1].remove();
88 }
89
90 card.appendChild(scrollCard);
91 }
92 card.children[0].remove();
93 card.classList.add('d-flex');
94 Array.from(card.children).forEach((place) => {place.classList.remove('d-none')});
95 });
96}
97
98function bestFriends() {
99 const friendsRow = document.querySelector('.card:has(.friendsPopup) .d-flex')!;
100
101 const createHeadshot = async function(id: number) {
102 const user: userApiSchema = (await (await fetch('https://api.polytoria.com/v1/users/' + id)).json());
103 const headshot = document.createElement('div');
104
105 // ? surely a better way to do this but who cares
106 headshot.classList.add('friend-circle');
107 headshot.setAttribute('data-user-id', id.toString());
108 headshot.setAttribute('data-username', user.username);
109 headshot.setAttribute('data-is-online', 'false');
110 headshot.setAttribute('data-location', 'offline');
111
112 headshot.innerHTML = `
113 <img width="90" height="auto" src="${user.thumbnail.icon}" alt="${user.username}" class="img-fluid rounded-circle border border-2 ">
114 <div class="friend-name text-truncate mt-1">
115 <div style="font-size: 0.5rem; line-height: 0.5rem; display: inline-block;">
116 <span class="text-muted">
117 <i class="fas fa-dot-circle"></i>
118 </span>
119 </div>
120 ${user.username}
121 </div>
122 `;
123
124 friendsRow.prepend(headshot);
125 return headshot;
126 };
127
128 _bestFriends.getValue()
129 .then(async (friends) => {
130 const userData = await pullCache('bestFriends', async () => {
131 const res: any = {}
132 for (let id of friends as number[]) {
133 const info = (await (await fetch('https://api.polytoria.com/v1/users/' + id)).json())
134 res[id] = info
135 }
136 return res
137 }, false);
138
139 for (const id of friends) {
140 let headshot = document.getElementById('friend-' + id);
141 if (!headshot) headshot = await createHeadshot(id);
142 friendsRow.prepend(headshot, friendsRow.children[0]);
143 };
144 });
145}