Das Tool Tool

Handles the process for language course signup with persistent course selection

  1. // ==UserScript==
  2. // @name Das Tool Tool
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.6.1
  5. // @description Handles the process for language course signup with persistent course selection
  6. // @author ricardofauch
  7. // @match https://tool.uni-leipzig.de/einschreibung/bookings/*
  8. // @match https://tool.uni-leipzig.de/einschreibung/info/*
  9. // @grant GM_addStyle
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. let isEnabled = GM_getValue('isEnabled', false);
  19. let autoReload = GM_getValue('autoReload', true);
  20. let targetCourse = GM_getValue('targetCourse', '');
  21. const RELOAD_INTERVAL = 1000; // Reload every 1 second
  22. let intervalId = null;
  23.  
  24. // Add styles
  25. GM_addStyle(`
  26. #courseSignupControl {
  27. position: fixed;
  28. top: 40%;
  29. left: 10px;
  30. background: rgba(255, 203, 207, 0.8);
  31. border: 1px solid #ccc;
  32. padding: 10px;
  33. z-index: 9999;
  34. width: 180px;
  35. font-family: Arial, sans-serif;
  36. font-size: 12px;
  37. box-shadow: 0 2px 5px rgba(0,0,0,0.2);
  38. border-radius: 10px;
  39. }
  40. #courseSignupControl h3 {
  41. margin: 0 0 10px 0;
  42. font-size: 14px;
  43. }
  44. .text{
  45. margin: 5px 5px 5px 0;
  46. }
  47. .toggle-switch {
  48. position: relative;
  49. display: inline-block;
  50. width: 40px;
  51. height: 20px;
  52. margin-right: 5px;
  53. }
  54. .toggle-switch input {
  55. opacity: 0;
  56. width: 0;
  57. height: 0;
  58. }
  59. .slider {
  60. position: absolute;
  61. cursor: pointer;
  62. top: 0;
  63. left: 0;
  64. right: 0;
  65. bottom: 0;
  66. background-color: #ccc;
  67. transition: .4s;
  68. border-radius: 20px;
  69. }
  70. .slider:before {
  71. position: absolute;
  72. content: "";
  73. height: 16px;
  74. width: 16px;
  75. left: 2px;
  76. bottom: 2px;
  77. background-color: white;
  78. transition: .4s;
  79. border-radius: 50%;
  80. }
  81. input:checked + .slider {
  82. background-color: #2196F3;
  83. }
  84. input:checked + .slider:before {
  85. transform: translateX(20px);
  86. }
  87. .control-group {
  88. margin-bottom: 5px;
  89. }
  90. .labelsprachkurs{
  91. margin-bottom: 10px;
  92. }
  93. #courseSelect {
  94. width: 100%;
  95. box-sizing: border-box;
  96. margin-top: 5px;
  97. margin-bottom: 10px;
  98. }
  99. #status {
  100. margin-top: 10px;
  101. font-size: 11px;
  102. color: red;
  103. }
  104. `);
  105. // Add this function to handle course changes
  106. function handleCourseChange(newCourse) {
  107. console.log('Course changed to:', newCourse);
  108. targetCourse = newCourse;
  109. GM_setValue('targetCourse', targetCourse);
  110. if (isEnabled){
  111. // Navigate back to the course selection page
  112. if (window.location.href !== 'https://tool.uni-leipzig.de/einschreibung/bookings/course') {
  113. console.log('Navigating to course selection page');
  114. window.location.href = 'https://tool.uni-leipzig.de/einschreibung/bookings/course';
  115. } else {
  116. console.log('Already on course selection page, running checkAndSelect');
  117. checkAndSelect();
  118. }
  119. }
  120. }
  121. // Create UI (updated to set the selected course)
  122. function createControlPanel() {
  123. const controlPanel = document.createElement('div');
  124. controlPanel.id = 'courseSignupControl';
  125. controlPanel.innerHTML = `
  126. <h3>Das Tool Tool</h3>
  127. <p class="text">1. Wähle einen Sprachkurs aus dem Dropdown-Menü aus.</p>
  128. <p class="text">2. Aktiviere das Skript</p>
  129. <p class="text labelsprachkurs" >Die Seite wird dann für dich einmal pro Sekunde aktualisiert und sobald die Einschreibung freigeschaltet ist wirst du automatisch bis zur finalen Terminwahl weitergeleitet.</p>
  130. <div class="control-group">
  131. <label for="courseSelect">Sprachkurs:</label>
  132. <select id="courseSelect">
  133. <option value="">Wähle einen Sprachkurs</option>
  134. <option value="Grundkurs Altgriechisch">Grundkurs Altgriechisch</option>
  135. <option value="Arabisch A2.1">Arabisch A2.1</option>
  136. <option value="Autonomes Sprachenlernen">Autonomes Sprachenlernen</option>
  137. <option value="Bosnisch Kroatisch Serbisch A2">Bosnisch Kroatisch Serbisch A2</option>
  138. <option value="Brasilianisches Portugiesisch B1">Brasilianisches Portugiesisch B1</option>
  139. <option value="Fachübergreifendes Englisch B2">Fachübergreifendes Englisch B2</option>
  140. <option value="Englisch für Geowissenschaften B2">Englisch für Geowissenschaften B2</option>
  141. <option value="Englisch für Geistes- und Sozialwissenschaften B2">Englisch für Geistes- und Sozialwissenschaften B2</option>
  142. <option value="Englisch für Geistes- und Sozialwissenschaften C1">Englisch für Geistes- und Sozialwissenschaften C1</option>
  143. <option value="Englisch in der Wirtschaft B2">Englisch in der Wirtschaft B2</option>
  144. <option value="Englisch in der Wirtschaft C1">Englisch in der Wirtschaft C1</option>
  145. <option value="Französisch A2">Französisch A2</option>
  146. <option value="Französisch B1">Französisch B1</option>
  147. <option value="Französisch B2">Französisch B2</option>
  148. <option value="Französisch C1">Französisch C1</option>
  149. <option value="Italienisch A2">Italienisch A2</option>
  150. <option value="Italienisch B1">Italienisch B1</option>
  151. <option value="Italienisch B2">Italienisch B2</option>
  152. <option value="Grundkurs Latein">Grundkurs Latein</option>
  153. <option value="Lateinkenntnisse">Lateinkenntnisse</option>
  154. <option value="Latinum">Latinum</option>
  155. <option value="Norwegisch B1">Norwegisch B1</option>
  156. <option value="Polnisch A1">Polnisch A1</option>
  157. <option value="Polnisch A2">Polnisch A2</option>
  158. <option value="Russisch A1">Russisch A1</option>
  159. <option value="Russisch A2">Russisch A2</option>
  160. <option value="Russisch B1 für Fremdsprachenlernende">Russisch B1 für Fremdsprachenlernende</option>
  161. <option value="Russisch B2.1">Russisch B2.1</option>
  162. <option value="Spanisch A2">Spanisch A2</option>
  163. <option value="Spanisch B1">Spanisch B1</option>
  164. <option value="Spanisch B2">Spanisch B2</option>
  165. <option value="Spanisch C1">Spanisch C1</option>
  166. <option value="Sprachenlernen im Tandem">Sprachenlernen im Tandem</option>
  167. <option value="Polnisch B2.1">Polnisch B2.1</option>
  168. <option value="Polnisch B1.1">Polnisch B1.1</option>
  169. <option value="Russisch B2.2 für Fremdsprachenlernende">Russisch B2.2 für Fremdsprachenlernende</option>
  170. <option value="Russisch B1 für Herkunftssprechende">Russisch B1 für Herkunftssprechende</option>
  171. <option value="Tschechisch B2.1">Tschechisch B2.1</option>
  172. <option value="Tschechisch B1.1">Tschechisch B1.1</option>
  173. <option value="Tschechisch A1">Tschechisch A1</option>
  174. <option value="Sprache und Kommunikation IIa (Obersorbisch)">Sprache und Kommunikation IIa (Obersorbisch)</option>
  175. <option value="Sprache und Kommunikation IIb (Niedersorbisch)">Sprache und Kommunikation IIb (Niedersorbisch)</option>
  176. <option value="Keltische Studien I">Keltische Studien I</option>
  177. <option value="Keltische Studien III">Keltische Studien III</option>
  178. <option value="Irisch im 21. Jahrhundert">Irisch im 21. Jahrhundert</option>
  179. <option value="Basiskenntnisse Obersorbisch">Basiskenntnisse Obersorbisch</option>
  180. <option value="Basiskenntnisse Niedersorbisch">Basiskenntnisse Niedersorbisch</option>
  181. <option value="Aktivierungskurs Obersorbisch I">Aktivierungskurs Obersorbisch I</option>
  182. </select>
  183. <div class="control-group">
  184. <label class="toggle-switch">
  185. <input type="checkbox" id="enableAutoReload" ${autoReload ? 'checked' : ''}>
  186. <span class="slider"></span>
  187. </label>
  188. <label for="enableAutoReload">Auto-Reload</label>
  189. </div>
  190. <div class="control-group">
  191. <label class="toggle-switch">
  192. <input type="checkbox" id="enableScript" ${isEnabled ? 'checked' : ''}>
  193. <span class="slider"></span>
  194. </label>
  195. <label for="enableScript">Skript aktivieren</label>
  196. </div>
  197. <div id="status"></div>
  198. </div>
  199.  
  200. `;
  201. document.body.appendChild(controlPanel);
  202.  
  203. // Set the selected course
  204. document.getElementById('courseSelect').value = targetCourse || "";
  205.  
  206. // Event listeners
  207. document.getElementById('enableScript').addEventListener('change', (e) => {
  208. isEnabled = e.target.checked;
  209. GM_setValue('isEnabled', isEnabled);
  210. if (isEnabled) {
  211. startScript();
  212. } else {
  213. console.log('Stopped, because Toggle Switch got deactivated');
  214. stopScript();
  215. }
  216. });
  217.  
  218. document.getElementById('enableAutoReload').addEventListener('change', (e) => {
  219. autoReload = e.target.checked;
  220. GM_setValue('autoReload', autoReload);
  221. });
  222.  
  223. document.getElementById('courseSelect').addEventListener('change', (e) => {
  224. const newCourse = e.target.value;
  225. handleCourseChange(newCourse);
  226. });
  227. }
  228.  
  229. function selectSprachenzentrum() {
  230. const radioButtons = document.querySelectorAll('input[type="radio"]');
  231. for (let radio of radioButtons) {
  232. const label = radio.nextElementSibling;
  233. if (label && label.textContent.includes('Sprachenmodule des Sprachenzentrums')) {
  234. if (!radio.disabled) {
  235. radio.click();
  236. return true;
  237. }
  238. return false;
  239. }
  240. }
  241. return false;
  242. }
  243.  
  244. function selectLanguageModule() {
  245. console.log('Starting selectLanguageModule function');
  246. console.log('Target course:', targetCourse);
  247.  
  248. const rows = document.querySelectorAll('table tbody tr');
  249. console.log('Found', rows.length, 'rows in the table');
  250.  
  251. // Uncheck all checkboxes first
  252. rows.forEach((row, index) => {
  253. const checkbox = row.querySelector('input[type="checkbox"]');
  254. if (checkbox && checkbox.checked) {
  255. console.log('Unchecking previously selected course in row', index + 1);
  256. checkbox.click();
  257. }
  258. });
  259.  
  260. for (let i = 0; i < rows.length; i++) {
  261. const row = rows[i];
  262. console.log('Checking row', i + 1);
  263.  
  264. const courseTitleCell = row.querySelector('td:nth-child(3)');
  265.  
  266. if (courseTitleCell) {
  267. const courseTitle = courseTitleCell.textContent.trim();
  268. console.log('Row', i + 1, 'Course Title:', courseTitle);
  269.  
  270. if (courseTitle.includes(targetCourse)) {
  271. console.log('Found matching course in row', i + 1);
  272.  
  273. const checkbox = row.querySelector('input[type="checkbox"]');
  274. if (checkbox) {
  275. console.log('Checkbox found. Disabled:', checkbox.disabled, 'Checked:', checkbox.checked);
  276.  
  277. if (checkbox.disabled) {
  278. console.log('Target course checkbox is disabled. Stopping the script.');
  279. checkbox.scrollIntoView({ behavior: "smooth", block: "center"});
  280. stopScript();
  281. updateStatus('Kurs ist ausgebucht!');
  282. return false;
  283. }
  284.  
  285. if (!checkbox.checked) {
  286. console.log('Clicking checkbox');
  287. checkbox.click();
  288. } else {
  289. console.log('Checkbox already checked');
  290. }
  291. console.log('Course selected successfully');
  292. return true;
  293. } else {
  294. console.log('No checkbox found in this row');
  295. }
  296. }
  297. } else {
  298. console.log('Row', i + 1, 'is missing course title cell');
  299. }
  300. }
  301.  
  302. console.log('No matching course found or all matching courses are unavailable');
  303. return false;
  304. }
  305.  
  306. function updateStatus(message) {
  307. const statusElement = document.getElementById('status');
  308. if (statusElement) {
  309. statusElement.textContent = message;
  310. } else {
  311. console.log('Status update:', message);
  312. }
  313. }
  314.  
  315. function clickWeiter() {
  316. const weiterButton = document.querySelector('input[type="submit"][value="Weiter"]');
  317. if (weiterButton) {
  318. weiterButton.click();
  319. return true;
  320. }
  321. return false;
  322. }
  323. function areAllCheckboxesChecked() {
  324. const checkboxes = document.querySelectorAll('input[type="checkbox"]');
  325. return Array.from(checkboxes).every(checkbox => checkbox.checked);
  326. }
  327.  
  328. function checkAndSelect() {
  329. if (!isEnabled) return false;
  330.  
  331. if (window.location.href.includes('/bookings/group')) {
  332. if (selectSprachenzentrum()) {
  333. return clickWeiter();
  334. }
  335. } else if (window.location.href.includes('/bookings/course')) {
  336. if (selectLanguageModule()) {
  337. if (clickWeiter()) {
  338. console.log('Successfully selected language module and clicked "Weiter"');
  339. return true;
  340. } else {
  341. console.log('Selected language module but failed to click "Weiter"');
  342. return false;
  343. }
  344. } else {
  345. console.log('Language Course not found or fully booked');
  346. stopScript();
  347. return false;
  348. }
  349. } else if (window.location.href.includes('/bookings/details')) {
  350. window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
  351. if (areAllCheckboxesChecked()){
  352. console.log('All Checkboxes checked!');
  353. const speichernButton = document.querySelector('input[type="submit"][value="Speichern"]');
  354. if (speichernButton) {
  355. speichernButton.click();
  356. }
  357. }
  358. return true;
  359. } else if (window.location.href.includes('/info/')) {
  360. window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
  361. console.log('Info Page reached. Stopping');
  362. return true;
  363. }
  364. return false;
  365. }
  366.  
  367. function reloadAndCheck() {
  368. if (isEnabled) {
  369. if (!checkAndSelect() && autoReload && window.location.href.includes('/bookings/group')) {
  370. console.log('Target not available. Reloading in 1 second...');
  371. setTimeout(() => {
  372. location.reload();
  373. }, RELOAD_INTERVAL);
  374. }
  375. }
  376. }
  377.  
  378. function startScript() {
  379. console.log('Starting script');
  380. if (window.location.href.includes('/info/')){
  381. window.location.href = 'https://tool.uni-leipzig.de/einschreibung/bookings/group';
  382. }
  383. if (!intervalId) {
  384. intervalId = setInterval(reloadAndCheck, RELOAD_INTERVAL);
  385. reloadAndCheck(); // Run once immediately
  386. }
  387. }
  388.  
  389. function stopScript() {
  390. console.log('Stopping script');
  391. if (intervalId) {
  392. clearInterval(intervalId);
  393. intervalId = null;
  394. }
  395. isEnabled = false;
  396. GM_setValue('isEnabled', false);
  397. const enableScriptCheckbox = document.getElementById('enableScript');
  398. if (enableScriptCheckbox) {
  399. enableScriptCheckbox.checked = false;
  400. }
  401. }
  402.  
  403. createControlPanel();
  404. if (isEnabled && window.location.href.includes('/bookings/')) {
  405. console.log('Script is enabled, starting automatically');
  406. startScript();
  407. }
  408. if (isEnabled && window.location.href.includes('/info/')) {
  409. console.log('Scrolling down!')
  410. window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
  411. stopScript();
  412. }
  413. })();