Greasy Fork is available in English.

Schoolbook Tools

Mess around with schoolbook.ge

// ==UserScript==
// @name         Schoolbook Tools
// @description  Mess around with schoolbook.ge
// @icon         https://eservices.schoolbook.ge/Images/sb-logo-blue.png
// @author       Naviamold
// @license      MIT
// @version      2.6
// @namespace    https://github.com/naviamold1
// @homepage     https://greasyfork.org/en/scripts/459858-schoolbook-tools
// @match        *://*.schoolbook.ge/*
// @exclude      *://schoolbook.ge/*
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==
(function () {
	'use strict';
	// do not change anything here if you don't know what you are doing!
	const settings = {
		// @ts-ignore
		changeGrade: GM_getValue('changeGrade'),
		// @ts-ignore
		changeAttendance: GM_getValue('changeAttendance'),
		// @ts-ignore
		liveGradeUpdate: GM_getValue('liveGradeUpdate'),
		// @ts-ignore
		gradeViewer: GM_getValue('gradeViewer'),
		// @ts-ignore
		hideComments: GM_getValue('hideComments'),
		// @ts-ignore
		attendanceViewer: GM_getValue('attendanceViewer')
	};
	// Add a button to open the settings dialog
	$('#pageBody')
		?.prepend(`<button data-open-modal id="openSettingsButton">Open Settings</button>

      <dialog data-modal id="schoolbook_tools_settings_dialog">
        <h2>Script Settings</h2>
        <label for="changeGrade">(CLIENT SIDE) Change Grade:</label>
        <input type="text" id="changeGrade" value="${
					settings.changeGrade ?? ''
				}">
        <br>
        <label for="changeAttendance">(CLIENT SIDE) Change Attendance:</label>
        <input type="text" id="changeAttendance" value="${
					settings.changeAttendance ?? ''
				}">
        <br>
        <label for="liveGradeUpdate">Live Grade Update:</label>
        <input type="checkbox" id="liveGradeUpdate" ${
					settings.liveGradeUpdate ? 'checked' : ''
				}>
        <br>
        <label for="gradeViewer">Grade Viewer:</label>
        <input type="checkbox" id="gradeViewer" ${
					settings.gradeViewer ? 'checked' : ''
				}>
        <br>
        <label for="attendanceViewer">Attendance Viewer:</label>
        <input type="checkbox" id="attendanceViewer" ${
					settings.attendanceViewer ? 'checked' : ''
				}>
        <br>
        <label for="hideComments">(CLIENT SIDE) Hide Comments:</label>
        <input type="checkbox" id="hideComments" ${
					settings.hideComments ? 'checked' : ''
				}>
        <br>
        <a href='https://github.com/Naviamold1/schoolbook-filterlist'>Block Ads</a>
        <br>
        <br>
        <button data-close-modal id="saveSettingsButton">Save Settings</button>
        <button data-close-modal id="cancelSettingsButton">Cancel</button>
      </dialog>`);
	const sanitizeInput = (query) => {
		const inp = document.querySelector(query);
		if (inp && inp.value.trim() !== '') {
			return inp.value.trim();
		}
		return '';
	};
	const dialog = document.querySelector('#schoolbook_tools_settings_dialog');
	document
		.querySelector('#openSettingsButton')
		.addEventListener('click', () => dialog.showModal());
	document
		.querySelector('#cancelSettingsButton')
		.addEventListener('click', () => dialog.close());
	document
		.querySelector('#saveSettingsButton')
		.addEventListener('click', () => {
			// @ts-ignore
			GM_setValue('changeGrade', sanitizeInput('#changeGrade'));
			// @ts-ignore
			GM_setValue('changeAttendance', sanitizeInput('#changeAttendance'));
			// @ts-ignore
			GM_setValue(
				'liveGradeUpdate',
				document.querySelector('#liveGradeUpdate').checked
			);
			// @ts-ignore
			GM_setValue(
				'gradeViewer',
				document.querySelector('#gradeViewer').checked
			);
			// @ts-ignore
			GM_setValue(
				'attendanceViewer',
				document.querySelector('#attendanceViewer').checked
			);
			// @ts-ignore
			GM_setValue(
				'hideComments',
				document.querySelector('#hideComments').checked
			);
			document.querySelector('#schoolbook_tools_settings_dialog').close();
			window.location.reload();
		});
	const totalAvgGradePath = '#saertosashualo > span, .otherRow > span';
	const totalAvgAttendPath = '.sec span';
	// Better Timetable
	const colors = ['#BE2727', '#F5FF2D', '#15D13D', ''];
	const textColors = ['#E0E6E1', '#1C0316', '#1C0316', ''];
	const currentTime = new Date().getTime();
	const endTime = currentTime + 10 * 1000;
	let checkExist = setInterval(() => {
		if (
			document.querySelectorAll(
				'#daysList > div > div.owl-stage-outer > div > div > div > a > ol'
			).length >= 5
		) {
			clearInterval(checkExist);
			const subjects = document.querySelectorAll(
				'#daysList > div > div.owl-stage-outer > div > div > div > a > ol > li'
			);
			subjects.forEach((elem) => {
				let currentColorIndex = 0;
				elem.addEventListener('click', () => {
					const currentColor = colors[currentColorIndex];
					const currentTextColor = textColors[currentColorIndex];
					elem.style.backgroundColor = currentColor;
					elem.style.color = currentTextColor;
					currentColorIndex = (currentColorIndex + 1) % colors.length;
					let subj_name = elem.innerText;
					document
						.querySelectorAll(
							'#subs > div.owl-carousel.subjects.owl-loaded.owl-drag > div.owl-stage-outer > div > div > div > span'
						)
						.forEach((e) => {
							if (
								subj_name.replace(/ \d\d:\d\d/gm, '').replace(' ', '') ==
								e.innerText.replace(' ', '')
							) {
								let elem = e.parentNode;
								if (!elem) return;
								elem.style.backgroundColor = currentColor;
								elem.style.color = currentTextColor;
								$(elem.parentNode).insertBefore(
									document.querySelector(
										'#subs > div.owl-carousel.subjects.owl-loaded.owl-drag > div.owl-stage-outer > div > div:nth-child(1)'
									)
								);
							}
						});
				});
			});
		} else if (endTime < new Date().getTime()) {
			clearInterval(checkExist);
			console.log('not found in specified time.');
			return;
		} else {
			console.log('waiting for element to be present…');
		}
	}, 100);
	// Logic Functions
	function newGrade(grade) {
		let grades = document.querySelectorAll(`.avg_value, ${totalAvgGradePath}`);
		grades.forEach((val) => (val.textContent = grade));
	}
	function newAttendance(attendance) {
		let attendances = document.querySelectorAll(`.prc, ${totalAvgAttendPath}`);
		attendances.forEach((val) => (val.textContent = attendance));
	}
	function removeComments() {
		let comments = document.querySelectorAll(
			'.notificationsList, .notificationsListalter, .homeworkContent'
		);
		comments.forEach((mes) => (mes.style.display = 'none'));
	}
	function getAvg(query) {
		let numbers = [];
		query.forEach((el) => {
			let text = el.textContent;
			if (text) {
				let number = parseInt(text.replace(/\D/g, ''), 10);
				if (!isNaN(number)) {
					numbers.push(number);
				}
			}
		});
		const sum = numbers.reduce((acc, val) => acc + val, 0);
		const avg = numbers.length > 0 ? sum / numbers.length : 0;
		return Math.round(avg * 100) / 100;
	}
	function waitUntilAvgPresent(selector, elem, close = false) {
		const currentTime = new Date().getTime();
		const endTime = currentTime + 5 * 1000;
		let checkExist = setInterval(() => {
			if (document.querySelectorAll(selector).length) {
				clearInterval(checkExist);
				const avg = getAvg(document.querySelectorAll(selector));
				elem.textContent = avg.toString();
				// @ts-ignore
				close && closeGradeDialog();
			} else if (endTime < new Date().getTime()) {
				clearInterval(checkExist);
				console.log('not found in specified time.');
				return;
			} else {
				console.log('waiting for element to be present…');
			}
		}, 100);
	}
	function liveUpdate() {
		let totalGrade = document.querySelector('#saertosashualo span');
		totalGrade && totalGrade.click();
		waitUntilAvgPresent(
			'#cnt > div.div_container_grades > table > tbody > tr > td:nth-child(4)',
			totalGrade,
			true
		);
	}
	let ftSpan = document.createElement('span');
	document.querySelector('#ft')?.appendChild(ftSpan);
	function gradeSpier() {
		$('#pageBody')?.prepend(`
        <form id="gmSomeID">
          <input type="search" placeholder="Grade Viewer - User" list="datalistOptions" id="gminput">
          <button id="gmview">View</button>
          <datalist id='datalistOptions'>
        </form>
        `);
		document.querySelector('#gmview').addEventListener('click', (e) => {
			e.preventDefault();
			let val = document.querySelector('#gminput').value;
			if (val) {
				// @ts-ignore
				gradeclick(val, -1);
				waitUntilAvgPresent(
					'#cnt > div.div_container_grades > table > tbody > tr > td:nth-child(4)',
					ftSpan
				);
			}
		});
	}
	function attendanceSpier() {
		$('#pageBody')?.prepend(`
        <form id="gmSomeID2">
          <input type="search" placeholder="Attendance Viewer - User" list='datalistOptions' id="gminput2">
          <input type="search" placeholder="Subject ID" list='datalistOptionsAttend' id="gminput3">
          <button id="gmview2">View</button>
          <datalist id='datalistOptionsAttend'>
        </form>
        `);
		document.querySelector('#gmview2').addEventListener('click', (e) => {
			e.preventDefault();
			let val = document.querySelector('#gminput2').value;
			let val2 = document.querySelector('#gminput3').value;
			// @ts-ignore
			val && val2 && attendanceclick(val, val2);
		});
	}
	const currentPage = window.location.pathname;
	if (
		currentPage === '/Parent/Index' ||
		currentPage === '/Parent/AllSubjects'
	) {
		if (settings.changeGrade) {
			newGrade(settings.changeGrade);
		}
		if (settings.changeAttendance) {
			newAttendance(settings.changeAttendance);
		}
		if (settings.liveGradeUpdate) {
			liveUpdate();
		}
		if (settings.attendanceViewer || settings.gradeViewer) {
			const main = async () => {
				try {
					const options = {
						method: 'POST',
						headers: {
							accept: 'application/json, text/javascript, */*; q=0.01',
							'content-type': 'application/json'
						},
						body: '{"pageSize":1000,"filter":"","initValue":null}'
					};
					const req = await fetch(
						'https://eservices.schoolbook.ge/SchoolBook/SchoolPersonsList',
						options
					);
					const res = await req.json();
					res['mas'].forEach((val) => {
						$('#datalistOptions')?.append(
							`<option value="${val.value}" label="${val.text}">`
						);
					});
					document
						.querySelectorAll(
							'#tablestatistic > div.owl-carousel.tablestat.owl-loaded.owl-drag > div.owl-stage-outer > div > div:not(.cloned) > div > div'
						)
						.forEach((val) => {
							$('#datalistOptionsAttend')?.append(
								`<option value="${val.id.replace('sagani_', '')}" label="${
									val.querySelector('.subject_name').innerText
								}">`
							);
						});
				} catch (error) {
					console.error(error);
				}
			};
			main();
			if (settings.gradeViewer) {
				gradeSpier();
			}
			if (settings.attendanceViewer) {
				attendanceSpier();
			}
		}
	}
	if (currentPage === '/Parent/Messages' && settings.hideComments) {
		removeComments();
	}
})();