├── screenshots ├── tiss-enhancement1.png ├── tiss-enhancement2.png └── tuwel-group-member-list.png ├── tuwel-relative-profile-links.user.js ├── README.md ├── tuwel-group-member-list.user.js ├── ufind-extract-lva-daten.user.js ├── vowi-link-tiss-search.user.js ├── tuwien-autologin.user.js ├── peer-tube-download-button.user.js ├── tiss-enhancement.user.js ├── tiss-extract-vowi-templates.user.js └── tiss-ects-chart.user.js /screenshots/tiss-enhancement1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsinf/userscripts/HEAD/screenshots/tiss-enhancement1.png -------------------------------------------------------------------------------- /screenshots/tiss-enhancement2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsinf/userscripts/HEAD/screenshots/tiss-enhancement2.png -------------------------------------------------------------------------------- /screenshots/tuwel-group-member-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fsinf/userscripts/HEAD/screenshots/tuwel-group-member-list.png -------------------------------------------------------------------------------- /tuwel-relative-profile-links.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name TUWEL: Make profile links relative to course 3 | // @namespace https://fsinf.at/ 4 | // @match https://tuwel.tuwien.ac.at/mod/forum/discuss.php 5 | // @grant none 6 | // @version 1.1 7 | // ==/UserScript== 8 | 9 | var courseId = document.querySelector(".forumsearch form > input").value 10 | var profileLinks = document.querySelectorAll("address a").forEach(function(profileLink) { 11 | profileLink.href += "&course=" + courseId; 12 | }); 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Userscripts 2 | 3 | This repository contains small JavaScripts that enhance TU Wien websites. 4 | 5 | To use one of these scripts you need a userscript manager, we recommend [Violentmonkey](https://violentmonkey.github.io/). 6 | 7 | ## Contributing 8 | 9 | Pull requests and issues are welcome! 10 | 11 | ## Autologin for TISS, TUWEL & OpenCast 12 | 13 | [**Install**](https://fsinf.at/userscripts/tuwien-autologin.user.js) 14 | 15 | ## VoWi & Mattermost links in TISS 16 | 17 |  18 | 19 |  20 | 21 | [**Install**](https://fsinf.at/userscripts/tiss-enhancement.user.js) 22 | 23 | ## Re-enable download button in TUpeerTube 24 | 25 |  26 | 27 | [**Install**](https://fsinf.at/userscripts/peer-tube-download-button.user.js) 28 | -------------------------------------------------------------------------------- /tuwel-group-member-list.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name TUWEL: Show group members 3 | // @namespace https://fsinf.at/ 4 | // @downloadURL https://fsinf.at/userscripts/tuwel-group-member-list.user.js 5 | // @updateURL https://fsinf.at/userscripts/tuwel-group-member-list.user.js 6 | // @version 1 7 | // @grant none 8 | // @include https://tuwel.tuwien.ac.at/mod/grouptool/view.php* 9 | // ==/UserScript== 10 | 11 | var textBreadcrumb = document.getElementsByClassName('breadcrumb')[0].innerHTML; 12 | var courseId = textBreadcrumb.match('id\=([0-9]+)')[1] 13 | 14 | var groupContainers = document.body.getElementsByClassName('showmembers'); 15 | 16 | for (var i = 0; i < groupContainers.length; i++) { 17 | var groupContainer = groupContainers[i]; 18 | var showMemberLink = groupContainer.firstElementChild 19 | var groupData = showMemberLink.getAttribute('data-absregs'); 20 | var groupObj = JSON.parse(groupData); 21 | 22 | var listNode = document.createElement('ul'); 23 | for (var y = 0; y < groupObj.length; y++) { 24 | var listItemNode = document.createElement('li'); 25 | var memberObj = groupObj[y]; 26 | const a = document.createElement('a'); 27 | a.href = 'https://tuwel.tuwien.ac.at/user/view.php?course=' + courseId + '&id=' + memberObj.id; 28 | a.target = '_blank'; 29 | a.textContent = memberObj.fullname; 30 | listItemNode.appendChild(a); 31 | listNode.appendChild(listItemNode); 32 | } 33 | 34 | groupContainer.appendChild(listNode); 35 | } 36 | -------------------------------------------------------------------------------- /ufind-extract-lva-daten.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Extract LVA-Daten template for vowi.fsinf.at 3 | // @namespace https://vowi.fsinf.at/ 4 | // @match https://ufind.univie.ac.at/de/course.html 5 | // @match https://ufind.univie.ac.at/en/course.html 6 | // @description Does not work with Greasemonkey because of https://github.com/greasemonkey/greasemonkey/issues/2700 7 | // @grant none 8 | // @version 1.3 9 | // @downloadURL https://fsinf.at/userscripts/ufind-extract-lva-daten.user.js 10 | // @updateURL https://fsinf.at/userscripts/ufind-extract-lva-daten.user.js 11 | // ==/UserScript== 12 | 13 | function vowiLink(ns, id) { 14 | return 'https://vowi.fsinf.at/wiki/Spezial:CourseById?ns=' + ns + '&id=' + id; 15 | } 16 | 17 | document.addEventListener('ufind:finished', function (e) { 18 | var id = document.getElementsByClassName('number')[0].textContent + '/' + document.getElementsByClassName('when')[0].textContent; 19 | var a = document.createElement("a"); 20 | a.href = vowiLink('Uni_Wien', id); 21 | a.innerHTML = 'zum VoWi'; 22 | document.getElementsByClassName('details')[0].insertAdjacentElement('afterend', a); 23 | 24 | var ects = parseFloat(document.getElementsByClassName('ects')[0].textContent); 25 | var lecturers = []; 26 | $('.lecturers a').each(function(){ 27 | lecturers.push('[[ufind.person:'+this.href.split('=')[1] +'|'+this.textContent+']]'); 28 | }); 29 | var block = document.createElement("pre"); 30 | block.textContent = `{{LVA-Daten 31 | | ects = `+ects+`; 32 | | vortragende = `+lecturers.join(', ')+` 33 | | abteilung = 34 | | homepage = 35 | | id = `+id+` 36 | | wann = 37 | | sprache = 38 | | zuordnungen = 39 | 43 | }}`; 44 | a.insertAdjacentElement('afterend', block); 45 | }, false); 46 | -------------------------------------------------------------------------------- /vowi-link-tiss-search.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name TISS Search in VoWi 3 | // @namespace https://fsinf.at/ 4 | // @match https://vowi.fsinf.at/wiki/* 5 | // @match https://tiss.tuwien.ac.at/course/courseList.xhtml* 6 | // @description Does not work with Greasemonkey because of https://github.com/greasemonkey/greasemonkey/issues/2700 7 | // @version 1.4 8 | // @downloadURL https://fsinf.at/userscripts/vowi-link-tiss-search.user.js 9 | // @updateURL https://fsinf.at/userscripts/vowi-link-tiss-search.user.js 10 | // ==/UserScript== 11 | 12 | if (location.host == 'vowi.fsinf.at') { 13 | if (document.getElementById('lva-daten') != null) { 14 | var content = document.getElementById('mw-content-text'); 15 | var div = document.createElement('div'); 16 | content.insertBefore(div, content.firstChild); 17 | var a = document.createElement('a'); 18 | div.insertBefore(a, div.firstChild); 19 | a.setAttribute('target', '_blank'); 20 | a.innerHTML = 'TISS Suche'; 21 | var heading = document.getElementById('firstHeading').innerHTML; 22 | var title = encodeURIComponent(heading.substring(heading.indexOf(':') + 1, heading.indexOf('(') - 4)); 23 | var type = encodeURIComponent(heading.substr(heading.indexOf('(') - 3, 2)); 24 | a.href = 'https://tiss.tuwien.ac.at/course/courseList.xhtml?title=' + title + '&type=' + type; 25 | } 26 | } else if (location.host == 'tiss.tuwien.ac.at') { 27 | var params = new URL(location).searchParams; 28 | if (params.get('title')) { 29 | jsf.ajax.addOnEvent(function (data) { 30 | if (data.status == 'success') { 31 | document.getElementById('courseList:courseTitleInp').value = params.get('title'); 32 | document.getElementById('courseList:courseType').value = params.get('type'); 33 | var select = document.getElementById('courseList:semFrom') 34 | select.value = select.children[select.children.length - 1].value; 35 | document.getElementById('courseList:cSearchBtn').click(); 36 | } 37 | }) 38 | document.getElementById('courseList:quickSearchPanel').children[0].lastElementChild.click() 39 | } else { 40 | var titleInput = document.getElementById('courseList:courseTitleInp'); 41 | if (titleInput) { 42 | document.getElementById('courseList:courseLecturer').focus() 43 | window.find(titleInput.value); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tuwien-autologin.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Autologin for TU Wien SSO, TISS, TUWEL and OpenCast 3 | // @namespace https://vowi.fsinf.at/ 4 | // @include https://tiss.tuwien.ac.at/* 5 | // @include https://tuwel.tuwien.ac.at/* 6 | // @include https://oc-presentation.ltcc.tuwien.ac.at/* 7 | // @match https://idp.zid.tuwien.ac.at/simplesaml/module.php/core/loginuserpass.php 8 | // @match https://toss.fsinf.at/ 9 | // @grant none 10 | // @version 1.7 11 | // @downloadURL https://fsinf.at/userscripts/tuwien-autologin.user.js 12 | // @updateURL https://fsinf.at/userscripts/tuwien-autologin.user.js 13 | // ==/UserScript== 14 | 15 | function tuwelRefreshSession() { 16 | setTimeout(function() { 17 | fetch("https://tuwel.tuwien.ac.at/my/", {method: "HEAD"}); 18 | tuwelRefreshSession(); 19 | }, 15*60*1000); 20 | } 21 | 22 | async function openCastAutoLogin(){ 23 | let response = await fetch('/info/me.json'); 24 | if (response.ok){ 25 | let info = await response.json(); 26 | if (info.user.username == 'anonymous'){ 27 | localStorage.returnURL = location.toString(); 28 | window.location = 'https://tuwel.tuwien.ac.at/mod/lti/launch.php?id=385097'; 29 | } 30 | } 31 | } 32 | 33 | switch(location.host){ 34 | case 'idp.zid.tuwien.ac.at': 35 | if (document.querySelector('input[name="password"]').value) 36 | document.querySelector('input[name="password"]').form.submit() 37 | break; 38 | 39 | case 'tiss.tuwien.ac.at': 40 | if (document.getElementsByClassName("loading").length > 0) { 41 | // Don't run the script on sites which only contain the loading animation. 42 | return; 43 | } 44 | 45 | login = document.querySelector(".toolLogin"); 46 | if (login != null) { 47 | login.click(); 48 | } 49 | break; 50 | 51 | case 'tuwel.tuwien.ac.at': 52 | if (location.pathname == "/theme/university_boost/login/index.php") { 53 | document.querySelector("a[title='TU Wien Login']").click(); 54 | } else { 55 | tuwelRefreshSession(); 56 | } 57 | break; 58 | 59 | case 'oc-presentation.ltcc.tuwien.ac.at': 60 | if (location.search == '?epFrom=d264f820-6d51-4cb1-a4f2-bb74e2094149&e=1&p=1' && localStorage.returnURL){ 61 | let returnURL = localStorage.returnURL; 62 | localStorage.removeItem('returnURL'); 63 | window.location = returnURL; 64 | } else { 65 | openCastAutoLogin(); 66 | } 67 | break; 68 | 69 | case 'toss.fsinf.at': 70 | hasTES = true; 71 | break; 72 | } 73 | -------------------------------------------------------------------------------- /peer-tube-download-button.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Re-enable TUpeerTube download button 3 | // @namespace https://vowi.fsinf.at/ 4 | // @version 1.0 5 | // @description Re-enables the download button for TUpeerTube videos, for videos where they were disabled. 6 | // @author Fabian Scherer 7 | // @match https://tube1.it.tuwien.ac.at/videos/watch/* 8 | // @icon https://tube1.it.tuwien.ac.at/client/assets/images/favicon.png 9 | // @grant GM_openInTab 10 | // @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js 11 | // @downloadURL https://fsinf.at/userscripts/peer-tube-download-button.user.js 12 | // @updateURL https://fsinf.at/userscripts/peer-tube-download-button.user.js 13 | // ==/UserScript== 14 | 15 | 16 | 17 | 18 | 19 | //==================================================== 20 | // MIT Licensed 21 | // Author: jwilson8767 22 | 23 | /** 24 | * Waits for an element satisfying selector to exist, then resolves promise with the element. 25 | * Useful for resolving race conditions. 26 | * 27 | * @param selector 28 | * @returns {Promise} 29 | */ 30 | function elementReady(selector) { 31 | return new Promise((resolve, reject) => { 32 | const el = document.querySelector(selector); 33 | if (el) {resolve(el);} 34 | new MutationObserver((mutationRecords, observer) => { 35 | // Query for elements matching the specified selector 36 | Array.from(document.querySelectorAll(selector)).forEach((element) => { 37 | resolve(element); 38 | //Once we have resolved we don't need the observer anymore. 39 | observer.disconnect(); 40 | }); 41 | }) 42 | .observe(document.documentElement, { 43 | childList: true, 44 | subtree: true 45 | }); 46 | }); 47 | } 48 | //===================================================== 49 | 50 | function fs_download_url(id){return "https://tube1.it.tuwien.ac.at/download/videos/" + id + "-720.mp4"} 51 | 52 | function fs_do_the_download(){ 53 | var id = window.location.href.split("/").pop() 54 | var download_url = fs_download_url(id) 55 | GM_openInTab(download_url) 56 | } 57 | 58 | (function() { 59 | 'use strict'; 60 | 61 | $(function() { 62 | elementReady(".video-actions").then(() => { 63 | $(".video-actions").append('
').html(`
43 | {{Katalog-Daten
44 | |typ=${data.typ}
45 | |name=${data.name}${data.plan ? '\n|plan=' + data.plan : ''}
46 | |tiss=${data.tiss}
47 | }}`))
48 | }));
49 | }
50 |
51 | STUDIENKENNZAHL_BLACKLIST = [
52 | "E066011", // Erasmus
53 | "E066950", // Informatikdidaktik
54 | "E033531", // Data Engineering & Statistics
55 | "E066933", // Information & Knowledge Management
56 | "860GW", // Gebundene Wahlfächer - Technische Mathematik
57 | "884UF", // Informatik und Informatikmanagement
58 | "175FW", // Freie Wahlfächer - Wirtschaftsinformatik
59 | "880FW", // Freie Wahlfächer - Informatik
60 | ];
61 |
62 | function getLvaTitel() {
63 | let title = $("#contentInner h1").text();
64 | let matches = /^\s*([A-Z0-9.]{7})\s+(.*)$/gm.exec(title);
65 | return matches[2];
66 | }
67 |
68 | function getTissID() {
69 | let title = $("#contentInner h1").text();
70 | let matches = /^\s*([A-Z0-9.]{7})\s+(.*)$/gm.exec(title);
71 | return matches[1];
72 | }
73 |
74 | function getECTS() {
75 | let matches = /ECTS:\s([\d.]+)/.exec($("h2:contains('Merkmale') + ul > li").text());
76 | return matches[1].replace(".", ",").replace(",0", "");
77 | }
78 |
79 | function getLvaTyp() {
80 | let matches = /Typ:\s([A-Z]+)/.exec($("h2:contains('Merkmale') + ul > li").text());
81 | return matches[1];
82 | }
83 |
84 | function getVortragende() {
85 | let vortragende = [];
86 | $.each($("h2:contains('Vortragende') + ul a"), (key, link) => {
87 | let vortragender = link.innerText.split(", ");
88 | vortragender.reverse();
89 | vortragender = vortragender.join(" ");
90 | let personId = /(\d+)/.exec(link.href)[1];
91 | vortragende.push({
92 | wikiLink: `[[tiss.person:${personId}|${vortragender}]]`,
93 | name: vortragender,
94 | id: personId,
95 | });
96 | });
97 | return vortragende;
98 | }
99 |
100 | async function getLeiter(vortragende) {
101 | let leiter = [];
102 | let fallbackLeiter;
103 | let promises = vortragende.map((vortragender) => {
104 | return $.ajax({
105 | url: `https://tiss.tuwien.ac.at/adressbuch/adressbuch/person/${vortragender["id"]}`,
106 | dataType: "html",
107 | });
108 | });
109 | let adressbuchEntries = await Promise.all(promises);
110 | $.each(adressbuchEntries, (key, value) => {
111 | let titel = $(".vorangestellte-titel", value).text();
112 | let nachname = $(".nachname", value).text();
113 | if (titel.match(/Prof/) || titel.match(/PD/)) {
114 | leiter.push(nachname);
115 | } else if (key == 0) {
116 | fallbackLeiter = nachname;
117 | }
118 | });
119 | if (leiter.length == 0) {
120 | leiter.push(fallbackLeiter)
121 | }
122 | return leiter;
123 | }
124 |
125 | function getAbteilung() {
126 | return $("h2:contains('Institut') + ul > li").text().replace(/E\d+/, "").replace(/Institut für\s*/, "").trim();
127 | }
128 |
129 | function getSprache() {
130 | let sprache = $("h2:contains('Sprache')")[0].nextSibling.textContent;
131 | if (sprache.match(/Bei Bedarf/)) {
132 | return null;
133 | } else if (sprache == "Deutsch") {
134 | return "de";
135 | } else if (sprache == "Englisch") {
136 | return "en";
137 | }
138 | }
139 |
140 | function getSemester() {
141 | let semester = "";
142 |
143 | let semesters = $("#semesterForm option");
144 | if (semesters.length > 0) {
145 | // educationDetails
146 | let currentSemester = semesters[0].innerText;
147 | if (semesters.length > 1) {
148 | // course has been offered in more than one Semester already
149 | let lastSemester = $("#semesterForm option")[1].innerText;
150 | if (currentSemester[currentSemester.length - 1] == lastSemester[lastSemester.length - 1]) {
151 | semester = currentSemester[currentSemester.length - 1] + "S";
152 | } else {
153 | semester = "beide";
154 | }
155 | } else {
156 | // first time the course is held
157 | semester = currentSemester[currentSemester.length - 1] + "S";
158 | }
159 | } else {
160 | // courseDetails
161 | semester = "";
162 | }
163 |
164 | return semester;
165 | }
166 |
167 | function getHomepage() {
168 | return $("h2:contains('Weitere Informationen') + ul > li:contains('Homepage') a").attr("href");
169 | }
170 |
171 | function getWindowIdRequestTokenUrl(url) {
172 | // Thanks to Gittenburg for the hints.
173 | let windowId = dswh.utils.getWindowIdFromWindowName();
174 | let requestToken = dswh.utils.generateNewRequestToken();
175 | url = dswh.utils.setUrlParam(url, "dsrid", requestToken);
176 | url = dswh.utils.setUrlParam(url, "dswid", windowId);
177 | dswh.utils.storeCookie("dsrwid-" + requestToken, windowId, 3);
178 | return url;
179 | }
180 |
181 | function getModulNameFromTissID(data, tissID) {
182 | return $(`.courseKey:contains('${tissID}')`, data).parents("tr").prevAll("tr:has(.nodeTable-level-2)").first().find(".bold").text().replace(/(Wahl|Pflicht)?[Mm]odul /, "");
183 | }
184 |
185 | async function getModule(tissID) {
186 | let promises = [];
187 |
188 | $.each($("h2:contains('Curricula') + .ui-datatable tr.ui-widget-content"), (key, tr) => {
189 | let columns = $.find("td", tr);
190 | if (columns.length <= 3) {
191 | return;
192 | }
193 | let studienplanUrl = $(columns[0]).find("a").attr("href");
194 | let matches = /\s*([A-Z0-9 ]{3,7})\s+(.*)/.exec(columns[0].textContent);
195 | let studienkennzahl = matches[1].replace(" ", "");
196 | if (studienkennzahl[0] == "0") {
197 | studienkennzahl = "E" + studienkennzahl;
198 | }
199 | if ($.inArray(studienkennzahl, STUDIENKENNZAHL_BLACKLIST) != -1 || /^\d{3}$/.exec(studienkennzahl)) {
200 | return;
201 | }
202 | let studium = matches[2].trim();
203 | let semester = columns[1].textContent.trim();
204 |
205 | promises.push(
206 | $.ajax({
207 | url: getWindowIdRequestTokenUrl(studienplanUrl),
208 | dataType: "html",
209 | }).then((data) => {
210 | return {
211 | studienkennzahl: studienkennzahl,
212 | wahl: semester == "",
213 | name: getModulNameFromTissID(data, tissID),
214 | };
215 | })
216 | );
217 | });
218 |
219 | return Promise.all(promises);
220 | }
221 |
222 | function filterModule(module) {
223 | let trs = module.find((modul) => modul["studienkennzahl"] == "TRS") != null;
224 | if (!trs) {
225 | return module;
226 | } else {
227 | return module.filter((modul) => !(modul["name"].includes("Transferable Skills") || modul["name"].includes("Fachübergreifende Qualifikation")));
228 | }
229 | }
230 |
231 | async function showLvaDaten() {
232 | $("#vowi-lva-daten").html(`
233 |
234 |
235 |
242 |
243 | `);
244 |
245 | let lvaTitel = getLvaTitel();
246 | let tissID = getTissID();
247 | let ects = getECTS();
248 | let lvaTyp = getLvaTyp();
249 | let vortragende = getVortragende();
250 | let leiter = await getLeiter(vortragende);
251 | let abteilung = getAbteilung();
252 | let sprache = getSprache();
253 | let semester = getSemester();
254 | let homepage = getHomepage();
255 | let module = filterModule(await getModule(tissID));
256 |
257 | // Build final template
258 | let lvaDaten = `
259 | {{LVA-Daten
260 | | ects = ${ects}
261 | | vortragende = ${vortragende.map((vortragender) => vortragender["wikiLink"]).join(", ")}
262 | | abteilung = ${abteilung}${homepage != undefined ? "\n| homepage = " + homepage : ""}
263 | | id = ${tissID.replace(".", "")}
264 | | wann = ${semester}${sprache != null ? "\n| sprache = " + sprache : ""}
265 | | zuordnungen =
266 | ${module.map((modul) => `{{Zuordnung|${modul["studienkennzahl"]}|${modul["name"]}${modul["wahl"] ? "|wahl=1" : ""}}}`).join("\n ")}
267 | }}
268 | `;
269 | $("#vowi-lva-daten").html(`
270 | ${lvaDaten}
275 | `);
276 | }
277 |
278 | $("#contentInner > form").append(`
279 |