IVE Att & Abs Calculator

Precisly determine and control the time you can miss.

当前为 2018-09-13 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         IVE Att & Abs Calculator
// @namespace    https://ive.miklet.pro
// @version      4.0.3
// @description  Precisly determine and control the time you can miss.
// @author       Miklet
// @license      MIT
// @match        *://myportal.vtc.edu.hk/*
// @match        *://ive.miklet.pro/*
// @require      https://code.jquery.com/jquery-latest.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js
// @require      https://greasyfork.org/scripts/372057-omichelsen-compare-versions/code/omichelsencompare-versions.js
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_registerMenuCommand
// @grant        GM_info
// @grant        unsafeWindow
// @supportURL   https://ive.miklet.pro/document.html
// @run-at       document-end
// ==/UserScript==


/* global $, fetch, GM_info, GM_registerMenuCommand, GM_getValue, GM_setValue, GM_deleteValue, wptheme_QuickLinksShelf */

// Declare global variable
var data = null,
    c_id, c_name, ini = false;
// Declarea element variable
var calc_btn, sbj_info_div, sbj_info, sbj_edit_time_btn, toggle_btn, sbj_dashboard_div, sbj_dashboard, sbj_select_btn;
// Unit of Time
const u_hour = 'hr',
    u_minute = 'min',
    version = GM_info.script.version,
    invokeUrl = 'https://eogv4z6sv3.execute-api.ap-southeast-1.amazonaws.com/Prod';

// Run the nessesary function for userscript
function initialize() {
    data = GM_getValue(getStuId()) ? JSON.parse(GM_getValue(getStuId())) : setDefault();
    addToolbarCommand();
    createElement();
    GM_info.scriptHandler !== 'Violentmonkey' ? window.alert('WARNING! Since the version 3.0 of IVE att & abs calculator was require Violentmonkey! And your userscript manager was not Violentmonkey.This may cause the userscript not working properly.') : '';
}

// Get student ID number
function getStuId() {
    return wptheme_QuickLinksShelf.cookieName.substring(0, 9);
}

function addToolbarCommand() {
    if (!data.secret) {
        GM_registerMenuCommand('登入帳戶', function() { bindAccount(); });
    }
    else {
        //GM_registerMenuCommand('更新報告表中的所有單元', function() { sbj_update_all_confirm(); });
        GM_registerMenuCommand('重設報告表', function() { resetDashboard(); });
        GM_registerMenuCommand('重設全部', function() { resetAll(); });
    }
}

// Set default value for first use or reset
function setDefault() {
    data = templateData();
    updateData();
    return data;
}

// Only reset dashboard
function resetDashboard() {
    data.dashboard = {};
    updateData();
    dashboard();
}

// Reset Everything
// Reset Everything
// Reset Everything
function resetAll() {
    if (window.confirm("確認重設全部資料?這會刪除所有本地數據,包括在網站中已存儲的數據(如果您已鏈結帳戶)")) {
        if (window.prompt("請輸入您的學生證號碼以便進一步確認。") === data.sys_stu_id) {
            setDefault();
            window.alert("重設完成!請刷新頁面!");
        }
        else {
            window.alert("學生證號碼錯誤!");
        }
    }
}
// Reset Everything
// Reset Everything
// Reset Everything

// Create the object for new subject
function sbjCreate(t_hours) {
    data.sbj[c_id] = {
        id: c_id,
        name: c_name,
        t_hours: t_hours,
        att: 0,
        att_p: 0,
        abs: 0,
        abs_p: 0,
        avg_att: 0,
        remain: 0,
        updated_time: Date.now()
    };
}

function time_convert(input_min) {
    var output_hr = Math.round(Math.floor(input_min / 60));
    var output_min = Math.round(input_min % 60);
    return output_min !== 0 ? output_hr + u_hour + output_min + u_minute : output_hr + u_hour;
}

