MB_plugin

exporting MB timetables to calendar file

// ==UserScript==
// @name         MB_plugin
// @version      2024-04-09
// @author       zixiao_hi
// @license GNU GPLv3
// @description    exporting MB timetables to calendar file
// @match        https://bnds.managebac.cn/student/timetables/*
// @match        https://bnds.managebac.cn/student/timetables
// @run-at          document-body
// @grant           GM_info
// @grant           GM_addStyle
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_xmlhttpRequest
// @grant           GM_registerMenuCommand
// @namespace https://greasyfork.org/users/1285841
// ==/UserScript==

(function() {




//  newVideoLoaded();
//  console.log("jd3indin3di3")
    function get_cal(){
        function extractTimetableToJson() {
            const table = document.querySelector('.table');
            const headings = table.querySelectorAll('thead th');
            const rows = table.querySelectorAll('tbody tr');

            // Extract day headings
            const days = Array.from(headings).map((th, index) => index > 0 ? th.innerText.trim() : null).filter(day => day);

            let timetable = {
                periods: []
            };

            rows.forEach(row => {
                const periodCell = row.querySelector('.period-label');
                const periodClasses = row.querySelectorAll('.period-classes');

                if (periodCell) {
                    let periodInfo = {
                        period: periodCell.querySelector('.numeric-circle') ? periodCell.querySelector('.numeric-circle').innerText.trim() : 'Homeroom',
                        classes: {}
                    };

                    periodClasses.forEach((classCell, index) => {
                        if (days[index]) {
                            const className = classCell.querySelector('.class-name') ? classCell.querySelector('.class-name').innerText.trim() : 'Free Period';
                            const grade = classCell.querySelector('.class-grade') ? classCell.querySelector('.class-grade').innerText.trim() : 'Not specified';
                            const ellipses = classCell.querySelectorAll('.text-ellipsis:not(.class-grade)');
                            let time = '', teacher = '', location = 'Not specified';

                            ellipses.forEach((el) => {
                                const text = el.innerText.trim();
                                // Check if text starts with a number (for time or location)
                                if (/^\d/.test(text)) {
                                    // Time format check
                                    if (/\d{1,2}:\d{2}(AM|PM) - \d{1,2}:\d{2}(AM|PM)/.test(text)) {
                                        time = text;
                                    } else { // Assume location if not time
                                        location = text;
                                    }
                                } else  { // If not already assigned as time or location
                                    teacher = text; // Assume remaining text is teacher's name
                                }
                            });

                            periodInfo.classes[days[index]] = {
                                name: className,
                                grade: grade,
                                time: time,
                                teacher: teacher,
                                location: location
                            };
                        }
                    });

                    timetable.periods.push(periodInfo);
                }
            });

            return JSON.stringify(timetable, null, 2);
        }
        let time_table_JSON = extractTimetableToJson();
        console.log(time_table_JSON);

        // Assuming extractTimetableToJson() has been run and stored in time_table_JSON

        // Parse the JSON to work with it as an object
        let timetable = JSON.parse(time_table_JSON);

        // Define a map for period start times, excluding period 5 (lunchtime)
        const periodStartTimes = {
            "1": "8:00AM",
            "2": "8:55AM",
            "3": "9:50AM",
            "4": "10:45AM",
            "6": "12:35PM",
            "7": "1:30PM",
            "8": "2:25PM",
            "9": "3:25PM"
        };

        function findSubject(courseName, subjects) {
            subjects = [
                "Math",
                "Calculas",
                "Statistics","Physics",
                "English",
                "History",
                "Geography",
                "Spanish",
                "French",
                "German",
                "Latin",
                "Art",
                "Music",
                "Physical Education",
                "Computer Science",
                "Economics",
                "Psychology",
                "Chinese",
                "CCC",
                "Homeroom"
            ];

            const lowerCaseCourseName = courseName.toLowerCase();
            for (const subject of subjects) {
                if (lowerCaseCourseName.includes(subject.toLowerCase())) {
                    return subject;
                }
            }
            return "Subject not found";
        }



        // Function to calculate end time given start time and duration (45 minutes)
        function calculateEndTime(startTime) {
            let [hour, minutePeriod] = startTime.split(':');
            let minutes = parseInt(minutePeriod.substring(0, 2));
            let period = minutePeriod.substring(2);
            minutes += 45; // Each period lasts for 45 minutes

            if (minutes >= 60) {
                hour = parseInt(hour) + Math.floor(minutes / 60);
                minutes = minutes % 60;
            }

            if (hour >= 12 && period === 'AM') {
                period = 'PM';
            }

            return `${hour}:${minutes.toString().padStart(2, '0')}${period}`;
        }

        // Update the timetable with times for free periods
        timetable.periods.forEach(period => {
            Object.keys(period.classes).forEach(day => {
                if (period.classes[day].name === "Free Period") {
                    const startTime = periodStartTimes[period.period];
                    const endTime = calculateEndTime(startTime);
                    period.classes[day].time = `${startTime} - ${endTime}`;
                }else{
                    period.classes[day].name=findSubject(period.classes[day].name)
                }
            });
        });

        // Convert the updated object back to JSON
        time_table_JSON = JSON.stringify(timetable, null, 2);


        console.log(time_table_JSON);


        function _45_min_later(date) {
            // Make a copy of the date to avoid mutating the original date
            const newDate = new Date(date);
            // Add 45 minutes to the new date
            newDate.setMinutes(newDate.getMinutes() + 45);
            return newDate;
        }
        function parseDateString(dateString) {
            // Split the input string by spaces
            const parts = dateString.split(" ");

            // Define month abbreviations mapping
            const monthAbbreviations = {
                "Jan": 0, "Feb": 1, "Mar": 2, "Apr": 3,
                "May": 4, "Jun": 5, "Jul": 6, "Aug": 7,
                "Sep": 8, "Oct": 9, "Nov": 10, "Dec": 11
            };

            // Parse day, month, year, time
            const day = parseInt(parts[2]);
            const month = monthAbbreviations[parts[1]];
            const year = new Date().getFullYear(); // Assuming current year
            const timeParts = parts[3].split(":");
            let hours = parseInt(timeParts[0]);
            const minutes = parseInt(timeParts[1]);
            const ampm = dateString.substring(dateString.length-2,dateString.length)

            // Adjust hours for PM
            if (ampm === "PM" && hours < 12) {
                hours += 12;
            }

            // Create and return the Date object
            return new Date(year, month, day, hours, minutes);
        }

        // Test the function

        const dateString = "Thu, Mar 28 2:25PM";
        const dateVariable = parseDateString(dateString);
        console.log(dateVariable);


        function generateICSFromTimetable(timetableJSON) {
            //  const timetable = JSON.parse(timetableJSON);
            const icsEvents = [];

            timetable.periods.forEach(period => {
                Object.entries(period.classes).forEach(([date, classInfo]) => {
                    if (classInfo.time) {
                        const [startTime, endTime] = classInfo.time.split(" - ");
                        const [startHour, startMinute] = startTime.split(/[:AMPM]+/);
                        const [endHour, endMinute] = endTime.split(/[:AMPM]+/);
                        /*
                        const startDate = parseDateString(date + " " + startTime);
                        const endDate = _45_min_later(startDate);
                        //console.log(date + " " + startTime)
                        const formattedStartDate = startDate.toISOString().replace(/-|:|\.\d+Z/g, '').slice(0, 15);
                        const formattedEndDate = endDate.toISOString().replace(/-|:|\.\d+Z/g, '').slice(0, 15);
                        */
                        const startDate = parseDateString(date + " " + startTime);
                        // Adjusting for the 8-hour time difference for Perth
                        startDate.setHours(startDate.getHours() + 8);

                        const endDate = _45_min_later(startDate);
                        // Adjusting for the 8-hour time difference for Perth
                        //              endDate.setHours(endDate.getHours() + 8);

                        const formattedStartDate = startDate.toISOString().replace(/-|:|\.\d+Z/g, '').slice(0, 15);
                        const formattedEndDate = endDate.toISOString().replace(/-|:|\.\d+Z/g, '').slice(0, 15);




                        const event = `BEGIN:VEVENT\n` +
                        `DTSTART:${formattedStartDate}\n` +
                        `DTEND:${formattedEndDate}\n` +
                        `SUMMARY:${classInfo.name}\n` +
                        //                  `DESCRIPTION:Period ${period.period} with ${classInfo.teacher} in ${classInfo.location}\n` +
                        `LOCATION:${classInfo.location}\n` +
                        `END:VEVENT\n`;
                        icsEvents.push(event);
                    }
                });
            });

            const icsFileContent = `BEGIN:VCALENDAR\n` +
            `VERSION:2.0\n` +
            `PRODID:-//Your Organization//Your Calendar//EN\n` +
            `${icsEvents.join('')}` +
            `END:VCALENDAR`;

            const blob = new Blob([icsFileContent], { type: 'text/calendar' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');
            link.href = url;
            link.download = 'timetable.ics';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }

        // Assuming `time_table_JSON` is a JSON string of your timetable
        //const time_table_JSON = `{"periods": [...]}`; // Replace [...] with your actual JSON content
        generateICSFromTimetable(timetable);


    }
    var btn =document.getElementsByClassName("fusion-card timetable-wrapper")[0].getElementsByClassName("title")[0]
    btn.innerText='Export timetable'
    btn.style.background='#1570ef'
    btn.addEventListener("click",
        function(e){
            get_cal()
        }
    )



})();