您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Автоматизация процессов journal
// ==UserScript== // @name Top Academy Journal Auto Rater Pro // @version 0.4 // @description Автоматизация процессов journal // @author Rodion // @match https://journal.top-academy.ru/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @run-at document-idle // @namespace http://tampermonkey.net/ // ==/UserScript== !function(){"use strict";const t={ZOOM_LEVEL:"80%",PROGRESS_PAGE_REGEX:"https://journal.top-academy.ru/.*/main/progress/.*"};class e{constructor(t,e){this.config={...t},this.onSaveCallback=e,this.modal=null,this.overlay=null,this.fields=[{key:"ZOOM_LEVEL",label:"Масштаб страницы (например: 80%)",type:"text",placeholder:"80%",validate:t=>""!==t.trim()||"Поле не должно быть пустым"}]}show(){document.getElementById("ta-config-modal")?document.getElementById("ta-config-modal").querySelector("input,textarea,button").focus():this.createModal()}createModal(){this.overlay=document.createElement("div"),this.overlay.id="ta-config-modal",Object.assign(this.overlay.style,{position:"fixed",inset:"0",background:"rgba(0,0,0,0.5)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:99999}),this.modal=document.createElement("div"),Object.assign(this.modal.style,{width:"520px",maxWidth:"95%",background:"#fff",borderRadius:"8px",padding:"16px",boxShadow:"0 10px 30px rgba(0,0,0,0.25)",fontFamily:"Arial, sans-serif",color:"#111"}),this.modal.innerHTML='\n\t\t\t<h2 style="margin:0 0 8px 0; font-size:18px;">Настройки скрипта</h2>\n\t\t\t<div style="margin-bottom:10px; color:#555; font-size:13px;">\n\t\t\t\tИзмените настройки и нажмите «Сохранить».\n\t\t\t</div>\n\t\t';const t=document.createElement("div");t.style.marginTop="8px",this.modal.appendChild(t),this.fields.forEach(e=>{const n=document.createElement("label");n.style.display="block",n.style.marginTop="8px",n.style.fontWeight="600",n.style.fontSize="13px",n.textContent=e.label;const s=document.createElement("input");s.type=e.type||"text",s.placeholder=e.placeholder||"",s.value=this.config[e.key]||"",s.dataset.key=e.key,Object.assign(s.style,{width:"100%",padding:"8px",marginTop:"6px",boxSizing:"border-box"}),n.appendChild(s),t.appendChild(n)});const e=document.createElement("div");Object.assign(e.style,{marginTop:"12px",display:"flex",gap:"8px",justifyContent:"flex-end"}),e.innerHTML='\n\t\t\t<button id="ta-cfg-defaults" style="padding:6px 10px; cursor:pointer;">Восстановить по умолчанию</button>\n\t\t\t<button id="ta-cfg-cancel" style="padding:6px 10px; cursor:pointer;">Отмена</button>\n\t\t\t<button id="ta-cfg-save" style="padding:6px 12px; background:#2b6cb0; color:#fff; border:none; border-radius:4px; cursor:pointer;">Сохранить</button>\n\t\t',this.modal.appendChild(e);const n=document.createElement("div");n.id="ta-cfg-error",n.style.cssText="margin-top:10px; color:#b00020; display:none; font-size:13px;",this.modal.appendChild(n),this.overlay.appendChild(this.modal),document.body.appendChild(this.overlay),this.setupEvents(n)}setupEvents(t){this.modal.querySelector('input[data-key="ZOOM_LEVEL"]');const e=this.modal.querySelector("#ta-cfg-save"),n=this.modal.querySelector("#ta-cfg-cancel"),s=this.modal.querySelector("#ta-cfg-defaults"),a=e=>{e?(t.style.display="block",t.textContent=e):(t.style.display="none",t.textContent="")},i=()=>{this.overlay&&this.overlay.parentNode&&this.overlay.parentNode.removeChild(this.overlay)};s.addEventListener("click",()=>{this.fields.forEach(t=>{this.modal.querySelector(`input[data-key="${t.key}"]`).value=t.placeholder||""}),a(null)}),n.addEventListener("click",i),e.addEventListener("click",async()=>{a(null);const t={};for(const e of this.fields){const n=this.modal.querySelector(`input[data-key="${e.key}"]`).value.trim();if(e.validate){const t=e.validate(n);if(!0!==t)return void a(t)}t[e.key]=n}try{"function"==typeof this.onSaveCallback&&await this.onSaveCallback(t),i()}catch(t){a("Не удалось сохранить настройки: "+(t.message||t))}}),this.overlay.addEventListener("click",t=>{t.target===this.overlay&&i()});window.addEventListener("keydown",t=>{"Escape"===t.key&&i()})}}function n(){let t=document.getElementById("attendance-stats");if(t)return t;const e=document.createElement("div");return e.id="attendance-stats",e.innerHTML='\n<header>\n <span>📊 Статистика</span>\n <button id="toggle-stats" title="Свернуть/развернуть">⯆</button>\n</header>\n<div class="stats-body" style="margin-top:8px;">\n <label>С: <input type="date" id="date-from"></label>\n <label>По: <input type="date" id="date-to"></label>\n <div style="margin-top:8px; display:flex; gap:6px;">\n <button id="reset-stats">Сбросить</button>\n <button id="refresh-stats" title="Обновить">↻</button>\n </div>\n <div id="stats-content" style="margin-top:10px; line-height:1.4;">\n Всего занятий: 0<br>\n <span class="present">Присутствия: 0</span><br>\n <span class="lateness">Опоздания: 0</span><br>\n <span class="absent">Пропуски: 0</span><br>\n Посещаемость: <b>0%</b>\n <div class="progress-bar"><div class="progress-bar-inner"></div></div>\n </div>\n</div>\n',document.body.appendChild(e),e}function s(){const t=document.getElementById("attendance-stats");t&&t.parentNode&&t.parentNode.removeChild(t)}GM_addStyle('\n #attendance-stats {\n position: fixed;\n top: 10%;\n right: 10px;\n background: #fff;\n border-radius: 12px;\n box-shadow: 0 4px 12px rgba(0,0,0,0.15);\n font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;\n font-size: 14px;\n width: 240px;\n padding: 12px;\n z-index: 9999;\n transition: all 0.3s ease;\n }\n\n #attendance-stats.collapsed {\n width: 40px;\n height: 40px;\n overflow: hidden;\n padding: 6px;\n }\n\n #attendance-stats header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n font-weight: 600;\n font-size: 15px;\n }\n\n #attendance-stats button {\n border: none;\n background: #e5e7eb;\n border-radius: 6px;\n padding: 4px 8px;\n margin-top: 6px;\n cursor: pointer;\n transition: background 0.2s;\n font-size: 13px;\n }\n #attendance-stats button:hover {\n background: #d1d5db;\n }\n\n #attendance-stats #toggle-stats {\n background: none;\n font-size: 16px;\n padding: 0;\n margin: 0;\n }\n\n #attendance-stats label {\n display: block;\n margin-top: 6px;\n font-size: 13px;\n }\n\n #attendance-stats input[type="date"] {\n width: 100%;\n padding: 6px;\n border: 1px solid #ccc;\n border-radius: 6px;\n margin-top: 4px;\n font-size: 13px;\n }\n\n #attendance-stats .present { color: #16a34a; }\n #attendance-stats .lateness { color: #d97706; }\n #attendance-stats .absent { color: #dc2626; }\n\n #attendance-stats .progress-bar {\n margin-top: 6px;\n height: 8px;\n border-radius: 4px;\n background: #e5e7eb;\n overflow: hidden;\n }\n #attendance-stats .progress-bar-inner {\n height: 100%;\n background: #16a34a;\n width: 0%;\n transition: width 0.4s ease;\n }\n\t#attendance-stats.collapsed .stats-body {\n\t\tdisplay: none;\n\t}\n\t#attendance-stats.collapsed header span {\n\t\tdisplay: none; /* скрываем текст "📊 Статистика", остаётся только кнопка */\n\t}\n');class a{constructor(t){this.CONFIG=t,this.widget=null,this.isCollapsed=!1,this.rangeStart=null,this.rangeEnd=null,this.updateAttendanceStats=function(t,e){let n;return function(...s){clearTimeout(n),n=setTimeout(()=>t.apply(this,s),e)}}(this.updateAttendanceStats.bind(this),300),this.init()}init(){this.setPageZoom(),this.isProgressPage()&&this.initAttendanceStats(),this.setupNavigationObserver()}isProgressPage(){return this.CONFIG.PROGRESS_PAGE_REGEX.test(window.location.href)}setPageZoom(){document.documentElement.style.zoom=this.CONFIG.ZOOM_LEVEL}initAttendanceStats(){this.widget||(this.widget=n(),this.setDefaultDateRange(),this.setupWidgetControls(),this.setupRangeSelection()),this.updateAttendanceStats()}setDefaultDateRange(){const t=new Date,e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),s=String(t.getDate()).padStart(2,"0");this.rangeStart=new Date(e,t.getMonth(),1),this.rangeEnd=new Date(e,t.getMonth(),t.getDate()),this.widget.querySelector("#date-from").value=`${e}-${n}-01`,this.widget.querySelector("#date-to").value=`${e}-${n}-${s}`}setupWidgetControls(){const t=this.widget.querySelector("#reset-stats"),e=this.widget.querySelector("#refresh-stats"),n=this.widget.querySelector("#toggle-stats");this.widget.querySelector("strong"),this.widget.querySelector(".stats-body"),t.addEventListener("click",()=>{this.setDefaultDateRange(),this.clearHighlights(),this.updateAttendanceStats()}),e.addEventListener("click",()=>{this.updateAttendanceStats(),this.highlightRange()}),n.addEventListener("click",()=>{this.isCollapsed=!this.isCollapsed,this.isCollapsed?(this.widget.classList.add("collapsed"),n.textContent="⯈"):(this.widget.classList.remove("collapsed"),n.textContent="⯆")})}updateAttendanceStats(){if(!this.isProgressPage())return void s();this.widget||(this.widget=n());const t=this.rangeStart,e=this.rangeEnd?new Date(this.rangeEnd):null;e&&e.setHours(23,59,59,999);const a=Array.from(document.querySelectorAll(".lessons, .lessons.lateness, .lessons.pass")).filter(n=>{const s=n.querySelector(".date")?.textContent.trim();if(!s)return!1;const[a,i,o]=s.split(".").map(Number),r=new Date(o,i-1,a);return(!t||r>=t)&&(!e||r<=e)}),i=a.length,o=a.filter(t=>t.classList.contains("lateness")).length,r=a.filter(t=>t.classList.contains("pass")).length,d=a.filter(t=>!t.classList.contains("pass")).length,l=i>0?(d/i*100).toFixed(1):0;this.widget.querySelector("#stats-content").innerHTML=`\n Всего занятий: ${i}<br>\n <span class="present">Присутствия: ${d}</span><br>\n <span class="lateness">Опоздания: ${o}</span><br>\n <span class="absent">Пропуски: ${r}</span><br>\n Посещаемость: <b>${l}%</b>\n <div class="progress-bar"><div class="progress-bar-inner" style="width:${l}%;"></div></div>\n`,this.highlightRange()}setupRangeSelection(){document.body.addEventListener("click",t=>{const e=t.target.closest(".lessons, .lessons.lateness, .lessons.pass");if(!e)return;const n=e.querySelector(".date");if(!n)return;const[s,a,i]=n.textContent.trim().split(".").map(Number),o=new Date(i,a-1,s);t.shiftKey||(this.rangeStart=o),this.rangeEnd=o,this.rangeStart&&this.rangeEnd&&this.rangeStart>this.rangeEnd&&([this.rangeStart,this.rangeEnd]=[this.rangeEnd,this.rangeStart]),this.updateAttendanceStats()})}highlightRange(){if(this.clearHighlights(),!this.rangeStart||!this.rangeEnd)return;Array.from(document.querySelectorAll(".lessons, .lessons.lateness, .lessons.pass")).map(t=>{const e=t.querySelector(".date");if(!e)return null;const[n,s,a]=e.textContent.trim().split(".").map(Number);return{lesson:t,dt:new Date(a,s-1,n)}}).filter(t=>t&&t.dt>=this.rangeStart&&t.dt<=this.rangeEnd).forEach(({lesson:t})=>{t.style.boxShadow="",t.style.border="",t.style.position=t.style.position||"relative",t.style.boxSizing="border-box",t.style.border="2px solid green"})}clearHighlights(){document.querySelectorAll(".lessons, .lessons.lateness, .lessons.pass").forEach(t=>{t.style.border=""})}setupNavigationObserver(){let t=window.location.href;new MutationObserver(()=>{const e=window.location.href;e!==t&&(t=e,this.isProgressPage()?this.initAttendanceStats():s())}).observe(document.body,{childList:!0,subtree:!0}),window.addEventListener("popstate",()=>{this.isProgressPage()?this.initAttendanceStats():s()})}cleanup(){s()}}(async()=>{const n=await async function(){const e=await GM_getValue("config",t);let n=t.PROGRESS_PAGE_REGEX;"string"==typeof e.PROGRESS_PAGE_REGEX?n=e.PROGRESS_PAGE_REGEX:e.PROGRESS_PAGE_REGEX&&"string"==typeof e.PROGRESS_PAGE_REGEX.source&&(n=e.PROGRESS_PAGE_REGEX.source);try{e.PROGRESS_PAGE_REGEX=new RegExp(n)}catch{console.warn("Невалидный Regex, используем дефолт"),e.PROGRESS_PAGE_REGEX=new RegExp(t.PROGRESS_PAGE_REGEX)}return e}(),s=new a(n);GM_registerMenuCommand("Настройки скрипта",()=>{new e(n,async t=>{await async function(t){const e={...t};e.PROGRESS_PAGE_REGEX instanceof RegExp&&(e.PROGRESS_PAGE_REGEX=e.PROGRESS_PAGE_REGEX.source),await GM_setValue("config",e)}(t),s.updateAttendanceStats()}).show()}),window.addEventListener("unload",()=>s.cleanup())})()}();