您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A tool for grabbing High School sports schedules on all the school sites (VNN) and also maxpreps.com and sports.deseret.com, and converting them into a simple plain text format for easy compilation and printing. By StevenRoy
// ==UserScript== // @name High School Sports Schedules in Plain Text // @namespace http://michrev.com/ // @description A tool for grabbing High School sports schedules on all the school sites (VNN) and also maxpreps.com and sports.deseret.com, and converting them into a simple plain text format for easy compilation and printing. By StevenRoy // @include *schedule* // @include *maxpreps.com/* // @include *sports.deseret.com/* // @version 0.26 // @grant none // @run-at document-start // @supportURL https://gf.qytechs.cn/en/users/934871 // ==/UserScript== "use strict"; // _____________ __ __ // / ___________/ / \,-' / // / /__ ___ / /\,-/ / StevenRoy was here // \___ \ / __\ / / / / //______/ / / / / / / / 02023.10.20 //_______/ /_/ /_/ /_/ window.addEventListener('beforescriptexecute', killtheadmiral); // ,true (?) if (top===self /*&& top.document.constructor=== HTMLDocument*/) { // weird things happen when loading images or PDF files? document.addEventListener("DOMContentLoaded", function(){ console.log("We got DOMContentLoaded - readyState:",document.readyState); // document.removeEventListener("DOMContentLoaded", arguments.callee, false); // Required? I doubt it! performTheMagic(document); },false); } else { console.log('Frame did a weird thing',top,"!==",self); } function performTheMagic(D){ if (top.location.href.match(/\/{2}[w.]{0,4}maxpreps\.com\/print\/schedule/i)) { return mpprintmode(D); } if (top.location.href.match(/\/{2}[w.]{0,4}maxpreps\.com/i)) { return waitfornextthing('MCS/mpmode',mpmode); } if (top.location.href.match(/\/{2}[w.]{0,4}sports\.deseret\.com/i)) { return waitfornextthing('MCS/dnmode',dnmode); } var t=D.location.pathname.match(/schedule/i); if (!(t && t.length)) return; // t=D.getElementsByClassName("gFMfPS"); t=D.getElementById("event-list-table"); if (t) { return vnnmode(D,t); } console.log('MCS: Found schedule page without schedule'); } function tcboo(l,f,e) { // there can be only one if (l && l.length==1) { return f(l[0]); } else if (e) return e(l); // failure function } function trims(s) { return s.trim().replace(/ {2,}/g," "); } var plink; // TODO: better template format, user-configurable templates const _rrr={ // the raw row renderer thisteam:'', // NOTE: reset() does not clear this one! reset() { this.d='TBD'; this.t='TBD'; this.ht=false; this.vt=false; this.ot=false; this.away=false; this.extra=''; }, // template='date time @o'; // Fri Aug 27 7:00 PM @ Granger _template:'dxvt', // date, visitor@home team, time render() { return trims(this._template.split('').map((i)=>{ switch(i){ case 'd': return this.d; // "Fri Aug 20" case 't': return this.t; // "7:00 PM" case 'v': if (!(this.vt && this.ht)) { if (!(this.ot && this.thisteam)) throw ("RRR/v without teams set"); if (this.away) { this.vt=this.thisteam; this.ht=this.ot; } else { this.ht=this.thisteam; this.vt=this.ot; } } return this.vt+" @ "+this.ht; // visitor @ home teams case 'o': if (!(this.ot)) { if (!(this.vt && this.ht && this.thisteam)) throw ("RRR/o without teams set"); this.away=this.vt==this.thisteam; this.ot=this.away?this.ht:this.vt; } return (this.away?"":"@ ")+this.ot; // o += d+(r.opponentTeam.homeAwayType?" ":" @ ")+r.opponentTeam.name+"\n"; case 'x': return ("extra" in this) ? this.extra : ""; case ',': case ':': case '-': return i; // a literal character default: throw("RRR Template broke at character "+i); } }).join(' ').replace(/ [,:-]/g,",")); // no spaces before punctuation } }; function hideparalink(){ if (!plink) return; plink.style.display="none"; plink.innerHTML=''; } function createparalink(t,cb){ // TODO: Rethink this UI, make a menu or something if (!plink) { plink = document.createElement('a'); plink.setAttribute('href', '#'); plink.setAttribute('style', 'position:fixed; text-align:center; bottom:0px; right:0px; z-index:9099998;'+ 'border:3px solid #789; box-sizing:border-box; font-family: Courier New; font-weight: 700;' + 'background:#bcd; margin:0; padding:6px; text-decoration:none; color:#456; font-size:15pt; line-height:14pt'); document.body.appendChild(plink); } plink.innerHTML=t; plink.style.display="block"; plink.onclick=(e)=>{ if (!e) e=window.event; if (e.preventDefault) e.preventDefault(); try { cb(); } catch (X) { window.alert("Well, that didn't work...\nInstead we got this:\n\n"+X); } }; } function setClipboard(text,forcefallback) { // TODO: Test this in Vivaldi, it might be broken! (a Chromium quirk perhaps?) log("MCS: Setting clipboard"+(forcefallback?" (fallback)":""),text); if (navigator.clipboard && navigator.clipboard.writeText && !forcefallback) { // Supported anywhere? Might remove. try { navigator.clipboard.writeText(text).then(function() { log('Async: Copying to clipboard was successful!'); window.alert("The following text has been copied to your clipboard:\n\n"+text); }, function(err) { console.error('Async: Could not copy text: ', err); setClipboard(text,1); }); } catch (err) { console.error('Sync: Could not copy text: ', err); setClipboard(text,1); } return; } var textArea = document.createElement("textarea"); textArea.value = text; textArea.style.position="fixed";// Avoid scrolling to bottom textArea.style.top = "0"; textArea.style.left = "-9000px"; // textArea.style.display= "none"; // Does this cause it to not work? Weird. document.body.appendChild(textArea); // Necessary too? Probably is! textArea.focus(); textArea.select(); try { var successful = document.execCommand('copy'); var msg = successful ? 'successful' : 'unsuccessful'; log('Fallback: Copying text command was ' + msg); if (successful) { window.alert("The following text has been copied to your clipboard:\n\n"+text); } } catch (err) { console.error('Fallback: Oops, unable to copy', err); } document.body.removeChild(textArea); } function vnnmode(D,E){ // this covers most high school sites var rows=Array.from(E.getElementsByClassName("event")).filter(e=>!e.classList.contains('print-hide')); if (!rows.length) return; // How to determine the current school name? // Delving into all the "meta" tags could work but could it get messy? // For example: <meta property="og:description" content="Davis High School"/> tcboo(Array.from(document.head.getElementsByTagName("meta")).filter(n=> n.hasAttribute("property")&&n.hasAttribute("content")&&n.getAttribute("property").match(/^og:description$/i)),n=>{ _rrr.thisteam=n.getAttribute("content").replace(/ +high school.*$/i,''); },e=>{ _rrr.thisteam="(TEAM)"; }); // That wasn't so bad...? createparalink("Copy schedule<br />("+rows.length+" rows)<br />to clipboard",()=>{ var o=''; // Output line: Tue, 11/24 7:00 PM Box Elder log('MCS: VNN mode. Rows:',rows); rows.forEach(e=>{ // log('MCS: Row',e); tcboo(e.getElementsByClassName('date print-hide'),(tds)=>{ _rrr.reset(); // tds=tds[0]; o+=trims(tds.textContent).replace(/,/g,''); // worse: <td class="date print-show" style="display:none;"> Fri, Dec 4 7:00 pm </td> // better: <td class="date print-hide pl-3 date-mobile" data-event-date="Mon, Nov 20" data-event-start-time=" 7:00 pm"> let ta= tds.getAttribute("data-event-date")||""; ta=trims(ta).replace(/,/g,''); if (ta) _rrr.d=ta; ta= tds.getAttribute("data-event-start-time")||""; ta=trims(ta).replace(/,/g,''); if (ta) _rrr.t=ta; // TODO: Might it eventually become really useful to parse that into a proper // Date object so that it can be rendered by RRR with more template options? tcboo(e.getElementsByClassName('home-away'),(tds)=>{ tds=trims(tds.textContent).toUpperCase(); _rrr.away= (tds!='HOME'); // o += ' @'; }); tcboo(e.getElementsByClassName('opponent'),(tds)=>{ _rrr.ot = trims(tds.textContent.replace(/ high school/i,'')); tcboo(tds.parentNode.getElementsByClassName('event-name'),(tds)=>{ tds=trims(tds.textContent); if (tds && (tds !== '-')) { _rrr.extra = ' '+tds; } }); }); o += _rrr.render()+"\n"; }); }); if (o) { setClipboard(o); } }); } /* If they're going to keep changing the class names, we can't use them! BUT the printable pages don't use the random names; should we try to load those instead? https:// www.maxpreps.com/print/schedule.aspx?schoolid= # &ssid= # &print=1 How to extract schoolid/"teamId" and ssid/"sportSeasonId" from current page? JSON.stringify(__NEXT_DATA__.props.pageProps.schedule[1]) And from all that, we need something that looks like: Fri 12/4 7:00 PM Pleasant Grove __NEXT_DATA__.props.pageProps.schedule[0].isDeleted == true *** WATCH OUT FOR "DELETED" ROWS! */ function mpmode(r){ // the maxpreps site // var r=window.__NEXT_DATA__; // if (!r) return log("__NEXT_DATA__ not found?"); // r=r.props; // if (r) r=r.pageProps; if (r) r=r.schedule || r.contests; // A recent change? if (!(r && r.length)) { hideparalink(); return log("Schedule not found"); } r=r.filter(x=>!(x.isDeleted || x.opponentTeam.isTeamTBA || x.isDateTba)); // Gotta watch out for this! createparalink("Copy schedule<br />("+r.length+" rows)<br />to clipboard",()=>{ enterlogsection('MCS/mpmode',()=>{ var o=''; r.forEach((r)=>{ _rrr.reset(); var d=r.isDateTba||r.isTimeTba; if (d) { d="TBD"; } else { d=new Date(r.date); // "2022-08-12T19:00:00" -> 8/12 7:00pm _rrr.d=d.toDateString().replace(/ +[0-9]{4}$/,''); // "Fri Dec 04 2020" _rrr.t=d.toLocaleTimeString('en',{hour:'numeric',minute:'2-digit'}); // "7:00 PM" } if (r.currentTeam && !_rrr.thisteam) { _rrr.thisteam=r.currentTeam.name; } if (!r.opponentTeam) { return log('No opponent team?',r); } // Skip this if no team info // if (r.opponentTeam.isTeamTBA) { // o += d+(r.opponentTeam.homeAwayType?" ":" @ ")+"TBA\n"; // } else { _rrr.away=!r.opponentTeam.homeAwayType; // if (!r.opponentTeam.name) { return log('Opponent team has no name?',r); } _rrr.ot=r.opponentTeam.name || "(No name?)"; // o += d+(r.opponentTeam.homeAwayType?" ":" @ ")+r.opponentTeam.name+"\n"; if (r.location) { _rrr.extra=r.location; } // Extra game info, not always location? o += _rrr.render()+"\n"; // } }); /* THE OLD WAY and guess how often this breaks and why... t=E.getElementsByClassName('kefwvP'); if (t && t.length) { Array.from(t).forEach(r=>{ // The "featured" game(s?) console.log("MCS/mpmode: processing featured:",r); to=''; tt=r.getElementsByClassName('foHRy'); if (!(tt && tt.length==1)) { return; } // skip this row if we don't find date to=trims(tt[0].textContent).replace(/,/g,''); tt=r.getElementsByClassName('dfAURx'); if (!(tt && tt.length==1)) { return; } // skip this row if we don't find time to+=" "+trims(tt[0].textContent); tt=r.getElementsByClassName('homeAway'); if (!(tt && tt.length==1)) { return; } // skip this row if we don't find home/away tt=trims(tt[0].textContent); if (tt.match(/^away/i)) to += ' @'; tt=r.getElementsByClassName('hdmKYN'); if (!(tt && tt.length==2)) { return; } // didn't find opponent team name o+=to+' '+trims(tt[1].textContent)+"\n"; // opponent team name ...and then we done! }); } else console.log("MCS/mpmode: no featured?",t); t=E.getElementsByClassName('guknjX'); if (t && t.length) { Array.from(t).forEach(r=>{ // The other games console.log("MCS/mpmode: processing row:",r); to=trims(r.textContent).replace(/,/g,''); t=r.nextSibling; if (!t) { console.log("MCS/mpmode: Borked row, date but no time?",r,to); return; } to += ' '+trims(t.textContent); r=r.parentNode.parentNode; // this should point to the tr now tt=r.getElementsByClassName('cKEqht'); if (!(tt && tt.length==1)) { console.log("MCS/mpmode: no home/away"); return; } // didn't find home/away tt=trims(tt[0].textContent); if (tt.match(/^away/i)) to += ' @'; tt=r.getElementsByClassName('align-x'); // They don't even use their random class names consistently!? if (!(tt && tt.length==1)) { console.log("MCS/mpmode: no align-x"); return; } tt=tt[0].children; // But at least we can (apparently) get to it this way... if (!(tt && tt.length>=1)) { console.log("MCS/mpmode: no name element"); return; } o+=to+' '+trims(tt[0].textContent.replace(/ high school/i,''))+"\n"; // Replace probably unnecessary here? }); } else console.log("MCS/mpmode: no rows?",t); */ if (o) { setClipboard(o); } }); }); } function mpprintmode(D) { // schedule print page - VERY different! var r=D.getElementById("schedule"); if (r) r=r.getElementsByClassName('dual-contest'); if (!(r && r.length)) return log("Schedule not found",r); createparalink("Copy schedule<br />("+r.length+" rows)<br />to clipboard",()=>{ enterlogsection('MPPrint',()=>{ var o=''; if (utag_data && utag_data.schoolName) { _rrr.thisteam=utag_data.schoolName.toUpperCase().replace(/ \(.*\)$/,"") .replace(/([A-Z])([A-Z]+)/g,(m,a,b)=>{return a+b.toLowerCase(); }); // Mixed case (because page does toLowerCase() to it) } Array.from(r).forEach((r)=>{ _rrr.reset(); /* var d=r.isDateTba||r.isTimeTba; if (d) { d="TBD"; } else { */ var t,d=r.getElementsByClassName('event-time'); if (d && d[0] && d[0].textContent.toUpperCase()!='TBD') { t=new Date(d[0].getAttribute("title")+".000Z"); // "2022-08-13T02:00:00" -> 8/12 7:00pm ??? _rrr.d=t.toDateString().replace(/ +[0-9]{4}$/,''); // "Fri Dec 04 2020" // _rrr.t=t.toLocaleTimeString('en',{timeStyle:'short',hour:'numeric',minute:'2-digit'}); // "7:00 PM" // information here seems to be in the wrong TZ? (we get 8 PM instead of 7) So instead we just... _rrr.t=d[0].innerText; // "7:00p", assume correct time zone in the plain text // (And it's worse if we don't add the ".000Z" above. Never had to do that before.) // TODO: Parsing event-date seems potentially easier. Getting those numbers into a Date object less so though. } // if (!(r.home && r.home.team)) { return log('No home team?',r); } // Skip this if no team info // if (!(r.visitor && r.visitor.team)) { return log('No home team?',r); } // Skip this if no team info // _rrr.ht=r.home.team.name; // _rrr.vt=r.visitor.team.name; t=r.getElementsByClassName('contest-name'); if (t && t[0]) { t=t[0].innerText.replace(/ \(.*\)$/,""); if (t.match(/^@ /)) { _rrr.away=true; t=t.substr(2); } _rrr.ot=t; // opposing team } t=r.getElementsByClassName('contest-location'); if (t && t[0]) { t=t[0].textContent.match(/^Game Details: (.+)$/); if (t && t[1]) _rrr.extra=t[1]; } o += _rrr.render()+"\n"; }); if (o) { setClipboard(o); } }); }); } /* Very surprisingly, Deseret News also uses __NEXT_DATA__ ...Though in a different way so we need a different parser. */ function dnmode(r){ // var r=window.__NEXT_DATA__; // if (!r) return log("__NEXT_DATA__ not found?"); // r=r.props; // if (r) r=r.pageProps; if (r) { if (r.team && r.team.name) { _rrr.thisteam= r.team.name; } // TODO: Implement this in the other modes? r=r.previousMatchupGames; } if (!(r && r.length)) return log("Schedule not found",r); // r=r.filter(x=>!(x.isDeleted || x.opponentTeam.isTeamTBA || x.isDateTba)); // Gotta watch out for this? createparalink("Copy schedule<br />("+r.length+" rows)<br />to clipboard",()=>{ enterlogsection('MCS/dnmode',()=>{ var o=''; r.forEach((r)=>{ _rrr.reset(); /* var d=r.isDateTba||r.isTimeTba; if (d) { d="TBD"; } else { */ var d=new Date(r.date); _rrr.d=d.toDateString().replace(/ +[0-9]{4}$/,''); // "Fri Dec 04 2020" _rrr.t=d.toLocaleTimeString('en',{hour:'numeric',minute:'2-digit'}); // "7:00 PM" // } if (!(r.home && r.home.team)) { return log('No home team?',r); } // Skip this if no team info if (!(r.visitor && r.visitor.team)) { return log('No home team?',r); } // Skip this if no team info _rrr.ht=r.home.team.name; _rrr.vt=r.visitor.team.name; if (r.tournament) _rrr.extra=r.tournament; o += _rrr.render()+"\n"; }); if (o) { setClipboard(o); } }); }); } var logsection=''; function enterlogsection(n,c,p){ var ols=logsection; log('ENTERING SECTION:',n); if (ols==='') { logsection=n; } else { logsection+="/"+n; } try { if (p) { c(p); } else { c(); } } catch (X) { console.error(logsection+" EXCEPTION: ",X); } logsection=ols; log('LEFT SECTION:',n); } function log() { console.log(logsection,...arguments); } function retry(count,condfunc,delay,scb,fcb,param) { if (count<1) return fcb&&fcb(param); // failed if (condfunc(param)) return scb&&scb(param); // condition returns true to indicate success return window.setTimeout(()=>{ retry(count-1,condfunc,delay,scb,fcb,param); },delay); } function waitfornextthing(lsname,func){ retry(20,(p)=>{ // conditional var e; p.c++; if (e=window.__NEXT_DATA__) { if (e.props && e.props.pageProps) { p.e=e.props.pageProps; return true; } // success p.cn++; } return false; },500,(p)=>{ log("After "+p.c+" pageProps searches and "+p.cn+" invalid results..."); enterlogsection(lsname,func,p.e); var lasturl=top.location.href; next.router.events.on('routeChangeStart',(u)=>{ // OH THANK GOODNESS this exists and is usable... if (u!=lasturl) { // window.alert("Going to:\n"+u); lasturl=u; hideparalink(); // When a different page is navigated to, new __NEXT_DATA__ is read by the site JS but not stored in an // accessible variable, so we need to force a full page load every time. SO ANNOYING! if (u.match(/schedule\./i)) { top.location.replace(u); return; } // A cheap workaround but it works! } }); },(p)=>{ log("FAILED After "+p.c+" pageProps searches and "+p.cn+" invalid results."); },{c:0,cn:0}); } function killtheadmiral(e) { if (e.target.src!="") return; var et=e.target.innerText; function yeskillit() { e.preventDefault(); e.stopPropagation(); console.log('*zzzap!*'); } // console.log("Checking:",et); // This is a thing on MaxPreps site that makes it obnoxious. // (Checking for it on all sites is probably overkill - but that's better than underkill.) if (et.match(/cbsoptanon/)) { console.log('Killed an Admiral script'); return yeskillit(); // *zzzap!* } if (et.replace(/[ \t\n\r]+/g,'')=="window.print();") { // Maxpreps print page doesn't need this! console.log('Killed automatic-printing script'); // window.print=function(){ throw(new ReferenceError("Intercepted")); }; return yeskillit(); // *zzzap!* } }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址