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}