// For return a template data
function templateData(stu_id = null, secret = null, sync_time = 0) {
    if (data && data.secret) {
        stu_id = data.stu_id;
        secret = data.secret;
    }
    return {
        sys_stu_id: getStuId(),
        stu_id: stu_id,
        secret: secret,
        version: version,
        sbj: {},
        dashboard: {},
        sync_time: sync_time
    };
}

// Update local storage(Set the data to local)
function updateLocal() {
    GM_setValue(getStuId(), JSON.stringify(data));
}

function fetchData() {
    data.version = version;
    if (data.secret) {
        fetchDb().then(rep => {
            if ($.isEmptyObject(rep.Data)) {
                updateData();
            }
            else {
                data = rep.Data;
                updateLocal();
            }
        }).catch(err => {
            throw err;
        });
    }
}

function updateData() {
    pushDb();
    updateLocal();
}

//=========================================================== Account =================================================================================

// Gather user information
function bindAccount() {
    let u_secret = window.prompt('請輸入密鑰(secret key)');
    if (u_secret) {
        let u_stu_id = window.prompt('請輸入您的學生證號碼');
        if (u_stu_id === getStuId()) {
            data.secret = u_secret;
            data.stu_id = u_stu_id;
            fetchData();
            window.alert('請刷新頁面!');
        }
        else {
            window.alert('學生證號碼不正確!');
        }
    }
}

//Fetch the user information from table
async function fetchDb(args = data) {
    if (data.secret) {
        try {
            let res = await fetch(`${invokeUrl}/get`, {
                method: 'POST',
                body: JSON.stringify({ StudentId: args.stu_id, Secret: args.secret })
            });
            if (res.status === 200) {
                return await res.json();
            }
            throw "User not found";
        }
        catch (err) {
            throw err;
        }
    }
}

// Push the user information to table
async function pushDb(args = data) {
    if (data.secret) {
        try {
            args.sync_time = Date.now();
            let res = await fetch(`${invokeUrl}/update`, {
                method: 'POST',
                body: JSON.stringify({ StudentId: args.stu_id, Secret: args.secret, Data: args })
            });
            return await res.json();
        }
        catch (err) {
            throw err;
        }
    }
}

//=========================================================== End Account =================================================================================

// Create element
function createElement() {
    //calc button if no total hours set
    calc_btn = document.createElement("button");
    calc_btn.setAttribute("style", "width:100%;padding:10px;background-color:#555;color:#fff;border:0;line-height:14px;font-size:13px;");
    calc_btn.setAttribute("id", "calcBtn");
    calc_btn.innerHTML = "計算出缺率";

    //Info, edit time button, and toggle button
    sbj_info_div = document.createElement("div");
    sbj_info_div.setAttribute("style", "width:12vw;position:fixed;right:0;bottom:0;border:0;font-family:'Microsoft jhenghei';z-index:999");
    sbj_info_div.setAttribute("id", "sbjDiv");
    sbj_info = document.createElement("p");
    sbj_info.setAttribute("style", "margin:0;width:100%;background-color:#555;color:#fff;border:0;border-bottom:1px solid #fff;line-height:14px;font-size:13px;");
    sbj_info.setAttribute("id", "sbjInfo");
    sbj_edit_time_btn = document.createElement("button");
    sbj_edit_time_btn.setAttribute("style", "width:85%;padding:10px;background-color:#555;color:#fff;border:0;line-height:14px;font-size:13px;");
    sbj_edit_time_btn.setAttribute("id", "editTimeBtn");
    sbj_edit_time_btn.innerHTML = "編輯總時數";
    toggle_btn = document.createElement("button");
    toggle_btn.setAttribute("style", "width:15%;padding:10px;background-color:#555;color:#fff;border:0;line-height:14px;font-size:13px;");
    toggle_btn.setAttribute("id", "btnToggle");
    toggle_btn.innerHTML = "-";

    //Dashboard
    sbj_dashboard_div = document.createElement("div");
    sbj_dashboard_div.setAttribute("style", "min-width:24vw;position:fixed;left:0;bottom:0;border:0;font-family:'Microsoft jhenghei';z-index:999");
    sbj_dashboard_div.setAttribute("id", "sbjDashboardDiv");
    sbj_dashboard = document.createElement("table");
    sbj_dashboard.setAttribute("style", "width:100%;padding:10px;background-color:#555;color:#fff;border:0;line-height:14px;font-size:13px;");
    sbj_dashboard.setAttribute("id", "sbjDashboard");
    sbj_select_btn = document.createElement("button");
    sbj_select_btn.setAttribute("id", "sbjSelectBtn");
    sbj_select_btn.setAttribute("style", "width:100%;padding:10px;background-color:#555;color:#fff;border:0;line-height:14px;font-size:13px;border-top: 1px solid #fff;");
}


