您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Изменение подсчёта баллов и местами дизайна, а также добавление/доработка расписания
// ==UserScript== // @name Better OriCOCKs // @version 3.0.8 // @description Изменение подсчёта баллов и местами дизайна, а также добавление/доработка расписания // @source https://github.com/Psychosoc1al/better-oricocks // @author Antonchik // @license MIT // @namespace https://github.com/Psychosoc1al // @match https://orioks.miet.ru/* // @icon https://orioks.miet.ru/favicon.ico // @run-at document-body // @connect miet.ru // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // ==/UserScript== (() => { "use strict"; /** * Changes the body width to make the interface wider and more readable. * Separate function for convenience to be used on different pages */ const changeBodyWidth = function () { for (const sheet of document.styleSheets) if ( sheet.href?.includes( "https://orioks.miet.ru/libs/bootstrap/bootstrap.min.css", ) ) { for (const element of sheet.cssRules) if (element.cssText.includes("1170px")) element["cssRules"][0].style.width = "1330px"; return; } }; /** * Save a key-value pair to the storage * * @param {string} key - The key to save * @param {string | Object} value - The value to save */ const saveKeyValue = function (key, value) { // noinspection JSUnresolvedReference GM.setValue(key, value); }; /** * Retrieves the value associated with the given key * * @param {string} key - The key to retrieve the value for * @return {Promise<string>} - The value associated with the given key */ const loadValueByKey = function (key) { // noinspection JSUnresolvedReference,JSCheckFunctionSignatures return GM.getValue(key); }; // check to know if we are on the page with grades if (document.URL.includes("student/student")) { const group = document .querySelector('select[name="student_id"] option[selected]') .innerText.split(" ")[0]; const weeksNumbers = { "1 числитель": 0, "1 знаменатель": 1, "2 числитель": 2, "2 знаменатель": 3, }; /** * Sends a request to the schedule server * * @param {string} url - The URL to send the request to * @param {string} method - The request method * @param {string} cookie - The cookie to include in the request headers * @return {Promise<Object>} A promise that resolves with the response text */ const sendRequest = function (url, method, cookie = "") { // noinspection JSUnresolvedReference,JSUnusedGlobalSymbols return GM.xmlHttpRequest({ url: url, method: method, headers: { "Content-Type": "application/x-www-form-urlencoded", Cookie: cookie, }, data: `group=${group}`, onload: function (responsePromise) { return responsePromise; }, onerror: function (response) { console.log(response); }, }); }; /** * Adjusts a number to be integer if possible and rounded to at most 2 decimal places if not * * @param {number} number - The number to be adjusted * @return {string} The adjusted number as a string */ const numberToFixedString = function (number) { if (!number) return "0"; let stringedNumber = number.toFixed(2); while (stringedNumber.endsWith("0")) stringedNumber = stringedNumber.slice(0, -1); if (stringedNumber.endsWith(".")) stringedNumber = stringedNumber.slice(0, -1); return stringedNumber; }; /** * Gets the grade string representation and its type (projection to five-ball system) * * @param {number} gradeRatio - the grade ratio (grade / maxGrade) * @param {string} controlForm - the control type to check if it is a credit * * @return {[string, number]} The new grade class as a string */ const getGradeNameAndType = function (gradeRatio, controlForm) { const isCredit = controlForm === "Зачёт"; if (gradeRatio < 0.5) { if (gradeRatio < 0.2) return ["Не зачтено", 1]; return ["Не зачтено", 2]; } else if (gradeRatio < 0.7) return isCredit ? ["Зачтено", 5] : ["Удовлетворительно", 3]; else if (gradeRatio < 0.86) return isCredit ? ["Зачтено", 5] : ["Хорошо", 4]; else return isCredit ? ["Зачтено", 5] : ["Отлично", 5]; }; /** * Changes the size of numeric and string grade fields */ const changeGradeFieldsSizes = function () { for (const sheet of document.styleSheets) if ( sheet.href?.includes( "https://orioks.miet.ru/controller/student/student.css", ) ) { for (const element of sheet.cssRules) { if (element.selectorText === ".w46") element.style.width = "34px"; if ( [".grade", "#bp"].includes(element["selectorText"]) ) { element.style.width = "45px"; element.style.padding = "3px"; } } break; } document.querySelector('span[style="width: 60px"]').style.width = "fit-content"; }; /** * Sets the schedule CSS. */ const setScheduleCSS = function () { for (const sheet of document.styleSheets) if ( sheet.href?.includes( "https://orioks.miet.ru/libs/bootstrap/bootstrap.min.css", ) ) { for (const element of sheet.cssRules) if ( element.cssText.startsWith(".table") && element.style && element.style.marginTop ) { element.style.marginTop = "5px"; } break; } document .querySelectorAll('tr[ng-repeat="c in data"] span') .forEach((elem) => (elem.style["white-space"] = "pre-line")); }; /** * Gets the schedule by sending a request and passing the protection(?) with setting the cookie * * @return {Promise<Object>} A JSON object containing the schedule */ const getSchedule = function () { return sendRequest("https://miet.ru/schedule/data", "POST").then( (responseObject) => { const cookie = responseObject.responseText.match(/wl=.*;path=\//); if (cookie) return sendRequest( "https://miet.ru/schedule/data", "POST", cookie[0], ).then((responseObject) => JSON.parse(responseObject.responseText), ); return JSON.parse(responseObject.responseText); }, ); }; /** * Parses the schedule data received from the server * * @return {Promise<Array<Object>>} An array of parsed and formatted schedule elements */ const parseSchedule = function () { return getSchedule().then((responseJSON) => { const parsedSchedule = []; for (const responseJSONElement of responseJSON["Data"]) { const scheduleElement = {}; scheduleElement["name"] = responseJSONElement["Class"]["Name"]; scheduleElement["teacher"] = responseJSONElement["Class"]["TeacherFull"]; scheduleElement["dayNumber"] = responseJSONElement["Day"]; scheduleElement["weekNumber"] = responseJSONElement["DayNumber"]; scheduleElement["room"] = responseJSONElement["Room"]["Name"]; scheduleElement["lessonNumber"] = responseJSONElement["Time"]["Time"]; scheduleElement["startTime"] = new Date( responseJSONElement["Time"]["TimeFrom"], ).toLocaleTimeString("ru", { hour: "2-digit", minute: "2-digit", }); scheduleElement["endTime"] = new Date( responseJSONElement["Time"]["TimeTo"], ).toLocaleTimeString("ru", { hour: "2-digit", minute: "2-digit", }); parsedSchedule.push(scheduleElement); } return parsedSchedule; }); }; /** * Updates the schedule and processes it */ const processSchedule = function () { loadValueByKey("schedule").then((schedule) => { parseSchedule().then((parsedSchedule) => { saveKeyValue("schedule", parsedSchedule); if (!schedule) window.location.reload(); }); if (schedule) { const parsedSchedule = JSON.parse(JSON.stringify(schedule)); const closestLessons = getClosestLessons(parsedSchedule); setSchedule(closestLessons); } }); }; /** * Sets the schedule based on the current time and day or on finds the closest lessons * * @param {Object} schedule - The whole schedule object * @param {number} daysOffset - The offset in days from the current day to start search * @param {boolean} weekChanged - Whether the week has changed while searching the closest day * @return {Object[]} The closest two days lessons list */ const getClosestLessons = function ( schedule, daysOffset = 0, weekChanged = false, ) { let currentTime, currentDayNumber; let date = new Date(); let utcDate = new Date( date.getTime() + date.getTimezoneOffset() * 60 * 1000, ); date = new Date(utcDate.getTime() + 3 * 60 * 60 * 1000); if (daysOffset === 0) { currentTime = date.toLocaleTimeString("ru", { hour: "2-digit", minute: "2-digit", hour12: false, }); currentDayNumber = date.getDay(); } else { date.setDate(date.getDate() + daysOffset); currentTime = "00:00"; currentDayNumber = date.getDay(); } let stringCurrentWeek = document .querySelector(".small") .innerText.split("\n")[1]; if (!stringCurrentWeek) stringCurrentWeek = document .querySelector(".small") .innerText.split(" ") .slice(3) .join(" "); let searchWeekNumber = weeksNumbers[stringCurrentWeek]; let searchDayNumber = currentDayNumber - 1; let closestLessons = []; let nextOffset = daysOffset; if (typeof searchWeekNumber === "undefined") return []; if (currentDayNumber === 0) { searchWeekNumber = ++searchWeekNumber % 4; searchDayNumber = 0; nextOffset++; weekChanged = true; } else if (weekChanged) searchWeekNumber = ++searchWeekNumber % 4; while (!closestLessons.length) { searchDayNumber = ++searchDayNumber % 7; nextOffset++; if (searchDayNumber === 0) { searchWeekNumber = ++searchWeekNumber % 4; searchDayNumber = 1; nextOffset++; weekChanged = true; } closestLessons = schedule.filter( (lesson) => lesson.dayNumber === searchDayNumber && lesson.weekNumber === searchWeekNumber && (currentDayNumber === searchDayNumber ? lesson.endTime >= currentTime : true) && !lesson.teacher.includes("УВЦ"), ); } closestLessons.sort((a, b) => { return a.lessonNumber > b.lessonNumber ? 1 : -1; }); date = new Date(); date.setDate(date.getDate() + nextOffset - 1); const stringDate = date.toLocaleDateString("ru", { weekday: "long", day: "2-digit", month: "2-digit", }); if (daysOffset === 0) return [ { date: stringDate, lessons: closestLessons, }, ].concat(getClosestLessons(schedule, nextOffset, weekChanged)); return [ { date: stringDate, lessons: closestLessons, }, ]; }; /** * Updates the grade fields based on the newest data */ const updateGrades = function () { const source = document.querySelector("#forang"); const jsonData = JSON.parse(source.textContent); const disciplines = jsonData["dises"]; for (const element of disciplines) { const controlPoints = element["segments"][0]["allKms"]; const grade = element["grade"]; const controlForm = element["formControl"]["name"]; const maxPossibleSum = element["mvb"]; let sum = 0; for (const element of controlPoints) { const balls = element["balls"][0]; if (balls && balls["ball"] > 0) sum += balls["ball"]; } grade["b"] = numberToFixedString(sum); // current ball grade["p"] = numberToFixedString((sum / maxPossibleSum) * 100); // current percentage // [maximal grade ("из ..."), class attribute for coloring] [grade["w"], grade["o"]] = getGradeNameAndType( sum / maxPossibleSum, controlForm, ); } source.textContent = JSON.stringify(jsonData); }; /** * Collapses multiplied lessons with the same name into one * * @param closestDays - The list of closest days with lessons (see {@link getClosestLessons()}) * @return {Object[]} The list of closest days with refactored lessons */ const collapseDuplicatedLessons = function (closestDays) { for (const day of closestDays) { const collapsedLessons = []; let currentLesson; let currentLessonNumber = 0; let lessonCount = 1; for (let i = 0; i < day.lessons.length; i++) if (day.lessons[i].name === day.lessons[i + 1]?.name) lessonCount++; else { if (lessonCount > 1) { currentLesson = day.lessons[currentLessonNumber]; let name = currentLesson.name; let amountPart = `(${lessonCount} пар${ lessonCount < 5 ? "ы" : "" })`; if (name.indexOf("[") !== -1) name = name.replace("[", amountPart + " ["); else name += amountPart; currentLesson.name = name; collapsedLessons.push(currentLesson); } else collapsedLessons.push( day.lessons[currentLessonNumber], ); currentLessonNumber += lessonCount; lessonCount = 1; } day.lessons = collapsedLessons; } return closestDays; }; /** * Sets the schedule based on the closest lessons * * @param closestDays - The list of closest days with lessons (see {@link getClosestLessons()}) */ const setSchedule = function (closestDays) { const source = document.querySelector("#forang"); const jsonData = JSON.parse(source.textContent); const schedule = []; closestDays = collapseDuplicatedLessons(closestDays); for (let i = 0; i < closestDays.length; i++) { schedule[i] = []; schedule[i][0] = closestDays[i].date; schedule[i][1] = []; for (const lesson of closestDays[i].lessons) { let lessonName, lessonType; let lessonTypeMatch = lesson.name.match(/\[(.*)]/); if (lessonTypeMatch) { lessonName = lesson.name.match(/(.*) \[?/)[1]; lessonType = lessonTypeMatch[1]; } else { lessonName = lesson.name; lessonType = ""; } schedule[i][1].push({ name: `${lessonName} ► ${lesson.teacher} `, type: lessonType, location: lesson.room, time: lesson.startTime === "12:00" ? "12:00/30" : lesson.startTime, }); } } jsonData["schedule"] = schedule; source.textContent = JSON.stringify(jsonData); }; /** * Executes the necessary actions when the page is opened. */ const onPageOpen = function () { updateGrades(); processSchedule(); changeGradeFieldsSizes(); changeBodyWidth(); setScheduleCSS(); }; onPageOpen(); } else if (document.URL.includes("orioks.miet.ru")) changeBodyWidth(); })();