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}