Attendance For Meituan

Attendance Notification!

// ==UserScript==
// @name         Attendance For Meituan
// @namespace    xbf321
// @version      2.0
// @description  Attendance Notification!
// @author       xbf321
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_info
// @match        *://*.sankuai.com/*
// @connect      sankuai.com
// @license      AGPL License
// ==/UserScript==

// https://www.tampermonkey.net/documentation.php

(function() {
    'use strict';
    const VERSION = GM_info.script.version;
    // x 秒检查一次接口
    const STORE_KEY = 'kaoqin-info';
    const DALAY_CHECK_TIME = 5 * 60;
    const date = new Date();
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    const currentMonthTime = new Date(year + '-'+ month + '-01 00:00:00').getTime();
    const currentDayTime =new Date(year + '-' + month + '-' + day).getTime();
    const opt = Object.prototype.toString;
    function isString(value) {
      return opt.call(value) === "[object String]";
    }
    const dom$1 = {
      query: function(selector) {
        return document.querySelector(selector);
      },
      attr: function(selector, attr, value) {
        const dom2 = document.querySelector(selector);
        dom2 && dom2.setAttribute(attr, value);
      },
      append: function(selector, content) {
        const container = document.createElement("div");
        if (isString(content)) {
          container.innerHTML = content;
        } else {
          container.appendChild(content);
        }
        const targetDOM = document.querySelector(selector);
        targetDOM && targetDOM.append(container);
        return container;
      },
      remove: function(selector) {
        const targetDOM = document.querySelector(selector);
        targetDOM && targetDOM.remove();
      }
    };
    function timestampToTime(timestamp) {
        if (timestamp.toString().split('').length === 10) {
            timestamp = timestamp * 1000;
        }
        const date = new Date(timestamp);
        const hh = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours());
        const mm = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes());
        return {hh, mm};
    }
    async function checkCurrentTime() {
        const nowTime = new Date().valueOf();
        let dataObject = GM_getValue(STORE_KEY, null);
        try {
            if (!dataObject) {
                dataObject = await getCurrentTimeFromRemote();
            } else {
                dataObject = JSON.parse(dataObject);
            }
            if (dataObject) {
                const { lastCheck } = dataObject;
                const diffTime = (nowTime - lastCheck) / 1000;
                if (diffTime > DALAY_CHECK_TIME) {
                    dataObject = await getCurrentTimeFromRemote();
                }
            }
        } catch(err) {
            console.error(err);
            showErrorNotification(err.url || '');
            return;
        }
        const { timeRange } = dataObject;
        const [startTime, endTime] = timeRange;
        const { hh: startHH, mm: startMM } = timestampToTime(startTime);
        const { hh: endHH, mm: endMM } = timestampToTime(endTime);
        const startTimeString = startHH + ':' + startMM;
        const endTimeString = endHH + ':' + endMM;
        const remainHour = parseInt(startHH, 10) + 9;
        let offDay = remainHour + ':'+ startMM;
        if (remainHour < 18) {
            offDay = "18:00"
        }
        showNotification(startTimeString, endTimeString, offDay);
    }
    async function getCurrentTimeFromRemote() {
        const data = await fetchCurrentData();
        const { day } = data;
        const timeRange = [];
        (day || []).forEach((item) => {
            const { day: currentDay, startTime, endTime } = item;
            if (currentDay === currentDayTime) {
               timeRange[0] = startTime;
               timeRange[1] = endTime
            }
        });
        if (timeRange.length === 0) {
           return null;
        }
        const dataObject = {
                 timeRange,
                 lastCheck: new Date().valueOf(),
        };
        GM_setValue(STORE_KEY, JSON.stringify(dataObject));
        return dataObject;
    }
    async function fetchCurrentData() {
        const promise = new Promise((resolve, reject) => {
           GM_xmlhttpRequest({
               url: "https://hr.sankuai.com/kaoqin/api/attendance/calendar",
               method: "POST",
               headers: {
                   "content-type": "application/json",
                   "user-agent": navigator.userAgent,
               },
               data: JSON.stringify({
                   "month": currentMonthTime
               }),
               responseType: "json",
               onload(response) {
                   if (response.status === 200) {
                       const data = response.response;
                       const { status, data: detailData} = data;
                       if (status === 1) {
                          resolve(detailData);
                       } else {
                          reject(detailData);
                       }
                   } else {
                       reject(response);
                   }
               },
               onerror: (err) => {
                   reject(err);
               },
           });
        });
        return promise;
    }
    function showErrorNotification(redirectURL){
        if (!redirectURL) {
          return;
        }
        const templateCSS = [
          "<style id='kaoqin-template-css'>",
          "#kaoqin-html{position: fixed; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center;z-index: 999999; background: #fff; width: 350px;height: 48px;}",
          "#kaoqin-html > .title{font-weight: 600;color: red; }",
          "#kaoqin-html > .version{margin-left: 10px; font-size: 12px; }",
          "</style>"
        ].join("");
        const templateHTML = [
            "<div id='kaoqin-html'>",
            "<span class='title'>登陆过期,</span>请点击",
            "<a href='",
            redirectURL,
            "' target='__blank'>这里</a>,然后刷新页面",
            "<span class='version'>v",
            VERSION,
            "</span>",
            "</div>"
          ].join("");
        dom$1.append("body", templateHTML);
        dom$1.append("body", templateCSS);
    }
    function showNotification(startTimeString, endTimeString, offTimeString) {
        const templateCSS = [
          "<style id='kaoqin-template-css'>",
          "#kaoqin-container {position: fixed; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center;z-index: 999999; background: #fff;height: 32px;}",
          "#kaoqin-html{ width: 480px; }",
          "#kaoqin-html > .title{font-weight: 600;margin-left: 10px; }",
          "#kaoqin-html > .time{color: red; font-size: 20px; }",
          "#kaoqin-html > .version{margin-left: 10px; font-size: 12px; margin-right: 10px;}",
          "</style>"
        ].join("");
        const templateHTML = [
            "<div id='kaoqin-container'>",
            "<div id='kaoqin-html'>",
            "<span class='title'>首次打卡:</span>",
            "<span class='time'>",
            startTimeString,
            "</span>",
            "<span class='title'>最后打卡:</span>",
            "<span class='time'>",
            endTimeString,
            "</span>",
            "<span class='title'>下班时间:</span>",
            "<span class='time'>",
            offTimeString,
            "</span>",
            "<span class='version'>v",
            VERSION,
            "</span>",
            "</div>",
            "<a href='javascript:void(0)' id='btn-kaoqin'>",
            "收起",
            "</a>",
            "</div>"
          ].join("");
        dom$1.append("body", templateHTML);
        dom$1.append("body", templateCSS);
        document.querySelector('#btn-kaoqin').addEventListener('click',function(){
            const obj = document.getElementById('kaoqin-html');
            const status = obj.style.display;
            obj.style.display = status === 'none' ? 'block' : 'none';
        });
    }
    checkCurrentTime();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址