您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Refresh the page
当前为
// ==UserScript== // @name CDC Booking Script v0 // @namespace http://tampermonkey.net/ // @version 0.7 // @description Refresh the page // @author afjw // @include https://bookingportal.cdc.com.sg/NewPortal/Booking/BookingPL.aspx // @grant none // @license MIT // ==/UserScript== /* jshint esversion: 8 */ const isTesting = false; let stop = false; // settings // const preferredTimeSlots = [ // { // day: 'WED', // slot: '16:25 - 18:05' // } // ] const getPreferredTimeSlots = () => { return JSON.parse(localStorage.getItem("preferredTimeSlots") || "[]"); }; const isLoading = () => { const loadingSpinner = document.querySelector( "#ctl00_ContentPlaceHolder1_UpdateProgress1" ); return loadingSpinner && loadingSpinner.style.display !== "none"; }; const toggleSelector = async () => { const s = document.querySelector("#ctl00_ContentPlaceHolder1_ddlCourse"); s.value = s.children[1].value; s.dispatchEvent(new Event("change")); await sleep(1); while (isLoading()) { sendMessage("reloading"); await sleep(1000); window.location.reload() } return Promise.resolve(); }; const isTimeSlotPreferred = ({ date = "", day = "", slot = "" }) => { if (!date) { return false; } const preferredTimeSlots = getPreferredTimeSlots(); const matched = preferredTimeSlots.find( (timeSlot) => timeSlot.day === day && timeSlot.slot === slot ); return !!matched; }; const getAvailableSlots = () => { const table = document.querySelector("#ctl00_ContentPlaceHolder1_gvLatestav"); if (!table) { return []; } const rows = table.querySelectorAll("tr"); const timeSlotRow = rows[0]; let allSlots = []; rows.forEach((row, index) => { if (index !== 0) { const slots = []; const cells = row.querySelectorAll("td"); const timeSlotCells = timeSlotRow.querySelectorAll("th"); cells.forEach((cell, index) => { const slotInput = cell.querySelector("input"); if (slotInput) { const src = slotInput.src || ""; if (src.includes("Images1")) { const date = cells[0].textContent; const day = cells[1].textContent; const slot = timeSlotCells[index].textContent.slice(1); slots.push({ slotInput, date, day, slot, }); } } }); allSlots = allSlots.concat(slots); } }); return allSlots; }; const reserve = (availableSlots) => { const timeAvailableSlot = availableSlots.find((slot) => isTimeSlotPreferred(slot) ); if (timeAvailableSlot) { const slotInput = timeAvailableSlot.slotInput; if (slotInput) { slotInput.click(); return timeAvailableSlot; } } return false; }; const run = async () => { try { await toggleSelector(); console.log(`Refreshed at`, new Date().toLocaleTimeString()); const availableSlots = getAvailableSlots(); const hasResult = !!availableSlots.length; if (hasResult) { const reservedSlot = reserve(availableSlots); let msg; if (reservedSlot) { msg = `Slot is reserved\n${reservedSlot.date} ${reservedSlot.day} ${reservedSlot.slot}`; } else { const slotsMsg = `${availableSlots .map(({ date, day, slot }) => `${date} ${day} ${slot}`) .join("\n")}`; msg = `Slots are available\n${slotsMsg}`; } sendMessage(`[${new Date().toLocaleString("en-ca")}] ${msg}`); } return Promise.resolve(hasResult); } catch (e) { return Promise.reject(`error: ${e}`); } }; const sleep = (interval) => { return new Promise((resolve) => { const jitter = Math.random() * 3 * 1000; // ±10s window.setTimeout(resolve, interval + jitter); }); }; // https://simpleguics2pygame.readthedocs.io/en/latest/_static/links/snd_links.html const audio = new Audio( "http://codeskulptor-demos.commondatastorage.googleapis.com/descent/gotitem.mp3" ); const sendMessage = (msg) => { console.log(msg); audio.play(); }; let startButton; let settingButton; let settingPopup; const start = async () => { console.log('looping'); const hasResult = await run(); if (hasResult) { await sleep(1); } else { await sleep(30 * 1000); } if (!stop) { console.log('relooping'); start(); } }; const insertStartButton = () => { startButton = document.createElement("button"); startButton.className = "start-button"; startButton.textContent = "running..."; const startButtonStyle = document.createElement("style"); startButtonStyle.innerHTML = ` .start-button { position: fixed; bottom: 1670px; right: 150px; width: 100px; padding: 5px; line-height: 1; background-color: #ff9933; border: none; border-radius: 10px; font-size: 18px; cursor: pointer; } `; document.head.appendChild(startButtonStyle); document.body.appendChild(startButton); startButton.addEventListener("click", async () => { try { stop = true; startButton.textContent = "stopped"; } catch (e) { startButton.textContent = "error"; console.log("error", e); } }); }; const insertSettingButton = () => { settingButton = document.createElement("button"); settingButton.className = "setting-button"; settingButton.textContent = "setting"; const settingButtonStyle = document.createElement("style"); settingButtonStyle.innerHTML = ` .setting-button { position: fixed; bottom: 1030px; right: 150px; width: 100px; padding: 5px; line-height: 1; background-color: #ff9933; border: none; border-radius: 10px; font-size: 18px; cursor: pointer; } `; document.head.appendChild(settingButtonStyle); document.body.appendChild(settingButton); settingButton.addEventListener("click", async () => { toggleSettingPopup(); }); }; const toggleSettingPopup = () => { if (window.getComputedStyle(settingPopup).display === "none") { settingPopup.style.display = "initial"; } else { settingPopup.style.display = "none"; } }; const insertSettingPopup = () => { settingPopup = document.createElement("div"); settingPopup.className = "setting-popup"; const settingPopupStyle = document.createElement("style"); settingPopupStyle.innerHTML = ` .setting-popup { display: none; position: fixed; bottom: 865px; right: 50px; width: 900px; padding: 10px; background-color: #ff9933; border: none; border-radius: 10px; font-size: 22px; } .setting-popup label { margin-bottom: 10px; display: block; } .setting-popup table { width: 100%; table-layout: auto; border-collapse: collapse; } .setting-popup table th, .setting-popup table td { text-align: center; border: 1px solid black; } .setting-popup table th { padding: 5px; } .setting-popup table td { height: 35px; } .setting-popup table img { width: 30px; } .setting-popup table .selectable { cursor: pointer; } .setting-buttons { display: flex; flex-wrap: wrap; } .setting-buttons .toggle-button { border: none; padding: 5px; border-radius: 5px; margin-right: 5px; margin-bottom: 5px; cursor: pointer; } .setting-buttons .toggle-button.active { background-color: red; } `; document.head.appendChild(settingPopupStyle); document.body.appendChild(settingPopup); renderSettingTable(); }; const toggleTimeSlot = (day, slot) => { const savedPreferredTimeSlots = getPreferredTimeSlots(); const matched = savedPreferredTimeSlots.find( (timeSlot) => timeSlot.day === day && timeSlot.slot === slot ); if (matched) { savedPreferredTimeSlots.splice(savedPreferredTimeSlots.indexOf(matched), 1); } else { savedPreferredTimeSlots.push({ day, slot, }); } localStorage.setItem( "preferredTimeSlots", JSON.stringify(savedPreferredTimeSlots) ); renderSettingTable(); }; const renderSettingTable = () => { const slots = [ "08:30 - 10:10", "10:20 - 12:00", "12:45 - 14:25", "14:35 - 16:15", "16:25 - 18:05", "18:50 - 20:30", "20:40 - 22:20", ]; const days = ["MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"]; window.toggleTimeSlot = toggleTimeSlot; const savedPreferredTimeSlots = getPreferredTimeSlots(); const table = ` <label>Preferred Time Slots</label> <table> <thead> <tr> <th>Day</th> ${slots.map((slot) => `<th>${slot}</th>`).join("\n")} </tr> </thead> ${days .map((day) => { return ` <tr> <td>${day}</td> ${slots .map((slot) => { const isSelected = !!savedPreferredTimeSlots.find( (timeSlot) => timeSlot.day === day && timeSlot.slot === slot ); return ` <td class="selectable" onclick="toggleTimeSlot('${day}', '${slot}', '${isSelected}')"> ${ isSelected ? '<img src="data:image/gif;base64,R0lGODlhMgAoAPcAAF5eXlfQRuP04WFhYWpqapKSkvHx8VbhONToxsnRw6mpqWPWOYKCgs7dv56enlziR76+vubm5qPYh42NjYK8aXZ2dsHBwdnl0pWVlWjCQzy+KHT6Od3d3WvqSK2trVHBK87OzkpKSm68UfX19bTdplXNNNfX12bzK22VduDg4Pz8/ErTNdnZ2bPooXJycvH77Hp6ejbCGt7e3mjnK/7+/vr6+pbZckCxQ+Pj4m1tbdra2oWFhVroKOzs7KfHmlPPYtTU1GVlZcbkumHhPKfVlIH4NLCwsFVVVVTSKmTMWOnp6YnKfVpaWlTfLkjFN+Xu3kBAQPj4+GTPFrq6usjIyLa2tpiYmLjmlerq6nyQhoiIiKenp9HR0aKiori4uGyZWn1+fnHwQJK3hIflR0O6PKCgoHizY0rJF5qammbKP0zrNMbGxmrmHpGki1FRUdDYzMrKymd0aaSkpNDQ0MTExH7VYvn++dr5u7Ozs1vVNILbUVrWJdXc1K2xp7Kysu7v7lzbI/z++5TtUkTXJWbbJGDWK1TZMUVFRVvKK8POumLdMljSHUjZMGTpPVndHUXUKmbeRIuLi1jaN5WekljTOaOkof7//ljMB6TydlymT12/PGT5P2r1OHSsd/H+5JrkdXX4TlPTGMvLy5PyWamuqKywrYPIaV/uOIfVZdnw3XvzSfPz82ruMZCQkErXHFHMF1tkWZCcjP39/VOgW7P3ilLWJlXaJ13ZLc/tr29/ds/6sOTj5KGxkL7BuXLxVdze3IyrgIS0jJq3larhlff+8b34nXbjLUy/T/v+91u6T1nQPL/Tt9re2LDcguvr51zdO47sUJzbZj2uK06vOpucm+f4zn3ZSU7oIWnhO2/+Surp66etpbGzsGH3NHzsO1z8KWvUXm/xN2/8MX7tXUjeJ22yZGDNLX2ob2DXIj+5W8D0nWfdLev07U7XJk/XKsfox43oZ9XvyZztZJPwYWXXS2LYSmfQRWvTSWrbRm7dSGWthlLfTnLyTU/PLHj3RP///yH5BAAAAAAALAAAAAAyACgAAAj/AP8JHEiwoMGDCBMqXMiw4cAXT2Q5nEgR2R0bCCxR3LjQkzprLWhMpKFCBY2TKFOKbGip2rxGJAI1lFXDQA8dJjiwYKGDQwocEXr0+GPAwAgVslYWDHQHGrYWdhjSWMVhigMFVTC4yOECBoMdrax0UWAEAhVRQHQYkEgwkK4iMyREXagixZYCMIIwMOIgSA6vO7QUsCLHQxUIQHCkMEGFQxS2xIptOFHnhVQTOxx0cdHFgwcFkbbgqdKFAYMJGLpMMTIlwp8eOERRSVGDGC1x3cAJaNgDTRdR1BhMsTKhSqsyVbxggAFGS6QCChRsWaMEi1AgEBpg2sApSaqGNSAU/wCyhloBDxi0TKFWoT3XCmAYaOmypYsRDhG0/flzYdQGf/YIoRFdOrSCBxcWGIHHZzsoMMUAIbhxBAAE5FABDFpgoKECflgAwhtX+OPPPT6swpAsSlRBDWsWWABEggREAgQDEh4x4QAuyNdKATxOIJYE44CSjxilmBCFCgnR0AMQc1AxxRQQGGGEHFqEUAEQU2ihBWGGeahDChEoYUAUI+BSRDYdLPEEDhzggMVRSg2kgg5AcCGDDCbMAcdqDjCABhc6iLIGFVzkxMGhOCghFBbxCLLBNwFcIFQEKfwkZg0FqWDCppVyAcdPlbIAxKZAUFGFAl1SsQYdVIAARAOCqP/iiyNE/PJLpRGEmWsPEWAq0B+zfZmCqBz0VKxOpIoyhRydVdHik4nIEw4/+JjCx6Y9WdrDKjhwEUEUAinRhQVnzWEoqBwwIwQJb7BggigQlKEABFN81kc0J2zSiAhvYLuTsHTSsakSAhmQoxdekAtCoTKk8MYwHfBgwxscmICFKCz8gYcCvTTjTTff9CPMFp55gIdZ62lRgQMuRoCkCmAc0YUcrEGwBhxcNADPKUMsYIsNv/TkzAj/bFHJFWF0cMo1ZmAwIQAADMAANVowEQIUE7iYAhYnKTAAj2KRhYcXPmDDgyQLGFILCTIAAaYSRvAyBiuNDPFBG1scAcXeUBz/UqGEBGyxaZs10FDFDhVoGZZmW5BCgSK19EMJJfbEY4A2uwCRyCfGsDFDAOdQUwUaFeSQAwEDUBiJEXSAYIIMiUZBwxpWwMdAcwVQ48AWbaQByCMlKNOOHn+s8gcOVzQyAxsBJJOAbKvSSy81qVGDBxBAsIAf0SmQTsAEMHilRSvUtPJFIYwgUQI5rpDAgQwNjKEGD8ocQwE3o3kBgc2bBQGA1RXAGRDeBy4DsCcEOZADBm4Hhh3sIA4ZMMQikMCIR9QhASBohiLUsI9jiGASaNAMqvxQBRiEwAUN7AoVsLcGCKyCBhGoGhTcYAUHWMEKrYiECwZwjjzsoRaMWEEa/4iwDD0cYB8/IAMwrECNmclhCx1iwBFc4IIgHIEAVQDBBMCgAHD1wAqH6NsRglCB01BjK7FIgyQMYQskmEMPqIDEKZJYjkhMYDBl2EJ90ECAQ/hxb4eIxA6+RwckKckLDADAEZgwgCAQoAIACEIBKFCPAziiEHtwxAMe0AQnkCEXNVqkCxxQgCOEIIx8C0FqjEAFGbDlHyrAgQdcwARGDmAAR2hkLJJwgHUQAhBN2CQSYtAJN/CNbzmwAgAixMgKTEAOEOBCCgxQuIKMAARWIAATouaGHFTBCEtYgS8VsclnnEEaWajlNisEgwl0gZQYuGEBwNcFE/QgTgRRgQGoMPwB/11RAQxogznQcYt60AOTMcgEGNrjlUi0ohUT0AID2lkB/9WSABhSQATwKacIlNBqMDDCNkSwCHcEIA1SCEUMUEAARzqSAKi75QAI0IodyjR1R8gBCDg6EBrUwARdyEEItOAFYOxhECtAhBTOkI44bDNqN5WpI28KNQptoQdSqYEOnBYCWGiCHI94xSU0oI+qRvWWVU3rTBvEBdk5hAYjEEUrcvAFSTQhFGe4QRbSatZGOvI9DMCAAtawi2pypAZ/4IMIIDGEEsyiFGVAQzypUYYnmgwCdEALfgyAJI4cZBnTcII0gtFZz5q2pwKIxzvYcdrWuva1sF1IQAAAOw==" />' : "" } </td> `; }) .join("\n")} </tr>`; }) .join("\n")} </table> `; settingPopup.innerHTML = table; }; const main = async () => { insertStartButton(); insertSettingButton(); insertSettingPopup(); start(); }; (function () { "use strict"; main(); })();