Skip to content

Instantly share code, notes, and snippets.

@ErykDarnowski
Last active April 10, 2024 17:30
Show Gist options
  • Save ErykDarnowski/fd08f94ca139b1f575d8e7205e2e3a90 to your computer and use it in GitHub Desktop.
Save ErykDarnowski/fd08f94ca139b1f575d8e7205e2e3a90 to your computer and use it in GitHub Desktop.
USOS nice timetable display / copy / download [UserScript]

USOS nice timetable display / copy / download

This little script works for both ordinary and part time timetables + has a bunch of cool QoL features ;)
(It displays the nicer timetable under the original one)

Instructions

  1. Make sure you're using the HTML timetable!
  2. Install a Userscript browser extension

*It should work fine with most extensions (if not, write a comment)
2. Add the script

Tampermonkey
  1. Click on the extension icon (in the top right corner of the browser - you may have to click the puzzle icon first to see it)
  2. Click Control panel
  3. Click Utilities
  4. At the bottom in to Import from URL paste in this URL: https://gist.githubusercontent.com/ErykDarnowski/470bbbb14f6beb8de1b2c8ec18b9c906/raw/3f58dbf12dcf9e7caa42161f782c8d939eba52fe/script.js
  5. Click Install
  6. Click Install again
Violentmonkey
  1. Click on the extension icon (in the top right corner of the browser - you may have to click the puzzle icon first to see it)
  2. Click the cog icon
  3. Click +
  4. Click Install from URL
  5. Paste in this URL: <>
  6. Click Confirm
  7. Click Close

*The script should auto update!
*Remember, you can turn the script ON / OFF whenever you like by just flipping the switch next to it's name


I'm aware that this is probably the goofiest approach you've ever seen... BUT I just couldn't figure out how to iterate through this dumb table... if you don't believe me, I encourage you to check for yourself!

P.S. This is a bit dirty and should be refractored... but hey, it works!


[OLD] Instructions
  1. Open your uni's USOS page
  2. Go to your lesson timetable
  3. Make sure it's set to HTML format in the advanced settings page
  4. Open the dev tools (F12)
  5. Click the Console tab
  6. Paste in the script
  7. Setup the config values
  8. Press Enter
  9. Copy the printed result / use the downloaded txt file!
