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
1const SaveBtn = document.getElementById('Save'); 2const Elements = Array.from(document.getElementsByClassName('setting-container')); 3 4var RecentSave; 5var Settings; 6 7var Utilities; 8(async () => { 9 Utilities = await import(chrome.runtime.getURL('resources/utils.js')); 10 Utilities = Utilities.default; 11 LoadCurrent(); 12 13 document.getElementById('PinnedGames-limit').innerText = Utilities.Limits.PinnedGames; 14 //document.getElementById('ImprovedFrLists-limit').innerText = Utilities.Limits.ImprovedFrLists; 15 //document.getElementById('ItemWishlist-limit').innerText = Utilities.Limits.ItemWishlist; 16 document.getElementById('HoardersList-pageLimit').innerText = Utilities.Limits.HoardersListPages; 17})(); 18 19// Handle buttons at the bottom of the page 20document.getElementById('ResetDefaults').addEventListener('click', function () { 21 document.getElementById('ResetDefaults-Modal').showModal(); 22}); 23SaveBtn.addEventListener('click', Save); 24 25// Handle modal buttons for Reset Defaults modal 26document.getElementById('ResetDefaults-Modal-Yes').addEventListener('click', function () { 27 Settings = Utilities.DefaultSettings; 28 Save(); 29 setTimeout(function () { 30 LoadCurrent(); 31 document.getElementById('ResetDefaults-Modal').close(); 32 }, 400); 33}); 34document.getElementById('ResetDefaults-Modal-No').addEventListener('click', function () { 35 document.getElementById('ResetDefaults-Modal').close(); 36}); 37 38// Loop thru each setting container and handle toggling, selecting, opening modal, etc 39Elements.forEach((element) => { 40 let Button = element.getElementsByClassName('toggle-btn')[0]; 41 let Options = element.getElementsByClassName('options-btn')[0]; 42 let Select = element.getElementsByTagName('select')[0]; 43 let Checkbox = element.getElementsByTagName('input'); 44 45 if (Button) { 46 Button.addEventListener('click', function () { 47 SetSetting(Button, 'bool'); 48 }); 49 } 50 51 if (Select) { 52 Select.addEventListener('change', function () { 53 if (Select.getAttribute('data-useValue') !== null) { 54 let Value = Select.options[Select.selectedIndex].value; 55 if (!isNaN(Value)) { 56 Value = parseInt(Value); 57 } 58 SetSetting(Select, Value, false); 59 } else { 60 SetSetting(Select, Select.selectedIndex, false); 61 } 62 }); 63 } 64 65 if (Checkbox) { 66 Array.from(Checkbox).forEach(check => { 67 check.addEventListener('change', function () { 68 SetSetting(check, check.checked, false); 69 }); 70 }) 71 } 72 73 if (Options) { 74 const Modal = document.getElementById(Options.getAttribute('data-modal') + '-Modal'); 75 const ModalButtons = Modal.getElementsByTagName('button'); 76 const ModalInputs = Modal.getElementsByTagName('input'); 77 const ModalSelect = Modal.getElementsByTagName('select'); 78 79 Options.addEventListener('click', function () { 80 Array.from(ModalButtons) 81 .filter((x) => !x.classList.contains('ignore')) 82 .forEach((button) => { 83 button.addEventListener('click', function () { 84 const Setting = button.getAttribute('data-setting'); 85 86 if (Setting === '[save]') { 87 // Save Modal Button 88 89 // Save Modal Inputs 90 Array.from(ModalInputs) 91 .filter((x) => !x.classList.contains('ignore')) 92 .forEach((input) => { 93 SetSetting(input, input.value, false, Modal.getAttribute('data-setting')); 94 }); 95 96 // Save Modal Select Menus 97 Array.from(ModalSelect) 98 .filter((x) => !x.classList.contains('ignore')) 99 .forEach((select) => { 100 SetSetting(select, select.selectedIndex, false, Modal.getAttribute('data-setting')); 101 }); 102 103 Save(); 104 setTimeout(function () { 105 LoadCurrent(); 106 Modal.close(); 107 }, 400); 108 } else if (Setting === '[reset-default]') { 109 // Reset to Defaults Modal Button 110 111 if (confirm("Are you sure you'd like to reset these options to their defaults?") === true) { 112 Settings[Modal.getAttribute('data-setting')] = Utilities.DefaultSettings[Modal.getAttribute('data-setting')]; 113 Save(); 114 Modal.close(); 115 } 116 } else if (Setting === '[cancel]') { 117 // Cancel Changes Button 118 119 Modal.close(); 120 } else { 121 // Default Toggle Button 122 123 SetSetting(button, 'bool', false, Modal.getAttribute('data-setting')); 124 } 125 }); 126 }); 127 128 Array.from(ModalInputs) 129 .filter((x) => !x.classList.contains('ignore')) 130 .forEach((input) => { 131 const Status = GetSettingValue(input, Modal.getAttribute('data-setting')); 132 if (Status !== 'undefined' && Status !== undefined) { 133 input.value = Status; 134 } else { 135 input.value = ''; 136 } 137 }); 138 139 Array.from(ModalSelect) 140 .filter((x) => !x.classList.contains('ignore')) 141 .forEach((select) => { 142 const Status = GetSettingValue(select, Modal.getAttribute('data-setting')); 143 if (Status !== 'undefined' && Status !== undefined) { 144 select.selectedIndex = Status; 145 } 146 }); 147 148 Modal.showModal(); 149 }); 150 } 151}); 152 153function LoadCurrent() { 154 chrome.storage.sync.get(['PolyPlus_Settings'], function (result) { 155 Settings = Utilities.MergeObjects(result.PolyPlus_Settings || Utilities.DefaultSettings, Utilities.DefaultSettings); 156 RecentSave = structuredClone(Settings) 157 158 console.log('Current Settings: ', Settings); 159 160 Elements.forEach((element) => { 161 UpdateElementState(element); 162 }); 163 }); 164} 165 166function SetSetting(element, value, update, modalParent) { 167 document.title = '*unsaved | Poly+ Settings' 168 const name = element.getAttribute('data-setting'); 169 let parent = element.getAttribute('data-parent'); 170 171 if (modalParent !== undefined) { 172 console.log(modalParent); 173 parent = modalParent; 174 } 175 176 if (value === 'bool') { 177 value = !GetSettingValue(element, modalParent); 178 } 179 if (parent !== null) { 180 let Parent = Object.values(Settings)[Object.keys(Settings).indexOf(parent)]; 181 if (!isNaN(element.getAttribute('data-parent')) && element.getAttribute('data-parent') !== null) { 182 Parent = Parent[parseInt(element.getAttribute('data-parent'))]; 183 } 184 Parent[name] = value; 185 } else { 186 Settings[name] = value; 187 } 188 if (update !== false) { 189 UpdateElementState(document.querySelector(`.setting-container:has([data-setting="${name}"])${parent !== null ? `:has([data-parent="${parent}"])` : ''}`), value); 190 } 191 if (SaveBtn.getAttribute('disabled')) { 192 SaveBtn.removeAttribute('disabled'); 193 194 // Handle leaving the settings page before saving 195 window.onbeforeunload = function (e) { 196 return "Are you sure you'd like to leave? Your Poly+ settings haven't been saved." 197 }; 198 } 199 /* 200 if (AreIdentical(Settings, RecentSave) === true) { 201 document.title = 'Poly+ Settings' 202 SaveBtn.disabled = true 203 } 204 */ 205} 206 207function GetSettingValue(element, modalParent) { 208 const name = element.getAttribute('data-setting'); 209 let parent = element.getAttribute('data-parent'); 210 211 if (modalParent !== undefined) { 212 parent = modalParent; 213 } 214 215 let Status = name; 216 if (parent !== null) { 217 let Parent = Object.values(Settings)[Object.keys(Settings).indexOf(parent)]; 218 if (!isNaN(element.getAttribute('data-parent')) && element.getAttribute('data-parent') !== null) { 219 Parent = Parent[parseInt(element.getAttribute('data-parent'))]; 220 Status = Parent[name]; 221 } else { 222 Status = Object.values(Parent)[Object.keys(Parent).indexOf(name)]; 223 } 224 } else { 225 Status = Settings[Status]; 226 } 227 228 if (element.tagName === 'SELECT' && element.getAttribute('data-useValue') === 'true') { 229 Status = Array.from(element.children).indexOf(element.querySelector('option[value="' + Status + '"]')) 230 } 231 232 return Status; 233} 234 235function UpdateElementState(element, status) { 236 const Button = element.getElementsByClassName('toggle-btn')[0]; 237 238 if (status === undefined) { 239 status = GetSettingValue(Button); 240 } 241 242 if (status === true) { 243 element.classList.add('enabled'); 244 element.classList.remove('disabled'); 245 Button.innerText = 'Disable'; 246 Button.classList.add('btn-danger'); 247 Button.classList.remove('btn-success'); 248 } else { 249 element.classList.add('disabled'); 250 element.classList.remove('enabled'); 251 Button.innerText = 'Enable'; 252 Button.classList.add('btn-success'); 253 Button.classList.remove('btn-danger'); 254 } 255 256 let SelectInput = element.getElementsByTagName('select')[0]; 257 if (SelectInput) { 258 SelectInput.selectedIndex = GetSettingValue(SelectInput); 259 } 260 261 let Checkbox = Array.from(element.getElementsByTagName('input')); 262 if (Checkbox.length > 0) { 263 Checkbox.forEach((check) => { 264 check.checked = GetSettingValue(check); 265 }); 266 } 267} 268 269function Save() { 270 document.title = 'Poly+ Settings'; 271 SaveBtn.setAttribute('disabled', 'true'); 272 chrome.storage.sync.set({PolyPlus_Settings: Settings}, function () { 273 console.log('Saved successfully!'); 274 RecentSave = Settings 275 }); 276 277 // Handle leaving the settings page after saving 278 window.onbeforeunload = null 279 280 console.log('Save:', Settings); 281} 282 283let LoadThemeFromJSONBtn = document.getElementById('LoadThemeFromJSONBtn'); 284let SaveThemeToJSONInput = document.getElementById('SaveThemeToJSONInput'); 285let CopyThemeJSONBtn = document.getElementById('CopyThemeJSONBtn'); 286LoadThemeFromJSONBtn.addEventListener('click', function () { 287 LoadThemeJSON(LoadThemeFromJSONBtn.previousElementSibling.value); 288}); 289document 290 .getElementById('theme-creator') 291 .getElementsByTagName('button')[1] 292 .addEventListener('click', function () { 293 SaveThemeToJSONInput.value = JSON.stringify(Settings.ThemeCreator); 294 }); 295CopyThemeJSONBtn.addEventListener('click', function () { 296 if (SaveThemeToJSONInput.value.length > 0) { 297 navigator.clipboard 298 .writeText(SaveThemeToJSONInput.value) 299 .then(() => { 300 alert('Successfully copied theme data to clipboard!'); 301 }) 302 .catch(() => { 303 alert('Failure to copy theme data to clipboard.'); 304 }); 305 } 306}); 307 308function LoadThemeJSON(string) { 309 try { 310 let JSONTable = JSON.parse(string); 311 if (JSONTable.length === Utilities.DefaultSettings.ThemeCreator.length) { 312 if (confirm("Are you sure you'd like to replace this theme with the theme specified in the JSON?") === true) { 313 LoadThemeFromJSONBtn.previousElementSibling.value = ''; 314 document.getElementById('ThemeCreator-Modal').close(); 315 for (let i = 0; i < JSONTable.length; i++) { 316 if (JSONTable[i][0] !== '#') { 317 JSONTable[i] = ''; 318 } 319 } 320 Settings.ThemeCreator = Utilities.MergeObjects(JSONTable, Utilities.DefaultSettings.ThemeCreator); 321 Save(); 322 console.log(JSONTable.length, JSONTable, 'applied'); 323 document.getElementById('ThemeCreator').getElementsByTagName('button')[1].click(); 324 } 325 } else { 326 alert('JSON is not a theme!'); 327 } 328 } catch (error) { 329 alert('JSON is invalid!'); 330 } 331} 332 333chrome.storage.sync.get(['PolyPlus_AutoAds'], function(result){ 334 let AutoAds = result.PolyPlus_AutoAds || []; 335 336 const Modal = document.getElementById("AutoAdBidding-Modal") 337 const AddButton = document.getElementById('auto-ad-bidding-add') 338 339 AddButton.addEventListener('click', async function() { 340 const Page = new DOMParser().parseFromString((await (await fetch('https://polytoria.com/create/ad/' + AddButton.previousElementSibling.value)).text()), 'text/html') 341 342 343 }) 344 345 const AddRow = function(index, info) { 346 const Row = document.createElement('tr') 347 Row.innerHTML = ` 348 <th scope="row">${index+1}</th> 349 <td><a href="https://polytoria.com/create/ad/${info.id}">"${info.name}"</a></td> 350 <td class="text-success"><span class="pi">$</span> 150</td> 351 <td> 352 <select class="form-select ignore"> 353 <option value="daily" selected>Daily</option> 354 <option value="weekly">Weekly</option> 355 <option value="monthly">Monthly</option> 356 </select> 357 </td> 358 <td> 359 <div role="group" class="btn-group w-100"> 360 <button class="btn btn-success w-25"> 361 BID 362 </button> 363 <button class="btn btn-orange w-25"> 364 EDIT 365 </button> 366 <button class="btn btn-danger w-25"> 367 DELETE 368 </button> 369 </div> 370 </td> 371 ` 372 } 373}) 374 375function AreIdentical(obj1, obj2) { 376 if (obj1.length !== obj2.length) { return false } 377 return JSON.stringify(obj1) === JSON.stringify(obj2) 378} 379 380function FormatBool(bool) { 381 if (bool === true) { 382 return 'enabled'; 383 } else { 384 return 'disabled'; 385 } 386} 387 388const Manifest = chrome.runtime.getManifest(); 389let BuildType = 'Stable'; 390if (Manifest.version_name !== undefined) { 391 BuildType = 'Pre-Release'; 392} 393 394const FooterText = document.getElementById('footer-text'); 395FooterText.children[0].innerHTML = `Version: v${Manifest.version} | Build Type: ${BuildType}`; 396 397const CheckForUpdatesButton = document.getElementById('check-for-updates'); 398function CheckForUpdates() { 399 CheckForUpdatesButton.removeEventListener('click', CheckForUpdates); 400 CheckForUpdatesButton.disabled = true; 401 fetch('https://polyplus.vercel.app/data/version.json') 402 .then((response) => { 403 if (!response.ok) { 404 throw new Error('Network not ok'); 405 } 406 return response.json(); 407 }) 408 .then((data) => { 409 if (data.version === Manifest.version || Math.floor((data.version - Manifest.version) * 10) === 0) { 410 CheckForUpdatesButton.innerHTML = '<b>No updates available</b>'; 411 alert('No updates available'); 412 } else { 413 const NumberOfUpdatesAvailable = Math.floor((data.version - Version) * 10); 414 CheckForUpdatesButton.innerHTML = '<b>' + NumberOfUpdatesAvailable + ' update(s) available</b>'; 415 alert(NumberOfUpdatesAvailable + ' updates available'); 416 } 417 }) 418 .catch((error) => { 419 console.log(error); 420 }); 421} 422CheckForUpdatesButton.addEventListener('click', CheckForUpdates); 423 424/* 425fetch(chrome.runtime.getURL('resources/currencies.json')) 426 .then((response) => { 427 if (!response.ok) { 428 throw new Error('Network not ok'); 429 } 430 return response.json(); 431 }) 432 .then((data) => { 433 const DateText = new Date(data.Date).toLocaleDateString('en-US', {day: 'numeric', month: 'long', year: 'numeric'}); 434 document.getElementById('IRLPriceWithCurrency-Date').innerText = DateText; 435 }) 436 .catch((error) => { 437 console.log(error); 438 }); 439*/ 440 441chrome.storage.local.get(['PolyPlus_OutOfDate', 'PolyPlus_LiveVersion', 'PolyPlus_ReleaseNotes', 'PolyPlus_SkipUpdate'], function (result) { 442 const OutOfDate = result.PolyPlus_OutOfDate || false; 443 const SkipUpdate = result.PolyPlus_SkipUpdate || null; 444 const LiveVersion = result.PolyPlus_LiveVersion || Manifest.version; 445 if (OutOfDate === true && SkipUpdate !== LiveVersion) { 446 const Banner = document.createElement('div'); 447 Banner.classList = 'alert position-sticky p-3'; 448 Banner.style = 'top: 30px; box-shadow: 0 0 20px 2px #000; z-index: 2000; background: rgb(163 39 39);'; 449 Banner.innerHTML = ` 450 <b>New Update Available!</b> 451 <br> 452 Your Poly+ installation is out of date! If you would like to get the latest and greatest features, improvements, and bug fixes click on one of the links below to dismiss this banner! 453 <br> 454 <div role="group" class="btn-group w-100 mt-2"> 455 <a href="${result.PolyPlus_ReleaseNotes}" class="btn btn-primary btn-sm w-25" target="_blank">Go to Release Notes</a> 456 <button id="skip-this-update" class="btn btn-warning btn-sm w-25">(Not Recommended) Skip this Update</button> 457 </div> 458 `; 459 document.getElementById('page').insertBefore(Banner, document.getElementById('page').children[1]); 460 461 const SkipButton = document.getElementById('skip-this-update'); 462 SkipButton.addEventListener('click', function () { 463 Banner.remove(); 464 chrome.storage.local.set({PolyPlus_SkipUpdate: result.PolyPlus_LiveVersion}, function () { 465 console.log('set skip update to live version: ', result.PolyPlus_LiveVersion); 466 }); 467 }); 468 } 469});