// =================================================================== Event Listener Function =================================================================== //


//Update the subject hours
function sbjHoursUpdate_Click() {
    $("#editTimeBtn").click(function() {
        let t_hours = window.prompt("編輯該單元的總時數", data.sbj[c_id].t_hours);
        if (t_hours !== "" && t_hours == parseInt(t_hours, 10)) {
            data.sbj[c_id].t_hours = t_hours;
            updateData();
            sbj_chk();
        }
        else {
            window.alert('發生錯誤。Error201');
        }
    });
}
//Ask the user to input the total hours of this subject
function sbjInputHours_Click() {
    $("#calcBtn").click(function() {
        let t_hours = window.prompt("請輸入該單元的總時數");
        if (t_hours !== "" && t_hours == parseInt(t_hours, 10)) {
            sbjCreate(t_hours);
            sbj_chk();
        }
        else {
            window.alert('發生錯誤。Error202');
        }
    });
}
//Function for toggle button
function calcToggle() {
    $("#btnToggle").click(function() {
        //$('#btnToggle').html(this.html == "+" ? "-" : "+");
        $("#sbjInfo").slideToggle();
        $("#sbjDashboardDiv").slideToggle();
        document.getElementById("btnToggle").innerHTML = document.getElementById("btnToggle").innerHTML === "+" ? "-" : "+";
    });
}

// Check is Subject inside array, if NOT push array
function sbjDashboardArray_Click() {
    $("#sbjSelectBtn").click(function() {
        $('#sbjSelectBtn').text(this.text == "加入" ? "清除" : "加入");
        data.dashboard[c_id] ? delete data.dashboard[c_id] : data.dashboard[c_id] = c_id;
        updateData();
        dashboard();
    });
}

// =================================================================== End of Event Listener Function =================================================================== //

//!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT FUNCTION !!!!!!!!!!!!!!!!!!!!!!!!!!!//
//!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT FUNCTION !!!!!!!!!!!!!!!!!!!!!!!!!!!//
//!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT FUNCTION !!!!!!!!!!!!!!!!!!!!!!!!!!!//
//Check if the subject have record or not, if yes then append and calculate the result
function sbj_chk() {
    if (c_id in data.sbj) {
        if (!ini) {
            $("#calcBtn").remove();
            document.getElementById("sbjDiv").append(sbj_info);
            document.getElementById("sbjDiv").append(sbj_edit_time_btn);
            document.getElementById("sbjDiv").append(toggle_btn);
            document.getElementById("sbjDashboardDiv").append(sbj_dashboard);
            document.getElementById("sbjDashboardDiv").append(sbj_select_btn);
            sbjHoursUpdate_Click();
            sbjDashboardArray_Click();
            calcToggle();
            ini = true;
        }
        sbj_calc();
    }
    else {
        document.getElementById("sbjDiv").append(calc_btn);
        sbjInputHours_Click();
    }
}
//!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT FUNCTION !!!!!!!!!!!!!!!!!!!!!!!!!!!//
//!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT FUNCTION !!!!!!!!!!!!!!!!!!!!!!!!!!!//
//!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT FUNCTION !!!!!!!!!!!!!!!!!!!!!!!!!!!//