// ==UserScript==
// @name USOS nice timetable display / copy / download
// @description A script that replaces the buildings frame under USOS's timetable. It also allows to easily copy and download (as a txt file) the timetable.
// @version 2.0.1
// @author Eryk Darnowski (GH: ErykDarnowski TW: @erykdarnowski)
// @match *://usosweb.ansb.pl/kontroler.php?_action=home/plan*
// @match *://usosweb.ansb.pl/kontroler.php?_action=katalog2/przedmioty/*
// @match *://usosweb.ansb.pl/kontroler.php?_action=katalog2%2Fprzedmioty*
// @run-at document-idle
// @grant none
// @namespace https://gist.github.com/ErykDarnowski/fd08f94ca139b1f575d8e7205e2e3a90
// @supportURL https://gist.github.com/ErykDarnowski/fd08f94ca139b1f575d8e7205e2e3a90
// @updateURL https://gist.githubusercontent.com/ErykDarnowski/fd08f94ca139b1f575d8e7205e2e3a90/raw/78d31712592da3ab2a8c34ba47c30203d54edf32/script.js
// @downloadURL https://gist.githubusercontent.com/ErykDarnowski/fd08f94ca139b1f575d8e7205e2e3a90/raw/78d31712592da3ab2a8c34ba47c30203d54edf32/script.js
// ==/UserScript==
/* Releases
- 1.0.0 Initial
- 1.1.0 Add support for my [USOS timetable day date adder... Userscript](https://gist.github.com/ErykDarnowski/a0566eaa2ed6aab400a6b6ec7355fde0)
- 1.1.1 Replace lesson name Regex with `split`
- 1.2.0 Update to new UI + add `cleanLessonTypes` functionality
- 2.0.0 Add 'display in USOS' functionality + fix bugs with `uniqe` and `ignoreList` functionality + release as Tampermonkey script
- 2.0.1 Fix download button not getting disabled and script activating on wrong kinds of timetables (diff than `HTML (stary)`)
*/
const config = {
unique: true, // only print each subject name once (for instance if you have the same lesson twice / thrice in the same day)
removeEmptyDays: true, // won't print days that have no lessons / only the ones you added to the `ignoreList`
cleanLessonTypes: true, // when gathering lesson names, don't include the type of lesson (laboratory / lecture etc.)
cleanLessonNames: true, // when printing lesson names, don't print the classroom number and teacher name + surname
ignoreList: [ // list of subjects you'd like to ignore (for instance because you've already passed them) - it works based on inlcude + is case insensitive!
],
};
(() => {
"use strict";
// Get information from table
// 10.04.2024
let finalDays = [];
let finalString = '';
const basePath = '#layout-c22 > div';
const viewSwitchElChildren = document.querySelector(`${basePath} > div > usos-frame:nth-child(2) > div > fieldset`).children;
const headers = [...document.querySelectorAll(`${basePath} > div > div > table > tbody > tr > th`)];
const lessons = [...document.querySelectorAll(`${basePath} > div > div > table > tbody > tr > td > div`)];
const cleanTypeRegex = new RegExp('\\s-[^-]*(?=\\s\\()');
const cleanNameRegex = new RegExp(' \\(.*');
const makeArrUniq = arr => Array.from(new Set(arr));
const downloadFile = (filename, text) => { // discretely creates and automatically downloads a text file -> <https://ourcodeworld.com/articles/read/189/how-to-create-a-file-and-generate-a-download-with-javascript-in-the-browser-without-a-server>
const el = document.createElement('a');
el.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
el.setAttribute('download', filename);
el.style.display = 'none';
document.body.appendChild(el);
el.click();
document.body.removeChild(el);
};
// only display on right type of timetable (`HTML (stary)`)
if (viewSwitchElChildren.length === 3 && viewSwitchElChildren[1].textContent.includes('HTML (nowy)')) {
// creating day dicts from headers
headers.map(header => {
const headerRect = header.getBoundingClientRect();
finalDays.push({
name: header.textContent.replace('(', ' ('),
startPx: headerRect.x,
endPx: headerRect.x + headerRect.width,
lessons: [],
});
});
// putting lessons in the correct days
lessons.map(lesson => {
finalDays.map(header => {
// check wheter lesson elements is in the day (header) borders
const lessonPosX = lesson.getBoundingClientRect().x;
if (header.startPx < lessonPosX && lessonPosX < header.endPx) {
header.lessons.push(config.cleanLessonTypes ? lesson.textContent.replace(cleanTypeRegex, '') : lesson.textContent);
};
});
});
// applying some settings
finalDays.map(day => {
if (config.cleanLessonNames) day.lessons = day.lessons.map(lesson => lesson.replace(cleanNameRegex, ''));
if (config.ignoreList.length > 0) {
day.lessons = day.lessons.filter(lesson => {
return !(config.ignoreList.map(x => x.toLowerCase())).includes(lesson.toLowerCase());
});
};
if (config.unique) day.lessons = makeArrUniq(day.lessons);
if (!config.removeEmptyDays && day.lessons.length === 0) day.lessons = ['(empty)'];
});
// removing empty days
if (config.removeEmptyDays) finalDays = finalDays.filter(x => x.lessons.length !== 0);
// output
finalDays.map(day => {
// don't add margin before first day
(day.name !== finalDays[0].name) && (finalString += '\n\n');
finalString += `${day.name}\n`;
let lessons = day.lessons;
finalString += lessons.map(lesson => `- ${lesson}`).join('\n');
});
// ---
// Display in USOS frame
const copyBtnFunc = () => {
window.navigator.clipboard.writeText(finalString)
document.getElementById("btn-copy").disabled = true;
};
const downloadBtnFunc = () => {
if (finalString.length !== 0) {
const timetableDateStr = document.querySelector(`${basePath} > div > div > div > b`).textContent;
downloadFile(`${timetableDateStr}.txt`, finalString);
};
document.getElementById("btn-download").disabled = true;
};
const createBtnEl = (id, text, onclickFunc, anchorEl) => {
const btnEl = document.createElement('button');
btnEl.id = id;
btnEl.textContent = text;
btnEl.setAttribute('style', 'margin-top: 4px');
btnEl.addEventListener('click', onclickFunc);
anchorEl.appendChild(btnEl);
};
const createUlEl = (elementsArr, anchorEl, addPadding=false) => {
const renderProductList = (text, index, arr) => {
const li = document.createElement('li');
if (addPadding) ul.setAttribute('style', 'padding-bottom: 20px');
ul.appendChild(li);
li.textContent = li.textContent + text;
};
const ul = document.createElement('ul');
anchorEl.appendChild(ul);
elementsArr.forEach(renderProductList);
};
const usosFrame = document.querySelector(`${basePath} > usos-frame:nth-child(6) > div`);
if (usosFrame) {
// clear current content
usosFrame.innerHTML = '';
// create and add title <p>
const titleEl = document.createElement('p').appendChild(document.createTextNode("Przedmioty:"));
usosFrame.appendChild(titleEl);
// create days ul
createUlEl(finalDays.map(day => day.name), usosFrame);
// create subjects uls
for (let i = 0; i < finalDays.length; i++) {
createUlEl(finalDays[i].lessons, usosFrame.querySelectorAll('div > ul > li')[i], (i < finalDays.length - 1 ? true : false));
};
// create btns
createBtnEl("btn-copy", "Copy", copyBtnFunc, usosFrame);
createBtnEl("btn-download", "Download txt", downloadBtnFunc, usosFrame);
};
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment