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