//Calculate the result
function sbj_calc() {
    //Get Table Data to Arrays
    let tb_array = [],
        headers = [],
        sbj_hours = data.sbj[c_id].t_hours;
    $('table.hkvtcsp_wording th').each(function(index, item) {
        headers[index] = $(item).text();
    });
    $('table.hkvtcsp_wording tr').has('td').each(function() {
        let arrayItem = {};
        $('td', $(this)).each(function(index, item) {
            arrayItem[headers[index]] = $(item).text();
        });
        tb_array.push(arrayItem);
    });
    let att_lesson = 0,
        abs_lesson = 0,
        late_lesson = 0,
        tt_lesson_time = 0,
        tt_att_time = 0,
        tt_abs_time = 0,
        att_time, abs_time, i = 0;
    for (i = 0; i < tb_array.length; i++) {
        let lesson_time_array = tb_array[i]['課堂時間'].split("-");
        //Lesson Count
        switch (tb_array[i]['']) {
            case 'Present':
                att_lesson++;
                break;
            case 'Late':
                late_lesson++;
                break;
            case 'Absent':
                abs_lesson++;
                break;
        }
        //ABS & ATT Caculate
        var arrived_time = tb_array[i]['出席時間'];
        var row_lesson_time_start = new Date();
        var lesson_time_array_0 = lesson_time_array[0].split(':');
        row_lesson_time_start.setHours(lesson_time_array_0[0], lesson_time_array_0[1]);
        var row_lesson_time_end = new Date();
        var lesson_time_array_1 = lesson_time_array[1].split(':');
        row_lesson_time_end.setHours(lesson_time_array_1[0], lesson_time_array_1[1]);
        var row_lesson_time_arrived = new Date();
        if (arrived_time !== '-') {
            arrived_time = arrived_time.split(':');
            row_lesson_time_arrived.setHours(arrived_time[0], arrived_time[1]);
            if (row_lesson_time_arrived > row_lesson_time_start.setMinutes(row_lesson_time_start.getMinutes() + 10)) {
                row_lesson_time_start.setHours(lesson_time_array_0[0], lesson_time_array_0[1]);
                att_time = (row_lesson_time_end - row_lesson_time_arrived) / 1000 / 60;
                abs_time = (row_lesson_time_arrived - row_lesson_time_start) / 1000 / 60;
            }
            else {
                row_lesson_time_start.setHours(lesson_time_array_0[0], lesson_time_array_0[1]);
                att_time = (row_lesson_time_end - row_lesson_time_start) / 1000 / 60;
                abs_time = 0;
            }
        }
        else {
            att_time = 0;
            abs_time = (row_lesson_time_end - row_lesson_time_start) / 1000 / 60;
        }
        tt_lesson_time = tt_lesson_time + ((row_lesson_time_end - row_lesson_time_start) / 1000 / 60);
        tt_att_time = tt_att_time + att_time;
        tt_abs_time = tt_abs_time + abs_time;
    }
    let time_remain;
    if (sbj_hours * 60 * 0.3 >= tt_abs_time) {
        time_remain = (sbj_hours * 60 * 0.3) - tt_abs_time;
        time_remain = time_convert(time_remain);
    }
    else {
        time_remain = "-/-";
    }
    //var sbj_array = JSON.parse(GM_getValue(sbj));
    data.sbj[c_id].att = time_convert(tt_att_time);
    data.sbj[c_id].att_p = (tt_att_time / (sbj_hours * 60) * 100).toFixed(2) + "%";
    data.sbj[c_id].abs = time_convert(tt_abs_time);
    data.sbj[c_id].abs_p = (tt_abs_time / (sbj_hours * 60) * 100).toFixed(2) + "%";
    data.sbj[c_id].remain = time_remain;
    data.sbj[c_id].avg_att = (60 / (sbj_hours * 60) * 100).toFixed(2) + "%";
    updateData();
    let calcResult = "<p style='padding:18px 25px;margin:0;line-height:130%'><b>計算結果 :</b><br>" +
        "<br>總時數 : " + sbj_hours + u_hour + "\n" +
        "<br>已上課堂時數 : " + time_convert(tt_lesson_time) +
        "<br><br>總出席時數 : " + time_convert(tt_att_time) +
        "<br>出席率 : " + (tt_att_time / (sbj_hours * 60) * 100).toFixed(2) + "%" +
        "<br><br>總缺席時數 : " + time_convert(tt_abs_time) + "\n" +
        "<br>缺席率 : " + (tt_abs_time / (sbj_hours * 60) * 100).toFixed(2) + "%" +
        "<br><br>每小時缺席率 : " + (60 / (sbj_hours * 60) * 100).toFixed(2) + "%" +
        "<br>剩餘可缺席時數 : " + time_remain + "</p>";
    document.getElementById("sbjInfo").innerHTML = calcResult;
    dashboard();
}

