import * as Sentry from "@sentry/browser" import { animate, spring, /* stagger */ } from 'motion' import loading, { AppendLoadingSymbol } from './seqta/ui/Loading' import updateVideo from 'url:./resources/update-video.mp4' import IconFamily from 'url:./resources/fonts/IconFamily.woff' import LogoLight from 'url:./resources/icons/betterseqta-light-icon.png' import LogoLightOutline from 'url:./resources/icons/betterseqta-light-outline.png' import icon48 from 'url:./resources/icons/icon-48.png' import Popup from 'url:./interface/index.html' import Color from 'color' import MenuitemSVGKey from './seqta/content/MenuItemSVGKey.json' import { MessageHandler } from './seqta/utils/MessageListener' import { SettingsState } from "./types/storage" import ShortcutLinks from './seqta/content/links.json' import Sortable from 'sortablejs' import StorageListener from './seqta/utils/StorageListener' import { appendBackgroundToUI } from './seqta/ui/ImageBackgrounds' import assessmentsicon from './seqta/icons/assessmentsIcon' import browser from 'webextension-polyfill' import coursesicon from './seqta/icons/coursesIcon' import { delay } from "./seqta/utils/delay" import { enableCurrentTheme } from './seqta/ui/Themes' import iframeCSS from "url:./css/iframe.scss" import { onError } from './seqta/utils/onError' import stringToHTML from './seqta/utils/stringToHTML' import { updateAllColors } from './seqta/ui/colors/Manager' import { updateBgDurations } from './seqta/ui/Animation' /* import injected from 'url:./css/injected.scss'; */ /* import documentLoad from 'url:./css/documentload.scss'; */ declare global { interface Window { chrome?: any } } export let isChrome = window.chrome let SettingsClicked = false export let MenuOptionsOpen = false let UserInitalCode = '' let currentSelectedDate = new Date() let LessonInterval: any export let DarkMode: boolean var MenuItemMutation = false var NonSEQTAPage = false var IsSEQTAPage = false document.addEventListener( 'load', async function () { CheckForMenuList() const hasSEQTAText = document.childNodes[1].textContent?.includes('Copyright (c) SEQTA Software') const hasSEQTATitle = document.title.includes('SEQTA Learn') if (hasSEQTAText && hasSEQTATitle && !IsSEQTAPage) { IsSEQTAPage = true console.log('[BetterSEQTA+] Verified SEQTA Page') import('./css/injected.scss') import('./css/documentload.scss') /* const injectedCSS = document.createElement('link') injectedCSS.setAttribute('rel', 'stylesheet') injectedCSS.setAttribute('type', 'text/css') injectedCSS.setAttribute('href', injected) */ /* const documentLoadCSS = document.createElement('link') documentLoadCSS.setAttribute('rel', 'stylesheet') documentLoadCSS.setAttribute('type', 'text/css') documentLoadCSS.setAttribute('href', documentLoad) */ /* document.head.appendChild(documentLoadCSS) */ /* document.head.appendChild(injectedCSS) */ enableCurrentTheme() try { const items = await browser.storage.local.get() as SettingsState main(items) } catch (error: any) { onError(error) } } if (!hasSEQTAText && !NonSEQTAPage) { NonSEQTAPage = true } }, true, ) function SetDisplayNone(ElementName: string) { return `li[data-key=${ElementName}]{display:var(--menuHidden) !important; transition: 1s;}` } function animbkEnable(item: any) { if (item.animatedbk) { CreateBackground() } else { RemoveBackground() document.getElementById('container')!.style.background = 'var(--background-secondary)' } } export async function HideMenuItems(): Promise { try { const result = await browser.storage.local.get() as SettingsState let stylesheetInnerText: string = '' for (const [menuItem, { toggle }] of Object.entries(result.menuitems)) { if (!toggle) { stylesheetInnerText += SetDisplayNone(menuItem) console.log(`[BetterSEQTA+] Hiding ${menuItem} menu item`) } } const menuItemStyle: HTMLStyleElement = document.createElement('style') menuItemStyle.innerText = stylesheetInnerText document.head.appendChild(menuItemStyle) } catch (error) { console.error("An error occurred:", error) } } function OpenWhatsNewPopup() { const background = document.createElement('div') background.id = 'whatsnewbk' background.classList.add('whatsnewBackground') const container = document.createElement('div') container.classList.add('whatsnewContainer') var header: any = stringToHTML(`

What's New

BetterSEQTA+ V${browser.runtime.getManifest().version}

`).firstChild let imagecont = document.createElement('div') imagecont.classList.add('whatsnewImgContainer') let video = document.createElement('video') let source = document.createElement('source') source.setAttribute('src', updateVideo) source.setAttribute('type', 'video/mp4') video.autoplay = true video.muted = true video.loop = true video.appendChild(source) video.classList.add('whatsnewImg') imagecont.appendChild(video) let textcontainer = document.createElement('div') textcontainer.classList.add('whatsnewTextContainer') let textheader: any = stringToHTML( '

DESIGN OVERHAUL

', ).firstChild textcontainer.append(textheader) let text = stringToHTML( String.raw`

3.2.2 - Minor Improvements

  • Added Settings open-close animation
  • Minor Bug Fixes
  • 3.2.0 - Custom Themes

  • Added transparency (blur) effects
  • Added custom themes
  • Added colour picker history
  • Heaps of bug fixes
  • 3.1.3 - Custom Backgrounds

  • Added custom backgrounds with support for images and videos
  • Overhauled topbar
  • New animated hamburger icon
  • Minor bug fixes
  • 3.1.2 - New settings menu!

  • Overhauled the settings menu
  • Added custom gradients
  • Added HEAPS of animations
  • Fixed a bug where shortcuts don't show up
  • Other minor bugs fixed
  • 3.1.1 - Minor Bug fixes

  • Fixed assessments overlapping
  • Fixed houses not displaying if they aren't a specific color
  • Fixed Chrome Webstore Link
  • 3.1.0 - Design Improvements

  • Minor UI improvements
  • Added Animation Speed Slider
  • Animation now enables and disables without reloading SEQTA
  • Changed logo
  • 3.0.0 - BetterSEQTA+ *Complete Overhaul*

  • Redesigned appearance
  • Upgraded to manifest V3 (longer support)
  • Fixed transitional glitches
  • Under the hood improvements
  • Fixed News Feed
  • 2.0.7 - Added support to other domains + Minor bug fixes

  • Fixed BetterSEQTA+ not loading on some pages
  • Fixed text colour of notices being unreadable
  • Fixed pages not reloading when saving changes
  • 2.0.2 - Minor bug fixes

  • Fixed indicator for current lesson
  • Fixed text colour for DM messages list in Light mode
  • Fixed user info text colour
  • Sleek New Layout

  • Updated with a new font and presentation, BetterSEQTA+ has never looked better.
  • New Updated Sidebar

  • Condensed appearance with new updated icons.
  • Independent Light Mode and Dark Mode

  • Dark mode and Light mode are now available to pick alongside your chosen Theme Colour. Your Theme Colour will now become an accent colour for the page. Light/Dark mode can be toggled with the new button, found in the top-right of the menu bar.
  • Create Custom Shortcuts

  • Found in the BetterSEQTA+ Settings menu, custom shortcuts can now be created with a name and URL of your choice.
  • `, ).firstChild let footer = stringToHTML( String.raw`
    Report bugs and feedback:
    `).firstChild let exitbutton = document.createElement('div') exitbutton.id = 'whatsnewclosebutton' container.append(header) container.append(imagecont) container.append(textcontainer) container.append(text as ChildNode) container.append(footer as ChildNode) container.append(exitbutton) background.append(container) document.getElementById('container')!.append(background) let bkelement = document.getElementById('whatsnewbk') let popup = document.getElementsByClassName('whatsnewContainer')[0] animate( [popup, bkelement as HTMLElement], { scale: [0, 1], opacity: [0, 1] }, { easing: spring({ stiffness: 220, damping: 18 }) } ) /* animate( '.whatsnewTextContainer *', { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.05, { start: 0.1 }), duration: 0.5, easing: [.22, .03, .26, 1] } ) */ browser.storage.local.remove(['justupdated']) bkelement!.addEventListener('click', function (event) { // Check if the click event originated from the element itself and not any of its children if (event.target === bkelement) { DeleteWhatsNew() } }); var closeelement = document.getElementById('whatsnewclosebutton') closeelement!.addEventListener('click', function () { DeleteWhatsNew() }) } async function finishLoad() { try { document.querySelector('.legacy-root')!.classList.remove('hidden') var loadingbk = document.getElementById('loading') loadingbk!.style.opacity = '0' await delay(501) loadingbk!.remove() } catch (err) { console.log(err) } const result = browser.storage.local.get(['justupdated']) function open (result: any) { if (result.justupdated && !document.getElementById('whatsnewbk')) { OpenWhatsNewPopup() } } result.then(open, onError) } async function DeleteWhatsNew() { const bkelement = document.getElementById('whatsnewbk') const popup = document.getElementsByClassName('whatsnewContainer')[0] animate( [popup, bkelement!], { opacity: [1, 0], scale: [1, 0] }, { easing: [.22, .03, .26, 1] } ).finished.then(() => { bkelement!.remove() }); } export function CreateBackground() { var bkCheck = document.getElementsByClassName('bg') if (bkCheck.length !== 0) { return } // Creating and inserting 3 divs containing the background applied to the pages var bklocation = document.getElementById('container') var menu = document.getElementById('menu') var bk = document.createElement('div') bk.classList.add('bg') bklocation!.insertBefore(bk, menu) var bk2 = document.createElement('div') bk2.classList.add('bg') bk2.classList.add('bg2') bklocation!.insertBefore(bk2, menu) var bk3 = document.createElement('div') bk3.classList.add('bg') bk3.classList.add('bg3') bklocation!.insertBefore(bk3, menu) } export function RemoveBackground() { var bk = document.getElementsByClassName('bg') var bk2 = document.getElementsByClassName('bg2') var bk3 = document.getElementsByClassName('bg3') if (bk.length == 0 || bk2.length == 0 || bk3.length == 0) return bk[0].remove() bk2[0].remove() bk3[0].remove() } export function waitForElm(selector: string) { return new Promise((resolve) => { const querySelector = () => document.querySelector(selector); if (querySelector()) { return resolve(querySelector()); } const observer = new MutationObserver(() => { if (querySelector()) { resolve(querySelector()); observer.disconnect(); } }); // 🛡️ Safety check: Ensure document.body is available if (document.body) { observer.observe(document.body, { childList: true, subtree: true, }); } else { // 🚨 Fallback: Wait for the document to be ready document.addEventListener('DOMContentLoaded', () => { observer.observe(document.body, { childList: true, subtree: true, }); }); } }); } export function GetCSSElement(file: string) { const cssFile = browser.runtime.getURL(file) const fileref = document.createElement('link') fileref.setAttribute('rel', 'stylesheet') fileref.setAttribute('type', 'text/css') fileref.setAttribute('href', cssFile) return fileref } function removeThemeTagsFromNotices () { // Grabs an array of the notice iFrames const userHTMLArray = document.getElementsByClassName('userHTML') // Iterates through the array, applying the iFrame css for (const item of userHTMLArray) { // Grabs the HTML of the body tag const item1 = item as HTMLIFrameElement const body = item1.contentWindow!.document.querySelectorAll('body')[0] if (body) { // Replaces the theme tag with nothing const bodyText = body.innerHTML body.innerHTML = bodyText.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, '').replace(/ +/, ' ') } } } async function updateIframesWithDarkMode(): Promise { // Load the CSS file to overwrite iFrame default CSS const cssLink = document.createElement('style') const cssContent = document.createTextNode(iframeCSS) cssLink.appendChild(cssContent) const observer = new MutationObserver(async (mutationsList) => { for (const mutation of mutationsList) { for (const node of mutation.addedNodes) { if (node.nodeName === 'IFRAME') { const iframe = node as HTMLIFrameElement try { const settings = await browser.storage.local.get('DarkMode') as SettingsState if (settings.DarkMode) { applyDarkModeToIframe(iframe, cssLink) } } catch (error) { console.error('Error applying dark mode:', error) } } } } }) if (document.body) { observer.observe(document.body, { childList: true, subtree: true, }); } else { document.addEventListener('DOMContentLoaded', () => { observer.observe(document.body, { childList: true, subtree: true, }); }); } } function applyDarkModeToIframe(iframe: HTMLIFrameElement, cssLink: HTMLStyleElement): void { const iframeDocument = iframe.contentDocument if (!iframeDocument) return const body = iframeDocument.body if (body && body.style.color !== 'white') { body.style.color = 'white' } const head = iframeDocument.head if (head && !head.innerHTML.includes('iframe.css')) { head.appendChild(cssLink) } } function SortMessagePageItems(messagesParentElement: any) { let filterbutton = document.createElement('div') filterbutton.classList.add('messages-filterbutton') filterbutton.innerText = 'Filter' let header = document.getElementsByClassName( 'MessageList__MessageList___3DxoC', )[0].firstChild as HTMLElement header.append(filterbutton) messagesParentElement /* const observer = new MutationObserver(function (mutations_list) { mutations_list.forEach(function (mutation) { mutation.addedNodes.forEach(function (added_node) { const node = added_node as HTMLElement if (node.dataset.message) { // Check if added_node.firstChild.title is in block list } }) }) }) observer.observe(messagesParentElement, { subtree: true, childList: true, }); */ } async function LoadPageElements(): Promise { await AddBetterSEQTAElements(true) const sublink: string | undefined = window.location.href.split('/')[4] async function handleNewsPage(): Promise { console.log('[BetterSEQTA+] Started Init') const settings: SettingsState = await browser.storage.local.get() as SettingsState if (settings.onoff) { SendNewsPage() const notificationSettings: SettingsState = await browser.storage.local.get() as SettingsState if (notificationSettings.notificationcollector) { enableNotificationCollector() } finishLoad() } } async function handleDefault(): Promise { finishLoad() const settings: SettingsState = await browser.storage.local.get() as SettingsState if (settings.notificationcollector) { enableNotificationCollector() } } switch (sublink) { case 'news': await handleNewsPage() break case 'home': case undefined: window.location.replace(`${location.origin}/#?page=/home`) LoadInit() break default: await handleDefault() break } const observer = new MutationObserver(function (mutations_list) { mutations_list.forEach(function (mutation) { mutation.addedNodes.forEach(function (added_node) { const node = added_node as HTMLElement if (node.classList.contains('messages')) { let element = document.getElementById('title')!.firstChild as HTMLElement element.innerText = 'Direct Messages' document.title = 'Direct Messages ― SEQTA Learn' SortMessagePageItems(added_node) } else if (node.classList.contains('notices')) { CheckNoticeTextColour(added_node) } }) }) }) observer.observe(document.querySelector('#main') as HTMLElement, { subtree: false, childList: true, }) } function CheckNoticeTextColour(notice: any) { const observer = new MutationObserver(function (mutations_list) { mutations_list.forEach(function (mutation) { mutation.addedNodes.forEach(function (added_node) { const node = added_node as HTMLElement const result = browser.storage.local.get(['DarkMode']) function open (result: any) { DarkMode = result.DarkMode if (node.classList.contains('notice')) { var hex = node.style.cssText.split(' ')[1] if (hex) { const hex1 = hex.slice(0,-1) var threshold = GetThresholdOfColor(hex1) if (DarkMode && threshold < 100) { node.style.cssText = '--color: undefined;' } } } } result.then(open, onError) }) }) }) observer.observe(notice, { subtree: true, childList: true, }) } export function tryLoad() { waitForElm('.login').then(() => { finishLoad() }) waitForElm('.day-container').then(() => { finishLoad() }) waitForElm('[data-key=welcome]').then((elm: any) => { elm.classList.remove('active') }) waitForElm('.code').then((elm: any) => { if (!elm.innerText.includes('BetterSEQTA')) LoadPageElements() }) // Waits for page to call on load, run scripts document.addEventListener( 'load', function () { updateIframesWithDarkMode() removeThemeTagsFromNotices() documentTextColor() }, true, ) const observer = new MutationObserver(() => { documentTextColor() }) observer.observe(document!, { attributes: true, childList: true, subtree: true, attributeFilter: ['td'], }) } function ChangeMenuItemPositions(storage: any) { let menuorder = storage var menuList = document.querySelector('#menu')!.firstChild!.childNodes let listorder = [] for (let i = 0; i < menuList.length; i++) { const menu = menuList[i] as HTMLElement let a = menuorder.indexOf(menu.dataset.key) listorder.push(a) } var newArr = [] for (var i = 0; i < listorder.length; i++) { newArr[listorder[i]] = menuList[i] } let listItemsDOM = document.getElementById('menu')!.firstChild for (let i = 0; i < newArr.length; i++) { const element = newArr[i] if (element) { const elem = element as HTMLElement elem.setAttribute('data-checked', 'true') listItemsDOM!.appendChild(element) } } } export async function ObserveMenuItemPosition() { const result = browser.storage.local.get() function open (result: any) { let menuorder = result.menuorder if (menuorder && result.onoff) { const observer = new MutationObserver(function (mutations_list) { mutations_list.forEach(function (mutation) { mutation.addedNodes.forEach(function (added_node) { const node = added_node as HTMLElement if (!node?.dataset?.checked && !MenuOptionsOpen) { const key = MenuitemSVGKey[node?.dataset?.key! as keyof typeof MenuitemSVGKey] if (key) { ReplaceMenuSVG( node, MenuitemSVGKey[node.dataset.key as keyof typeof MenuitemSVGKey], ) } ChangeMenuItemPositions(menuorder) } }) }) }) observer.observe(document.querySelector('#menu')!.firstChild!, { subtree: true, childList: true, }) } } result.then(open, onError) } function main(storedSetting: SettingsState) { // Handle undefined onoff setting if (typeof storedSetting.onoff === 'undefined') { browser.runtime.sendMessage({ type: 'setDefaultStorage' }) } if (storedSetting.telemetry && storedSetting.onoff) { Sentry.init({ dsn: "https://54bdb68e80b45182ded22ecf9fe9529c@o4506347383291904.ingest.sentry.io/4506347462393856", integrations: [ new Sentry.BrowserTracing({ // Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled tracePropagationTargets: ["localhost", /^https:\/\/yourserver\.io\/api/], }), new Sentry.Replay(), ], // Performance Monitoring tracesSampleRate: 1.0, // Capture 100% of the transactions // Session Replay replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production. replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur. }) } const handleDisabled = () => { waitForElm('.code').then(AppendElementsToDisabledPage) } if (storedSetting.onoff) { console.log('[BetterSEQTA+] Enabled') if (DarkMode) document.documentElement.classList.add('dark') document.querySelector('.legacy-root')?.classList.add('hidden') new StorageListener() new MessageHandler() updateAllColors(storedSetting) loading() InjectCustomIcons() HideMenuItems() CheckLoadOnPeriods() tryLoad() window.addEventListener('load', tryLoad) } else { handleDisabled() window.addEventListener('load', handleDisabled) } } function InjectCustomIcons() { console.log('[BetterSEQTA+] Injecting Icons') const style = document.createElement('style') style.setAttribute('type', 'text/css') style.innerHTML = ` @font-face { font-family: 'IconFamily'; src: url('${IconFamily}') format('woff'); font-weight: normal; font-style: normal; }` document.head.appendChild(style) } export function AppendElementsToDisabledPage() { AddBetterSEQTAElements(false) let settingsStyle = document.createElement('style') settingsStyle.innerText = ` .addedButton { position: absolute !important right: 50px width: 35px height: 35px padding: 6px !important overflow: unset !important border-radius: 50% margin: 7px !important cursor: pointer color: white !important } .addedButton svg { margin: 6px } .outside-container { top: 48px !important } #ExtensionPopup { border-radius: 1rem box-shadow: 0px 0px 20px -2px rgba(0, 0, 0, 0.6) transform-origin: 70% 0 } ` document.head.append(settingsStyle) } var PageLoaded = false async function CheckLoadOnPeriods() { if (!PageLoaded) { await delay(1000) var code = document.getElementsByClassName('code')[0] if (code && !UserInitalCode) { LoadPageElements() finishLoad() PageLoaded = true } if (!code) { CheckLoadOnPeriods() } } } export function closeSettings() { const ExtensionSettings = document.getElementById('ExtensionPopup')! const ExtensionIframe = document.getElementById('ExtensionIframe') as HTMLIFrameElement if (SettingsClicked == true) { ExtensionSettings!.classList.add('hide') animate( '#ExtensionPopup', { opacity: [1, 0], scale: [1, 0] }, { easing: spring({ stiffness: 220, damping: 18 }) } ) SettingsClicked = false if (ExtensionIframe.contentWindow) { ExtensionIframe.contentWindow.postMessage('popupClosed', '*') } } ExtensionSettings!.classList.add('hide') } function addExtensionSettings() { /* const link = GetCSSElement('src/interface/popup.css') document.querySelector('html')!.appendChild(link) */ const extensionPopup = document.createElement('div') extensionPopup.classList.add('outside-container', 'hide') extensionPopup.id = 'ExtensionPopup' document.body.appendChild(extensionPopup) const extensionIframe: HTMLIFrameElement = document.createElement('iframe') extensionIframe.src = `${Popup}#settings/embedded` extensionIframe.id = 'ExtensionIframe' extensionIframe.setAttribute('allowTransparency', 'true') extensionIframe.setAttribute('excludeDarkCheck', 'true') extensionIframe.style.width = '384px' extensionIframe.style.height = '600px' extensionIframe.style.border = 'none' extensionPopup.appendChild(extensionIframe) const container = document.getElementById('container') const closeExtensionPopup = () => { const ExtensionIframe = document.getElementById('ExtensionIframe') as HTMLIFrameElement extensionPopup.classList.add('hide') animate( '#ExtensionPopup', { opacity: [1, 0], scale: [1, 0] }, { easing: [.22, .03, .26, 1] } ) if (ExtensionIframe.contentWindow) { ExtensionIframe.contentWindow.postMessage('popupClosed', '*') } SettingsClicked = false } container!.onclick = (event) => { if ((event.target as HTMLElement).closest('#AddedSettings') == null && SettingsClicked) { closeExtensionPopup() } } } export function OpenMenuOptions() { const result = browser.storage.local.get() function open (result: any) { var container = document.getElementById('container') var menu = document.getElementById('menu') if (result.defaultmenuorder.length == '0') { let childnodes = menu!.firstChild!.childNodes let newdefaultmenuorder = [] for (let i = 0; i < childnodes.length; i++) { const element = childnodes[i] newdefaultmenuorder.push((element as HTMLElement).dataset.key) browser.storage.local.set({ defaultmenuorder: newdefaultmenuorder }) } } let childnodes = menu!.firstChild!.childNodes if (result.defaultmenuorder.length != childnodes.length) { for (let i = 0; i < childnodes.length; i++) { const element = childnodes[i] if (!result.defaultmenuorder.indexOf((element as HTMLElement).dataset.key)) { let newdefaultmenuorder = result.defaultmenuorder newdefaultmenuorder.push((element as HTMLElement).dataset.key) browser.storage.local.set({ defaultmenuorder: newdefaultmenuorder }) } } } MenuOptionsOpen = true let cover = document.createElement('div') cover.classList.add('notMenuCover') menu!.style.zIndex = '20' menu!.style.setProperty('--menuHidden', 'flex') container!.append(cover) let menusettings = document.createElement('div') menusettings.classList.add('editmenuoption-container') let defaultbutton = document.createElement('div') defaultbutton.classList.add('editmenuoption') defaultbutton.innerText = 'Restore Default' defaultbutton.id = 'restoredefaultoption' let savebutton = document.createElement('div') savebutton.classList.add('editmenuoption') savebutton.innerText = 'Save' savebutton.id = 'restoredefaultoption' menusettings.appendChild(defaultbutton) menusettings.appendChild(savebutton) menu!.appendChild(menusettings) let ListItems = menu!.firstChild!.childNodes for (let i = 0; i < ListItems.length; i++) { const element1 = ListItems[i] const element = element1 as HTMLElement (element as HTMLElement).classList.add('draggable'); if ((element as HTMLElement).classList.contains('hasChildren')) { (element as HTMLElement).classList.remove('active'); (element.firstChild as HTMLElement).classList.remove('noscroll'); } let MenuItemToggle = stringToHTML( `
    ` ).firstChild; (element as HTMLElement).append(MenuItemToggle!) if (!element.dataset.betterseqta) { const a = document.createElement('section') a.innerHTML = element.innerHTML cloneAttributes(a, element) menu!.firstChild!.insertBefore(a, element) element.remove() } } if (Object.keys(result.menuitems).length == 0) { menubuttons = menu!.firstChild!.childNodes var menuItems = {} for (var i = 0; i < menubuttons.length; i++) { var id = (menubuttons[i] as HTMLElement).dataset.key const element: any = {} element.toggle = true; (menuItems[id as keyof typeof menuItems] as any) = element; } browser.storage.local.set({ menuitems: menuItems }) } var menubuttons: any = document.getElementsByClassName('menuitem') const result1 = browser.storage.local.get(['menuitems']) function open (result: any) { var menuItems = result.menuitems let buttons = document.getElementsByClassName('menuitem') for (var i = 0; i < buttons.length; i++) { var id = buttons[i].id if (menuItems[id]) { (buttons[i] as HTMLInputElement).checked = menuItems[id].toggle } if (!menuItems[id]) { (buttons[i] as HTMLInputElement).checked = true } } } result1.then(open, onError) try { var el = document.querySelector('#menu > ul') var sortable = Sortable.create((el as HTMLElement), { draggable: '.draggable', dataIdAttr: 'data-key', animation: 150, easing: "cubic-bezier(.5,0,.5,1)", onEnd: function () { saveNewOrder(sortable) }, }); } catch (err) { console.log(err) } function changeDisplayProperty(element: any) { if (!element.checked) { element.parentNode.parentNode.style.display = 'var(--menuHidden)' } if (element.checked) { element.parentNode.parentNode.style.setProperty( 'display', 'flex', 'important', ) } } function StoreMenuSettings() { const menuItems: any = {} const menubuttons = menu!.firstChild!.childNodes const button = document.getElementsByClassName('menuitem') for (let i = 0; i < menubuttons.length; i++) { const id = (menubuttons[i] as HTMLElement).dataset.key const element: any = {} element.toggle = (button[i] as HTMLInputElement).checked menuItems[id as keyof typeof menuItems] = element } browser.storage.local.set({ menuitems: menuItems }) } for (let i = 0; i < menubuttons.length; i++) { const element = menubuttons[i] element.addEventListener('change', () => { element.parentElement.parentElement.getAttribute('data-key') StoreMenuSettings() changeDisplayProperty(element) }) } function closeAll() { console.log("Closing!") ListItems = menu!.firstChild!.childNodes menusettings.remove() cover.remove() MenuOptionsOpen = false menu!.style.setProperty('--menuHidden', 'none') for (let i = 0; i < ListItems.length; i++) { const element1 = ListItems[i] const element = element1 as HTMLElement element.classList.remove('draggable') element.setAttribute('draggable', 'false') if (!element.dataset.betterseqta) { const a = document.createElement('li') a.innerHTML = element.innerHTML cloneAttributes(a, element) menu!.firstChild!.insertBefore(a, element) element.remove() } } let switches = menu!.querySelectorAll('.onoffswitch') for (let i = 0; i < switches.length; i++) { switches[i].remove() } } cover.addEventListener('click', closeAll) savebutton.addEventListener('click', closeAll) defaultbutton.addEventListener('click', function () { const result = browser.storage.local.get() function open (response: any) { const options = response.defaultmenuorder browser.storage.local.set({ menuorder: options }) ChangeMenuItemPositions(options) for (let i = 0; i < menubuttons.length; i++) { const element = menubuttons[i] element.checked = true element.parentNode.parentNode.style.setProperty( 'display', 'flex', 'important', ) } saveNewOrder(sortable) } result.then(open, onError) }) } result.then(open, onError) } function saveNewOrder(sortable: any) { var order = sortable.toArray() browser.storage.local.set({ menuorder: order }) } function cloneAttributes(target: any, source: any) { [...source.attributes].forEach((attr) => { target.setAttribute(attr.nodeName, attr.nodeValue) }) } function ReplaceMenuSVG(element: HTMLElement, svg: string) { let item = element.firstChild as HTMLElement item!.firstChild!.remove() if (element.dataset.key == 'messages') { (element!.firstChild! as HTMLElement).innerText! = 'Direct Messages' } let newsvg = stringToHTML(svg).firstChild item.insertBefore((newsvg as Node), item.firstChild) } async function AddBetterSEQTAElements(toggle: any) { const code = document.getElementsByClassName('code')[0] // Replaces students code with the version of BetterSEQTA if (code != null) { if (!code.innerHTML.includes('BetterSEQTA')) { UserInitalCode = code.innerHTML code.innerHTML = `BetterSEQTA v${browser.runtime.getManifest().version}` code.setAttribute('data-hover', 'Click for user code') code.addEventListener('click', function () { var code = document.getElementsByClassName('code')[0] if (code.innerHTML.includes('BetterSEQTA')) { code.innerHTML = UserInitalCode code.setAttribute('data-hover', 'Click for BetterSEQTA version') } else { code.innerHTML = `BetterSEQTA v${ browser.runtime.getManifest().version }` code.setAttribute('data-hover', 'Click for user code') } }) if (toggle) { // Creates Home menu button and appends it as the first child of the list const result = await browser.storage.local.get() animbkEnable(result) updateBgDurations(result) DarkMode = result.DarkMode if (DarkMode) { document.documentElement.classList.add('dark') } const container = document.getElementById('content')! const div = document.createElement('div') div.classList.add('titlebar') container.append(div) const NewButton = stringToHTML('
  • ') const menu = document.getElementById('menu')! const List = menu.firstChild! as HTMLElement if (NewButton.firstChild) { List.insertBefore(NewButton.firstChild, List.firstChild) } try { // Fetch the response and wait for it const response = await fetch(`${location.origin}/seqta/student/login`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', }, body: JSON.stringify({ mode: 'normal', query: null, redirect_url: location.origin, }), }) // Parse the JSON response and wait for it const responseData = await response.json() let info = responseData.payload // Manipulate the DOM as needed const titlebar = document.getElementsByClassName('titlebar')[0] const userInfo = stringToHTML( '
    ', ).firstChild titlebar.append(userInfo!) const userinfo = stringToHTML(`

    ${info.userDesc}

    ${UserInitalCode}

    `).firstChild titlebar.append(userinfo!) var logoutbutton = document.getElementsByClassName('logout')[0] var userInfosvgdiv = document.getElementById('logouttooltip')! userInfosvgdiv.appendChild(logoutbutton) // Await the fetch response const peopleResponse = await fetch(`${location.origin}/seqta/student/load/message/people`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', }, body: JSON.stringify({ mode: 'student' }), }) // Await the JSON parsing of the response const peopleResponseData = await peopleResponse.json() let students = peopleResponseData.payload // Process the students data var index = students.findIndex(function (person: any) { return ( person.firstname == info.userDesc.split(' ')[0] && person.surname == info.userDesc.split(' ')[1] ) }) let houseelement1 = document.getElementsByClassName('userInfohouse')[0] const houseelement = houseelement1 as HTMLElement if (students[index]?.house) { if (students[index]?.house_colour) { houseelement.style.background = students[index].house_colour try { let colorresult = GetThresholdOfColor(students[index]?.house_colour) houseelement.style.color = colorresult && colorresult > 300 ? 'black' : 'white' houseelement.innerText = students[index].year + students[index].house } catch (error) { houseelement.innerText = students[index].house } } } else { houseelement.innerText = students[index].year } } catch (error) { console.error('Error fetching and processing student data:', error) } const NewsButtonStr = '
  • ' const NewsButton = stringToHTML(NewsButtonStr) List!.appendChild(NewsButton.firstChild!) let a = document.createElement('div') a.classList.add('icon-cover') a.id = 'icon-cover' menu!.appendChild(a) const menuCover = document.querySelector('#icon-cover') menuCover!.addEventListener('click', function () { location.href = '../#?page=/home' loadHomePage(); (document! .getElementById('menu')! .firstChild! as HTMLElement).classList.remove('noscroll') }) // Creates the home container when the menu button is pressed const homebutton = document.getElementById('homebutton') homebutton!.addEventListener('click', function () { if (!MenuOptionsOpen) { loadHomePage() } }) // Creates the news container when the menu button is pressed const newsbutton = document.getElementById('newsbutton') newsbutton!.addEventListener('click', function () { if (!MenuOptionsOpen) { SendNewsPage() } }) } appendBackgroundToUI() addExtensionSettings() // If betterSEQTA+ is enabled, run the code if (toggle) { // Creates settings and dashboard buttons next to alerts let SettingsButton = stringToHTML( '', ) let ContentDiv = document.getElementById('content') ContentDiv!.append(SettingsButton.firstChild!) const result: any = await new Promise(resolve => { const result = browser.storage.local.get() result.then(resolve, onError) }) const DarkMode = result!.DarkMode const tooltipString = GetLightDarkModeString(DarkMode) const svgContent = DarkMode ? '' : '' const LightDarkModeButton = stringToHTML(` `) ContentDiv!.append(LightDarkModeButton.firstChild!) updateAllColors(DarkMode, result.selectedColor) document.getElementById('LightDarkModeButton')!.addEventListener('click', async () => { const result: any = await new Promise(resolve => { const result = browser.storage.local.get() result.then(resolve, onError) }) const newDarkMode = !result!.DarkMode browser.storage.local.set({ DarkMode: newDarkMode }) updateAllColors(newDarkMode, result.selectedColor) const darklightText = document.getElementById('darklighttooliptext') darklightText!.innerText = GetLightDarkModeString(newDarkMode) }) // Locate the menuToggle element const menuToggle = document.getElementById('menuToggle') menuToggle!.innerHTML = '' // Create three divs to act as lines of the hamburger icon for (let i = 0; i < 3; i++) { const line = document.createElement('div') line.className = 'hamburger-line' menuToggle!.appendChild(line) } } else { // Creates settings and dashboard buttons next to alerts let SettingsButton = stringToHTML( '', ) let ContentDiv = document.getElementById('content') ContentDiv!.append(SettingsButton.firstChild!) } var AddedSettings = document.getElementById('AddedSettings') var extensionPopup = document.getElementById('ExtensionPopup') AddedSettings!.addEventListener('click', function () { if (SettingsClicked) { extensionPopup!.classList.add('hide') animate( '#ExtensionPopup', { opacity: [1, 0], scale: [1, 0] }, { easing: spring({ stiffness: 220, damping: 18 }) } ); (document.getElementById('ExtensionIframe')! as HTMLIFrameElement).contentWindow!.postMessage('popupClosed', '*') SettingsClicked = false; } else { extensionPopup!.classList.remove('hide') animate( '#ExtensionPopup', { opacity: [0, 1], scale: [0, 1] }, { easing: spring({ stiffness: 260, damping: 24 }) } ) SettingsClicked = true } }) } } } function GetLightDarkModeString(darkmodetoggle: boolean) { let tooltipstring if (darkmodetoggle) { tooltipstring = 'Switch to light theme' } else { tooltipstring = 'Switch to dark theme' } return tooltipstring } function CheckCurrentLesson(lesson: any, num: number) { var startTime = lesson.from var endTime = lesson.until // Gets current time let currentDate = new Date() // Takes start time of current lesson and makes it into a Date function for comparison let startDate = new Date(currentDate.getTime()) startDate.setHours(startTime.split(':')[0]) startDate.setMinutes(startTime.split(':')[1]) startDate.setSeconds(parseInt('00')) // Takes end time of current lesson and makes it into a Date function for comparison let endDate = new Date(currentDate.getTime()) endDate.setHours(endTime.split(':')[0]) endDate.setMinutes(endTime.split(':')[1]) endDate.setSeconds(parseInt('00')) // Gets the difference between the start time and current time var difference = startDate.getTime() - currentDate.getTime() // Converts the difference into minutes var minutes = Math.floor(difference / 1000 / 60) // Checks if current time is between the start time and end time of current tested lesson let valid = startDate < currentDate && endDate > currentDate let id = lesson.code + num const date = new Date() var elementA = document.getElementById(id) if (!elementA) { clearInterval(LessonInterval) } else { if ( currentSelectedDate.toLocaleDateString('en-au') == date.toLocaleDateString('en-au') ) { if (valid) { // Apply the activelesson class to increase the box-shadow of current lesson elementA.classList.add('activelesson') } else { // Removes the activelesson class to ensure only the active lesson have the class if (elementA != null) { elementA.classList.remove('activelesson') } } } } // If 5 minutes before the start of another lesson: if (minutes == 5) { const result = browser.storage.local.get('lessonalert') function open (result: any) { if (result.lessonalert) { // Checks if notifications are supported if (!window.Notification) { console.log('Browser does not support notifications.') } else { // check if permission is already granted if (Notification.permission === 'granted') { new Notification('Next Lesson in 5 Minutes:', { body: 'Subject: ' + lesson.description + ' \nRoom: ' + lesson.room + ' \nTeacher: ' + lesson.staff, }) } else { // request permission from user Notification.requestPermission() .then(function (p) { if (p === 'granted') { // show notification here new Notification('Next Lesson in 5 Minutes:', { body: 'Subject: ' + lesson.description + ' \nRoom: ' + lesson.room + ' \nTeacher: ' + lesson.staff, }) } else { console.log('User blocked notifications.') } }) .catch(function (err) { console.error(err) }) } } } } result.then(open, onError) } } export function GetThresholdOfColor(color: any) { // Case-insensitive regular expression for matching RGBA colors const rgbaRegex = /rgba?\(([^)]+)\)/gi // Check if the color string is a gradient (linear or radial) if (color.includes('gradient')) { let gradientThresholds = [] // Find and replace all instances of RGBA in the gradient let match while ((match = rgbaRegex.exec(color)) !== null) { // Extract the individual components (r, g, b, a) const rgbaString = match[1] const [r, g, b] = rgbaString.split(',').map(str => str.trim()) // Compute the threshold using your existing algorithm const threshold = Math.sqrt(parseInt(r) ** 2 + parseInt(g) ** 2 + parseInt(b) ** 2) // Store the computed threshold gradientThresholds.push(threshold) } // Calculate the average threshold const averageThreshold = gradientThresholds.reduce((acc, val) => acc + val, 0) / gradientThresholds.length return averageThreshold } else { // Handle the color as a simple RGBA (or hex, or whatever the Color library supports) const rgb = Color.rgb(color).object() return Math.sqrt(rgb.r ** 2 + rgb.g ** 2 + rgb.b ** 2) } } function CheckCurrentLessonAll(lessons: any) { // Checks each lesson and sets an interval to run every 60 seconds to continue updating LessonInterval = setInterval( function () { for (let i = 0; i < lessons.length; i++) { CheckCurrentLesson(lessons[i], i + 1) } }.bind(lessons), 60000, ) } // Helper function to build the assessment URL function buildAssessmentURL(programmeID: any, metaID: any, itemID = '') { const base = '../#?page=/assessments/' return itemID ? `${base}${programmeID}:${metaID}&item=${itemID}` : `${base}${programmeID}:${metaID}` } // Function to create a lesson div element from a lesson object function makeLessonDiv(lesson: any, num: number) { if (!lesson) throw new Error('No lesson provided.') const { code, colour, description, staff, room, from, until, attendanceTitle, programmeID, metaID, assessments } = lesson // Construct the base lesson string with default values using ternary operators let lessonString = `

    ${description || 'Unknown'}

    ${staff || 'Unknown'}

    ${room || 'Unknown'}

    ${from || 'Unknown'} - ${until || 'Unknown'}

    ${attendanceTitle || 'Unknown'}
    ` // Add buttons for assessments and courses if applicable if (programmeID !== 0) { lessonString += `
    ${assessmentsicon}
    ${coursesicon}
    ` } // Add assessments if they exist if (assessments && assessments.length > 0) { const assessmentString = assessments.map((element: any) => `

    ${element.title}

    ` ).join('') lessonString += `
    ${assessmentString}
    ` } lessonString += '
    ' return stringToHTML(lessonString) } function CheckUnmarkedAttendance(lessonattendance: any) { if (lessonattendance) { var lesson = lessonattendance.label } else { lesson = ' ' } return lesson } function callHomeTimetable(date: string, change?: any) { // Creates a HTTP Post Request to the SEQTA page for the students timetable var xhr = new XMLHttpRequest() xhr.open('POST', `${location.origin}/seqta/student/load/timetable?`, true) // Sets the response type to json xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8') xhr.onreadystatechange = function () { // Once the response is ready if (xhr.readyState === 4) { var serverResponse = JSON.parse(xhr.response) let lessonArray: Array = [] const DayContainer = document.getElementById('day-container')! // If items in response: if (serverResponse.payload.items.length > 0) { if (DayContainer.innerText || change) { for (let i = 0; i < serverResponse.payload.items.length; i++) { lessonArray.push(serverResponse.payload.items[i]) } lessonArray.sort(function (a, b) { return a.from.localeCompare(b.from) }) // If items in the response, set each corresponding value into divs // lessonArray = lessonArray.splice(1) GetLessonColours().then((colours) => { let subjects = colours for (let i = 0; i < lessonArray.length; i++) { let subjectname = `timetable.subject.colour.${lessonArray[i].code}` let subject = subjects.find( (element: any) => element.name === subjectname, ) if (!subject) { lessonArray[i].colour = '--item-colour: #8e8e8e;' } else { lessonArray[i].colour = `--item-colour: ${subject.value};` let result = GetThresholdOfColor(subject.value) if (result > 300) { lessonArray[i].invert = true } } // Removes seconds from the start and end times lessonArray[i].from = lessonArray[i].from.substring(0, 5) lessonArray[i].until = lessonArray[i].until.substring(0, 5) // Checks if attendance is unmarked, and sets the string to " ". lessonArray[i].attendanceTitle = CheckUnmarkedAttendance( lessonArray[i].attendance, ) } // If on home page, apply each lesson to HTML with information in each div DayContainer.innerText = '' for (let i = 0; i < lessonArray.length; i++) { var div = makeLessonDiv(lessonArray[i], i + 1) // Append each of the lessons into the day-container if (lessonArray[i].invert) { const div1 = div.firstChild! as HTMLElement div1.classList.add('day-inverted') } DayContainer.append(div.firstChild as HTMLElement) } const today = new Date() if (currentSelectedDate.getDate() == today.getDate()) { for (let i = 0; i < lessonArray.length; i++) { CheckCurrentLesson(lessonArray[i], i + 1) } // For each lesson, check the start and end times CheckCurrentLessonAll(lessonArray) } }) } } else { if (DayContainer == null) { console.log('DayContainer is null') //DayContainer = document.getElementById('day-container')! } console.log(DayContainer); DayContainer.innerHTML = '' var dummyDay = document.createElement('div') dummyDay.classList.add('day-empty') let img = document.createElement('img') img.src = LogoLight let text = document.createElement('p') text.innerText = 'No lessons available.' dummyDay.append(img) dummyDay.append(text) DayContainer.append(dummyDay) } } } xhr.send( JSON.stringify({ // Information sent to SEQTA page as a request with the dates and student number from: date, until: date, // Funny number student: 69, }), ) } function GetUpcomingAssessments() { let func = fetch(`${location.origin}/seqta/student/assessment/list/upcoming?`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', }, body: JSON.stringify({ student: 69 }), }) return func .then((result) => result.json()) .then((response) => response.payload) } async function GetActiveClasses() { try { const response = await fetch(`${location.origin}/seqta/student/load/subjects?`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify({}) }) if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`) } const data = await response.json() return data.payload } catch (error) { console.error('Oops! There was a problem fetching active classes:', error) } } function comparedate(obj1: any, obj2: any) { if (obj1.date < obj2.date) { return -1 } if (obj1.date > obj2.date) { return 1 } return 0 } function CreateElement(type: string, class_?: any, id?: any, innerText?: string, innerHTML?: string, style?: string) { let element = document.createElement(type) if (class_ !== undefined) { element.classList.add(class_) } if (id !== undefined) { element.id = id } if (innerText !== undefined) { element.innerText = innerText } if (innerHTML !== undefined) { element.innerHTML = innerHTML } if (style !== undefined) { element.style.cssText = style } return element } function createAssessmentDateDiv(date: string, value: any, datecase?: any) { var options = { weekday: 'long' as 'long', month: 'long' as 'long', day: 'numeric' as 'numeric' } const FormattedDate = new Date(date) const assessments = value.assessments const container = value.div let DateTitleDiv = document.createElement('div') DateTitleDiv.classList.add('upcoming-date-title') if (datecase) { let datetitle = document.createElement('h5') datetitle.classList.add('upcoming-special-day') datetitle.innerText = datecase DateTitleDiv.append(datetitle) container.setAttribute('data-day', datecase) } let DateTitle = document.createElement('h5') DateTitle.innerText = FormattedDate.toLocaleDateString('en-AU', options) DateTitleDiv.append(DateTitle) container.append(DateTitleDiv) let assessmentContainer = document.createElement('div') assessmentContainer.classList.add('upcoming-date-assessments') for (let i = 0; i < assessments.length; i++) { const element = assessments[i] let item = document.createElement('div') item.classList.add('upcoming-assessment') item.setAttribute('data-subject', element.code) item.id = `assessment${element.id}` item.style.cssText = element.colour let titlediv = document.createElement('div') titlediv.classList.add('upcoming-subject-title') let titlesvg = stringToHTML(` `).firstChild titlediv.append(titlesvg!) let detailsdiv = document.createElement('div') detailsdiv.classList.add('upcoming-details') let detailstitle = document.createElement('h5') detailstitle.innerText = `${element.subject} assessment` let subject = document.createElement('p') subject.innerText = element.title subject.classList.add('upcoming-assessment-title') subject.onclick = function () { document.querySelector('#menu ul')!.classList.add('noscroll'); location.href = `../#?page=/assessments/${element.programmeID}:${element.metaclassID}&item=${element.id}` } detailsdiv.append(detailstitle) detailsdiv.append(subject) item.append(titlediv) item.append(detailsdiv) assessmentContainer.append(item) fetch(`${location.origin}/seqta/student/assessment/submissions/get`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', }, body: JSON.stringify({ assessment: element.id, metaclass: element.metaclassID, student: 69, }), }) .then((result) => result.json()) .then((response) => { if (response.payload.length > 0) { const assessment = document.querySelector(`#assessment${element.id}`) // ticksvg = stringToHTML(``).firstChild // ticksvg.classList.add('upcoming-tick') // assessment.append(ticksvg) let submittedtext = document.createElement('div') submittedtext.classList.add('upcoming-submittedtext') submittedtext.innerText = 'Submitted' assessment!.append(submittedtext) } }) } container.append(assessmentContainer) return container } function CheckSpecialDay(date1: Date, date2: Date) { if ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() - 1 === date2.getDate() ) { return 'Yesterday' } if ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() === date2.getDate() ) { return 'Today' } if ( date1.getFullYear() === date2.getFullYear() && date1.getMonth() === date2.getMonth() && date1.getDate() + 1 === date2.getDate() ) { return 'Tomorrow' } } function CreateSubjectFilter(subjectcode: any, itemcolour: string, checked: any) { let label = CreateElement('label', 'upcoming-checkbox-container') label.innerText = subjectcode let input1 = CreateElement('input') const input = input1 as HTMLInputElement input.type = 'checkbox' input.checked = checked input.id = `filter-${subjectcode}` label.style.cssText = itemcolour let span = CreateElement('span', 'upcoming-checkmark') label.append(input) label.append(span) input.addEventListener('change', function (change) { const result = browser.storage.local.get() function open (storage: any) { let filters = storage.subjectfilters let id = (change.target as HTMLInputElement)!.id.split('-')[1] filters[id] = (change.target as HTMLInputElement)!.checked browser.storage.local.set({ subjectfilters: filters }) } result.then(open, onError) }) return label } function CreateFilters(subjects: any) { const result = browser.storage.local.get() function open (result: any) { let filteroptions = result.subjectfilters let filterdiv = document.querySelector('#upcoming-filters') for (let i = 0; i < subjects.length; i++) { const element = subjects[i] // eslint-disable-next-line if (!Object.prototype.hasOwnProperty.call(filteroptions, element.code)) { filteroptions[element.code] = true browser.storage.local.set({ subjectfilters: filteroptions }) } let elementdiv = CreateSubjectFilter( element.code, element.colour, filteroptions[element.code], ) filterdiv!.append(elementdiv) } } result.then(open, onError) } function CreateUpcomingSection(assessments: any, activeSubjects: any) { let upcomingitemcontainer = document.querySelector('#upcoming-items') let overdueDates = [] let upcomingDates = {} // date = '2022/3/20' // var Today = new Date(date) var Today = new Date() // Removes overdue assessments from the upcoming assessments array and pushes to overdue array for (let i = 0; i < assessments.length; i++) { const element = assessments[i] let assessmentdue = new Date(element.due) CheckSpecialDay(Today, assessmentdue) if (assessmentdue < Today) { if (!CheckSpecialDay(Today, assessmentdue)) { overdueDates.push(element) assessments.splice(i, 1) i-- } } } var TomorrowDate = new Date() TomorrowDate.setDate(TomorrowDate.getDate() + 1) GetLessonColours().then((colours) => { let subjects = colours for (let i = 0; i < assessments.length; i++) { let subjectname = `timetable.subject.colour.${assessments[i].code}` let subject = subjects.find((element: any) => element.name === subjectname) if (!subject) { assessments[i].colour = '--item-colour: #8e8e8e;' } else { assessments[i].colour = `--item-colour: ${subject.value};` GetThresholdOfColor(subject.value); // result (originally) result = GetThresholdOfColor } } for (let i = 0; i < activeSubjects.length; i++) { const element = activeSubjects[i] let subjectname = `timetable.subject.colour.${element.code}` let colour = colours.find((element: any) => element.name === subjectname) if (!colour) { element.colour = '--item-colour: #8e8e8e;' } else { element.colour = `--item-colour: ${colour.value};` let result = GetThresholdOfColor(colour.value) if (result > 300) { element.invert = true } } } CreateFilters(activeSubjects) // @ts-ignore let type // @ts-ignore let class_ for (let i = 0; i < assessments.length; i++) { const element: any = assessments[i] if (!upcomingDates[element.due as keyof typeof upcomingDates]) { let dateObj: any = new Object() dateObj.div = CreateElement( // TODO: not sure whats going on here? // eslint-disable-next-line type = "div", // eslint-disable-next-line class_ = "upcoming-date-container", ) dateObj.assessments = [] dateObj = upcomingDates[element.due as keyof typeof upcomingDates] as any } let assessmentDateDiv = upcomingDates[element.due as keyof typeof upcomingDates]; (assessmentDateDiv as any).assessments.push(element) } for (var date in upcomingDates) { let assessmentdue = new Date((upcomingDates[date as keyof typeof upcomingDates] as any).assessments[0].due) let specialcase = CheckSpecialDay(Today, assessmentdue) let assessmentDate if (specialcase) { let datecase: string = specialcase! assessmentDate = createAssessmentDateDiv( date, upcomingDates[date as keyof typeof upcomingDates], // eslint-disable-next-line datecase, ) } else { assessmentDate = createAssessmentDateDiv(date, upcomingDates[date as keyof typeof upcomingDates]) } if (specialcase === 'Yesterday') { upcomingitemcontainer!.insertBefore( assessmentDate, upcomingitemcontainer!.firstChild, ) } else { upcomingitemcontainer!.append(assessmentDate) } } const result = browser.storage.local.get() function open (result: any) { FilterUpcomingAssessments(result.subjectfilters) } result.then(open, onError) }) } function AddPlaceHolderToParent(parent: any, numberofassessments: any) { let textcontainer = CreateElement('div', 'upcoming-blank') let textblank = CreateElement('p', 'upcoming-hiddenassessment') let s = '' if (numberofassessments > 1) { s = 's' } textblank.innerText = `${numberofassessments} hidden assessment${s} due` textcontainer.append(textblank) textcontainer.setAttribute('data-hidden', 'true') parent.append(textcontainer) } function FilterUpcomingAssessments(subjectoptions: any) { for (var item in subjectoptions) { let subjectdivs = document.querySelectorAll(`[data-subject="${item}"]`) for (let i = 0; i < subjectdivs.length; i++) { const element = subjectdivs[i] if (!subjectoptions[item]) { element.classList.add('hidden') } if (subjectoptions[item]) { element.classList.remove('hidden') } (element.parentNode! as HTMLElement).classList.remove('hidden') let children = element.parentNode!.parentNode!.children for (let i = 0; i < children.length; i++) { const element = children[i] if (element.hasAttribute('data-hidden')) { element.remove() } } if ( element.parentNode!.children.length == element.parentNode!.querySelectorAll('.hidden').length ) { if (element.parentNode!.querySelectorAll('.hidden').length > 0) { if (!(element.parentNode!.parentNode! as HTMLElement).hasAttribute('data-day')) { (element.parentNode!.parentNode! as HTMLElement).classList.add('hidden') } else { AddPlaceHolderToParent( element.parentNode!.parentNode, element.parentNode!.querySelectorAll('.hidden').length, ) } } } else { (element.parentNode!.parentNode! as HTMLElement).classList.remove('hidden') } } } } browser.storage.onChanged.addListener(function (changes) { if (changes.subjectfilters) { FilterUpcomingAssessments(changes.subjectfilters.newValue) } }) async function GetLessonColours() { let func = fetch(`${location.origin}/seqta/student/load/prefs?`, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8', }, body: JSON.stringify({ request: 'userPrefs', asArray: true, user: 69 }), }) return func .then((result) => result.json()) .then((response) => response.payload) } export function CreateCustomShortcutDiv(element: any) { // Creates the stucture and element information for each seperate shortcut var shortcut = document.createElement('a') shortcut.setAttribute('href', element.url) shortcut.setAttribute('target', '_blank') var shortcutdiv = document.createElement('div') shortcutdiv.classList.add('shortcut') shortcutdiv.classList.add('customshortcut') let image = stringToHTML( ` ${element.icon} `, ).firstChild; (image as HTMLElement).classList.add('shortcuticondiv') var text = document.createElement('p') text.textContent = element.name shortcutdiv.append(image!) shortcutdiv.append(text) shortcut.append(shortcutdiv) document.getElementById('shortcuts')!.append(shortcut) } export function RemoveShortcutDiv(elements: any) { elements.forEach((element: any) => { const shortcuts = document.querySelectorAll('.shortcut') shortcuts.forEach((shortcut) => { const anchorElement = shortcut.parentElement; // the element is the parent const textElement = shortcut.querySelector('p'); //

    is a direct child of .shortcut const title = textElement ? textElement.textContent : '' let shouldRemove = title === element.name // Check href only if element.url exists if (element.url) { shouldRemove = shouldRemove && (anchorElement!.getAttribute('href') === element.url) } if (shouldRemove) { anchorElement!.remove() } }) }) } function AddCustomShortcutsToPage() { const result = browser.storage.local.get(['customshortcuts']) function open (result: any) { var customshortcuts: any = Object.values(result)[0] if (customshortcuts.length > 0) { (document.getElementsByClassName('shortcut-container')[0] as HTMLElement).style.display = 'block' for (let i = 0; i < customshortcuts.length; i++) { const element = customshortcuts[i] CreateCustomShortcutDiv(element) } } } result.then(open, onError) } async function loadHomePage() { // Sends the html data for the home page console.log('[BetterSEQTA] Started Loading Home Page') document.title = 'Home ― SEQTA Learn' const element = document.querySelector('[data-key=home]') // Apply the active class to indicate clicked on home button element!.classList.add('active') // Remove all current elements in the main div to add new elements const main = document.getElementById('main') if (!main) { console.error('Main element not found.') return } //main.innerHTML = ''; const icon = document.querySelector('link[rel*="icon"]')! as HTMLLinkElement icon.href = icon48 currentSelectedDate = new Date() // Creates the root of the home page added to the main div var html = stringToHTML('

    ') // Appends the html file to main div // Note: firstChild of html is done due to needing to grab the body from the stringToHTML function main!.append(html.firstChild!) // Gets the current date const date = new Date() // Formats the current date used send a request for timetable and notices later const TodayFormatted = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + (date.getDate() < 10 ? '0' : '') + date.getDate() // Creates the shortcut container into the home container const Shortcut = stringToHTML('
    ') // Appends the shortcut container into the home container document.getElementById('home-container')!.append(Shortcut.firstChild!) // Creates the container div for the timetable portion of the home page const Timetable = stringToHTML('

    Today\'s Lessons

    ') // Appends the timetable container into the home container document.getElementById('home-container')!.append(Timetable.firstChild!) if (document.getElementById('home-container')) { callHomeTimetable(TodayFormatted, true) } else { console.error("HELP! THERE IS NO HOME CONTAINER") } const timetablearrowback = document.getElementById('home-timetable-back') const timetablearrowforward = document.getElementById('home-timetable-forward') function SetTimetableSubtitle() { var homelessonsubtitle = document.getElementById('home-lesson-subtitle') const date = new Date() if ( date.getFullYear() == currentSelectedDate.getFullYear() && date.getMonth() == currentSelectedDate.getMonth() ) { if (date.getDate() == currentSelectedDate.getDate()) { // Change text to Today's Lessons homelessonsubtitle!.innerText = 'Today\'s Lessons' } else if (date.getDate() - 1 == currentSelectedDate.getDate()) { // Change text to Yesterday's Lessons homelessonsubtitle!.innerText = 'Yesterday\'s Lessons' } else if (date.getDate() + 1 == currentSelectedDate.getDate()) { // Change text to Tomorrow's Lessons homelessonsubtitle!.innerText = 'Tomorrow\'s Lessons' } else { // Change text to date of the day homelessonsubtitle!.innerText = `${currentSelectedDate.toLocaleString( 'en-us', { weekday: 'short' }, )} ${currentSelectedDate.toLocaleDateString('en-au')}` } } else { // Change text to date of the day homelessonsubtitle!.innerText = `${currentSelectedDate.toLocaleString( 'en-us', { weekday: 'short' }, )} ${currentSelectedDate.toLocaleDateString('en-au')}` } } function changeTimetable(value: any) { currentSelectedDate.setDate(currentSelectedDate.getDate() + value) let FormattedDate = currentSelectedDate.getFullYear() + '-' + (currentSelectedDate.getMonth() + 1) + '-' + currentSelectedDate.getDate() callHomeTimetable(FormattedDate, true) SetTimetableSubtitle() } timetablearrowback!.addEventListener('click', function () { changeTimetable(-1) }) timetablearrowforward!.addEventListener('click', function () { changeTimetable(1) }) // Adds the shortcuts to the shortcut container const result = browser.storage.local.get(['shortcuts']) function open (result: any) { const shortcuts = Object.values(result)[0] addShortcuts(shortcuts) } result.then(open, onError) // Creates the upcoming container and appends to the home container var upcomingcontainer = document.createElement('div') upcomingcontainer.classList.add('upcoming-container') upcomingcontainer.classList.add('border') let upcomingtitlediv = CreateElement('div', 'upcoming-title') let upcomingtitle = document.createElement('h2') upcomingtitle.classList.add('home-subtitle') upcomingtitle.innerText = 'Upcoming Assessments' upcomingtitlediv.append(upcomingtitle) let upcomingfilterdiv = CreateElement( 'div', 'upcoming-filters', 'upcoming-filters', ) upcomingtitlediv.append(upcomingfilterdiv) upcomingcontainer.append(upcomingtitlediv) let upcomingitems = document.createElement('div') upcomingitems.id = 'upcoming-items' upcomingitems.classList.add('upcoming-items') upcomingcontainer.append(upcomingitems) document.getElementById('home-container')!.append(upcomingcontainer) // Creates the notices container into the home container const NoticesStr = String.raw`

    Notices

    ` var Notices = stringToHTML(NoticesStr) // Appends the shortcut container into the home container document.getElementById('home-container')!.append(Notices.firstChild!) /* animate( '.home-container > div', { opacity: [0, 1], y: [10, 0] }, { delay: stagger(0.2, { start: 0 }), duration: 0.6, easing: [.22, .03, .26, 1] } ) */ callHomeTimetable(TodayFormatted) const GetPrefs = await fetch(`${location.origin}/seqta/student/load/prefs?`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ asArray: true, request: 'userPrefs' }) }) const response = await GetPrefs.json() const labelArray = response.payload[1].value.split(' ') const xhr2 = new XMLHttpRequest() xhr2.open( 'POST', `${location.origin}/seqta/student/load/notices?`, true ) xhr2.setRequestHeader('Content-Type', 'application/json; charset=utf-8') xhr2.onreadystatechange = function () { if (xhr2.readyState === 4) { const NoticesPayload = JSON.parse(xhr2.response) const NoticeContainer = document.getElementById('notice-container') if (NoticesPayload.payload.length === 0) { if (!NoticeContainer!.innerText) { // If no notices: display no notices const dummyNotice = document.createElement('div') dummyNotice.textContent = 'No notices for today.' dummyNotice.classList.add('dummynotice') NoticeContainer!.append(dummyNotice) } } else { if (!NoticeContainer!.innerText) { // For each element in the response json: const result = browser.storage.local.get(['DarkMode']) function noticeInfoDiv (result: any) { for (let i = 0; i < NoticesPayload.payload.length; i++) { if (labelArray.includes(JSON.stringify(NoticesPayload.payload[i].label))) { // Create a div, and place information from json response const NewNotice = document.createElement('div') NewNotice.classList.add('notice') const title = stringToHTML( '

    ' + NoticesPayload.payload[i].title + '

    ' ) NewNotice.append(title.firstChild!) if (NoticesPayload.payload[i].label_title !== undefined) { const label = stringToHTML( '
    ' + NoticesPayload.payload[i].label_title + '
    ' ) NewNotice.append(label.firstChild!) } const staff = stringToHTML( '
    ' + NoticesPayload.payload[i].staff + '
    ' ) NewNotice.append(staff.firstChild!) // Converts the string into HTML const content = stringToHTML(NoticesPayload.payload[i].contents.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, '').replace(/ +/, ' '), true) for (let i = 0; i < content.childNodes.length; i++) { NewNotice.append(content.childNodes[i]) } // Gets the colour for the top section of each notice let colour = NoticesPayload.payload[i].colour if (typeof (colour) === 'string') { const rgb = GetThresholdOfColor(colour) const DarkModeResult = result.DarkMode if (rgb < 100 && DarkModeResult) { colour = undefined } } const colourbar = document.createElement('div') colourbar.classList.add('colourbar') colourbar.style.background = 'var(--colour)' NewNotice.style.cssText = `--colour: ${colour}` // Appends the colour bar to the new notice NewNotice.append(colourbar) // Appends the new notice into the notice container NoticeContainer!.append(NewNotice) } } } result.then(noticeInfoDiv, onError) } } } } // Data sent as the POST request const dateControl = document.querySelector('input[type="date"]') as HTMLInputElement xhr2.send(JSON.stringify({ date: dateControl!.value })) function onInputChange (e: any) { xhr2.open('POST', `${location.origin}/seqta/student/load/notices?`, true) xhr2.setRequestHeader('Content-Type', 'application/json; charset=utf-8') xhr2.send(JSON.stringify({ date: e.target.value })) xhr2.onreadystatechange = function () { if (xhr2.readyState === 4) { const NoticesPayload = JSON.parse(xhr2.response) const NoticeContainer = document.getElementById('notice-container') if (NoticesPayload.payload.length === 0) { if (!NoticeContainer!.innerText) { // If no notices: display no notices const dummyNotice = document.createElement('div') dummyNotice.textContent = 'No notices for today.' dummyNotice.classList.add('dummynotice') NoticeContainer!.append(dummyNotice) } } else { document.querySelectorAll('.notice').forEach(e => e.remove()) // For each element in the response json: const result = browser.storage.local.get(['DarkMode']) function noticeInfoDiv (result: any) { for (let i = 0; i < NoticesPayload.payload.length; i++) { if (labelArray.includes(JSON.stringify(NoticesPayload.payload[i].label))) { // Create a div, and place information from json response const NewNotice = document.createElement('div') NewNotice.classList.add('notice') const title = stringToHTML( '

    ' + NoticesPayload.payload[i].title + '

    ' ) NewNotice.append(title.firstChild!) if (NoticesPayload.payload[i].label_title !== undefined) { const label = stringToHTML( '
    ' + NoticesPayload.payload[i].label_title + '
    ' ) NewNotice.append(label.firstChild!) } const staff = stringToHTML( '
    ' + NoticesPayload.payload[i].staff + '
    ' ) NewNotice.append(staff.firstChild!) // Converts the string into HTML const content = stringToHTML(NoticesPayload.payload[i].contents.replace(/\[\[[\w]+[:][\w]+[\]\]]+/g, '').replace(/ +/, ' '), true) for (let i = 0; i < content.childNodes.length; i++) { NewNotice.append(content.childNodes[i]) } // Gets the colour for the top section of each notice let colour = NoticesPayload.payload[i].colour if (typeof (colour) === 'string') { const rgb = GetThresholdOfColor(colour) const DarkModeResult = result.DarkMode if (rgb < 100 && DarkModeResult) { colour = undefined } } const colourbar = document.createElement('div') colourbar.classList.add('colourbar') colourbar.style.background = 'var(--colour)' NewNotice.style.cssText = `--colour: ${colour}` // Appends the colour bar to the new notice NewNotice.append(colourbar) // Appends the new notice into the notice container NoticeContainer!.append(NewNotice) } } } result.then(noticeInfoDiv, onError) } } } } dateControl.addEventListener('input', onInputChange) // Sends similar HTTP Post Request for the notices const result1 = browser.storage.local.get() function open1 (result: any) { if (result.notificationcollector) { enableNotificationCollector() } } result1.then(open1, onError) let activeClassList: any const assessments = await GetUpcomingAssessments() const classes = await GetActiveClasses() // Gets all subjects for the student for (let i = 0; i < classes.length; i++) { const element = classes[i] // eslint-disable-next-line if (element.hasOwnProperty("active")) { // for some reason eslint gets mad, even though it works? // Finds the active class list with the current subjects activeClassList = classes[i] } } let activeSubjects = [] if (activeClassList?.subjects) { activeSubjects = activeClassList.subjects } let activeSubjectCodes = [] // Gets the code for each of the subjects and puts them in an array for (let i = 0; i < activeSubjects.length; i++) { activeSubjectCodes.push(activeSubjects[i].code) } let CurrentAssessments = [] for (let i = 0; i < assessments.length; i++) { if (activeSubjectCodes.includes(assessments[i].code)) { CurrentAssessments.push(element) } } CurrentAssessments.sort(comparedate) CreateUpcomingSection(CurrentAssessments, activeSubjects) } export function addShortcuts(shortcuts: any) { for (let i = 0; i < shortcuts.length; i++) { const currentShortcut = shortcuts[i] if (currentShortcut?.enabled) { const Itemname = (currentShortcut?.name ?? '').replace(/\s/g, '') const linkDetails = ShortcutLinks?.[Itemname as keyof typeof ShortcutLinks] if (linkDetails) { createNewShortcut( linkDetails.link, linkDetails.icon, linkDetails.viewBox, currentShortcut?.name ) } else { console.warn(`No link details found for '${Itemname}'`) } } } AddCustomShortcutsToPage() } export function enableNotificationCollector() { var xhr3 = new XMLHttpRequest() xhr3.open('POST', `${location.origin}/seqta/student/heartbeat?`, true) xhr3.setRequestHeader( 'Content-Type', 'application/json; charset=utf-8' ) xhr3.onreadystatechange = function () { if (xhr3.readyState === 4) { var Notifications = JSON.parse(xhr3.response) var alertdiv = document.getElementsByClassName( 'notifications__bubble___1EkSQ' )[0] if (typeof alertdiv == 'undefined') { console.log('[BetterSEQTA] No notifications currently') } else { alertdiv.textContent = Notifications.payload.notifications.length } } } xhr3.send( JSON.stringify({ timestamp: '1970-01-01 00:00:00.0', hash: '#?page=/home', }) ) } export function disableNotificationCollector() { var alertdiv = document.getElementsByClassName('notifications__bubble___1EkSQ')[0] if (typeof alertdiv != 'undefined') { var currentNumber = parseInt(alertdiv.textContent!) if (currentNumber < 9) { alertdiv.textContent = currentNumber.toString() } else { alertdiv.textContent = '9+' } } } function createNewShortcut(link: any, icon: any, viewBox: any, title: any) { // Creates the stucture and element information for each seperate shortcut let shortcut = document.createElement('a') shortcut.setAttribute('href', link) shortcut.setAttribute('target', '_blank') let shortcutdiv = document.createElement('div') shortcutdiv.classList.add('shortcut') let image = stringToHTML( ``, ).firstChild; (image! as HTMLElement).classList.add('shortcuticondiv') let text = document.createElement('p') text.textContent = title shortcutdiv.append(image as HTMLElement) shortcutdiv.append(text) shortcut.append(shortcutdiv) document.getElementById('shortcuts')!.appendChild(shortcut) } function SendNewsPage() { setTimeout(function () { // Sends the html data for the home page console.log('[BetterSEQTA] Started Loading News Page') document.title = 'News ― SEQTA Learn' var element = document.querySelector('[data-key=news]') // Apply the active class to indicate clicked on home button element!.classList.add('active') // Remove all current elements in the main div to add new elements var main = document.getElementById('main') main!.innerHTML = '' // Creates the root of the home page added to the main div var htmlStr = '

    Latest Headlines - ABC News

    ' var html = stringToHTML(htmlStr) // Appends the html file to main div // Note : firstChild of html is done due to needing to grab the body from the stringToHTML function main!.append(html.firstChild!) const titlediv = document.getElementById('title')!.firstChild; (titlediv! as HTMLElement).innerText = 'News' AppendLoadingSymbol('newsloading', '#news-container') browser.runtime.sendMessage({ type: 'sendNews' }).then(function (response) { let newsarticles = response.news.articles var newscontainer = document.querySelector('#news-container') document.getElementById('newsloading')!.remove() for (let i = 0; i < newsarticles.length; i++) { let newsarticle = document.createElement('a') newsarticle.classList.add('NewsArticle') newsarticle.href = newsarticles[i].url newsarticle.target = '_blank' let articleimage = document.createElement('div') articleimage.classList.add('articleimage') if (newsarticles[i].urlToImage == 'null') { articleimage.style.backgroundImage = `url(${LogoLightOutline})` articleimage.style.width = '20%' articleimage.style.margin = '0 7.5%' } else { articleimage.style.backgroundImage = `url(${newsarticles[i].urlToImage})` } let articletext = document.createElement('div') articletext.classList.add('ArticleText') let title = document.createElement('a') title.innerText = newsarticles[i].title title.href = newsarticles[i].url title.target = '_blank' let description = document.createElement('p') description.innerHTML = newsarticles[i].description articletext.append(title) articletext.append(description) newsarticle.append(articleimage) newsarticle.append(articletext) newscontainer!.append(newsarticle) } }) }, 8) } async function CheckForMenuList() { if (!MenuItemMutation) { try { if (document.getElementById('menu')?.firstChild) { ObserveMenuItemPosition() MenuItemMutation = true } } catch (error) { return } } } function documentTextColor () { const result = browser.storage.local.get(['DarkMode']) function changeDocTextCol (result: any) { const Darkmode = result.DarkMode if (Darkmode) { const documentArray = document.querySelectorAll('td:not([class^="colourBar"]):not([class^="title"])') const fullDocArray = document.querySelectorAll('tr.document') const linkArray = document.querySelectorAll('a.uiFile') for (const item of fullDocArray) { item.classList.add('documentDark') } for (const item of linkArray) { item.setAttribute('style', 'color: #06b4fc;') } for (const item of documentArray) { item.setAttribute('style', 'color: white') } } else { const documentArray = document.querySelectorAll('td:not([class^="colourBar"]):not([class^="title"])') const fullDocArray = document.querySelectorAll('tr.document') const linkArray = document.querySelectorAll('a.uiFile') for (const item of fullDocArray) { item.classList.remove('documentDark') } for (const item of linkArray) { item.setAttribute('style', 'color: #3465a4;') } for (const item of documentArray) { item.setAttribute('style', 'color: black') } } } result.then(changeDocTextCol, onError) } browser.storage.onChanged.addListener(documentTextColor) function LoadInit() { console.log('[BetterSEQTA] Started Init') const result = browser.storage.local.get() function open (result: any) { if (result.onoff) { loadHomePage() } } result.then(open, onError) }