//Out put the dashboard record
function dashboard() {
    let isExist = false;
    if (!($.isEmptyObject(data.dashboard))) {
        document.getElementById("sbjDashboard").innerHTML = "<tr><td>單元</td><td>出席率</td><td>缺席率</td><td>剩餘</td></tr>";
        for (let id in data.dashboard) {
            if (c_id === data.dashboard[id]) {
                isExist = true;
                document.getElementById("sbjDashboard").innerHTML += "<tr><td><b><i>" + data.sbj[id].name + "</i></b></td><td><b><i>" + data.sbj[id].att_p + "</i></b></td><td><b><i>" + data.sbj[id].abs_p + "</i></b></td><td><b><i>" + data.sbj[id].remain + "</i></b></td></tr>";
            }
            else {
                document.getElementById("sbjDashboard").innerHTML += "<tr><td>" + data.sbj[id].name + "</td><td>" + data.sbj[id].att_p + "</i></b></td><td><b><i>" + data.sbj[id].abs_p + "</td><td>" + data.sbj[id].remain + "</td></tr>";
            }
        }
    }
    else {
        document.getElementById("sbjDashboard").innerHTML = "<tr><td>單元</td><td>出席率</td><td>缺席率</td><td>剩餘</td></tr><tr><td colspan=\"4\" style=\"text-align:center\"><b><i>暫無資料</i></b></td></tr>";
    }
    document.getElementById("sbjSelectBtn").innerHTML = isExist ? "清除" : "加入";
}

function sbj_update_all_confirm() {
    if (window.confirm('確認更新報告表中的所有單元?其間將會不斷更新畫面。')) {
        sbj_update_all();
    }
}

// Update all subject in dashboard
function sbj_update_all() {
    var temp = JSON.parse(GM_getValue('temp_' + getStuId()) || '{}');
    if (data.dashboard && JSON.stringify(temp) !== JSON.stringify(data.dashboard)) {
        for (let i in data.dashboard) {
            if (!(i in temp)) {
                temp[i] = data.dashboard[i];
                GM_setValue('temp_' + getStuId(), JSON.stringify(temp));
                $('.hkvtcsp_textInput').val('ITE4103').trigger('change');
                break;
            }
        }
    }
    else if (JSON.stringify(temp) === JSON.stringify(data.dashboard)) {
        window.alert('更新完成!');
        GM_deleteValue('temp_' + getStuId());
    }
}

(function() {
    try {
        if ($('table.hkvtcsp_wording').length) {
            initialize();
            if (data.secret) {
                if (window.compareVersions('4.*.*', data.version)) {
                    data.secret = null;
                    data.stu_id = null;
                    updateData();
                }
                fetchData();
                document.body.insertBefore(sbj_info_div, document.body.firstChild);
                document.body.insertBefore(sbj_dashboard_div, document.body.firstChild);
                var sbjCls = document.getElementsByClassName("hkvtcsp_textInput");
                c_id = sbjCls[0].options[sbjCls[0].selectedIndex].value;
                c_name = sbjCls[0].options[sbjCls[0].selectedIndex].text;
                sbj_chk();
                if (GM_getValue('temp_' + getStuId())) {
                    sbj_update_all();
                }
            }
        }
    }
    catch (err) {
        window.alert('Userscript error! Please contact developer!');
        console.error(err);
        throw err;
    }
})();