NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name KsqLAaq // @description asadqs // @match https://klas.kw.ac.kr/* // @run-at document-start // @homepageURL https://github.com/nbsp1221/klas-helper // @supportURL https://github.com/nbsp1221/klas-helper/issues // @author kyh2707 // @copyright 2020, nbsp1221 (https://openuserjs.org/users/nbsp1221) // @license MIT // @grant GM.xmlHttpRequest // ==/UserScript== // Element 생성 function createElement(elementName, htmlCode) { const newElement = document.createElement(elementName); newElement.innerHTML = htmlCode; return newElement; } // 새 창으로 열기 function openLinkNewWindow(url, urlDatas = null, features = null) { let completeURL = url; let completeFeatures = ''; if (urlDatas) { completeURL += '?'; for (const name in urlDatas) completeURL += `${name}=${urlDatas[name]}&`; completeURL = completeURL.substring(0, completeURL.length - 1); } if (features) { for (const name in features) completeFeatures += `${name}=${features[name]},`; completeFeatures = completeFeatures.substring(0, completeFeatures.length - 1); } window.open(completeURL, '', completeFeatures); } // 소수점 버림 함수 function floorFixed(number, decimalPlace = 2) { const pow10 = 10 ** decimalPlace; return (Math.floor(number * pow10) / pow10).toFixed(decimalPlace); } // 콘솔에 오류 띄우기 function consoleError(error, info) { console.error(`[KLAS Helper Error]\n${info.title}: ${info.content}\nMessage: ${error.message}`); } // 메인 실행 함수 (function () { 'use strict'; // 기본 함수 삽입 document.head.appendChild(createElement('script', createElement.toString() + openLinkNewWindow.toString() + floorFixed.toString() + consoleError.toString() )); const externalLibs = [ 'https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js' ]; // 외부 라이브러리 삽입 for (const src of externalLibs) { const scriptElement = document.createElement('script'); scriptElement.setAttribute('src', src); document.head.appendChild(scriptElement); } // window.onload 설정 window.addEventListener('load', () => { // externalPathFunctions 함수 삽입 for (const path in externalPathFunctions) { if (path === location.pathname) { document.head.appendChild(createElement('script', `(${externalPathFunctions[path].toString()})();`)); } } }); })(); // 태그에 삽입되는 함수 목록 // 다른 확장 프로그램을 지원하기 위해 태그 삽입이 필요 const externalPathFunctions = { // 강의 계획서 조회 - 학부 '/std/cps/atnlc/LectrePlanStdPage.do': () => { let waitSearch = false; // 인증 코드 개선 및 메시지 제거 appModule.getSearch = function () { this.selectYearHakgi = this.selectYear + ',' + this.selecthakgi; // 서버 부하를 방지하기 위해 모든 강의 계획서 검색 방지 if (this.selectRadio === 'all' && this.selectText === '' && this.selectProfsr === '' && this.cmmnGamok === '' && this.selecthakgwa === '') { alert('과목명 또는 담당 교수를 입력하지 않은 경우 반드시 과목이나 학과를 선택하셔야 합니다.'); return false; } // 서버 부하를 방지하기 위해 2초간 검색 방지 if (waitSearch) { alert('서버 부하 문제를 방지하기 위해 2초 뒤에 검색이 가능합니다.'); return false; } // 2초 타이머 waitSearch = true; setTimeout(() => { waitSearch = false; }, 2000); axios.post('LectrePlanStdList.do', this.$data).then(function (response) { this.list = response.data; }.bind(this)); }; // 강의 계획서 새 창으로 열기 appModule.goLectrePlan = function (item) { if (item.closeOpt === 'Y') { alert('폐강된 강의입니다.'); return false; } if (!item.summary) { alert('강의 계획서 정보가 없습니다.'); return false; } openLinkNewWindow( 'popup/LectrePlanStdView.do', { selectSubj: 'U' + item.thisYear + item.hakgi + item.openGwamokNo + item.openMajorCode + item.bunbanNo + item.openGrade, selectYear: item.thisYear, selecthakgi: item.hakgi }, { width: 1000, height: 800, scrollbars: 'yes', title: '강의 계획서 조회' } ); }; // 안내 문구 렌더링 document.querySelector('table:nth-of-type(1) tr:nth-of-type(5) td').innerText = '인증 코드를 입력하실 필요가 없습니다.'; // 엔터로 강의 계획서 검색 $('table:nth-of-type(1) input[type="text"]').keydown((event) => { if (event.keyCode === 13) { appModule.getSearch(); } }); }, // 강의 계획서 조회 - 대학원 '/std/cps/atnlc/LectrePlanGdhlStdPage.do': () => { // 인증 코드 개선 및 메시지 제거 appModule.getSearch = function () { if (!this.selectGdhlitem) { alert('대학원을 선택해 주세요.'); return; } axios.post('LectrePlanDaList.do', this.$data).then(function (response) { this.GdhlList = response.data; }.bind(this)); }; // 안내 문구 렌더링 document.querySelector('table:nth-of-type(1) tr:nth-of-type(4) td').innerText = '인증 코드를 입력하실 필요가 없습니다.'; // 엔터로 강의 계획서 검색 $('table:nth-of-type(1) input[type="text"]').keydown((event) => { if (event.keyCode === 13) { appModule.getSearch(); } }); }, // 수강 및 성적 조회 '/std/cps/inqire/AtnlcScreStdPage.do': () => { const scoreTimer = setInterval(() => { if (!appModule) { return; } if (appModule.$data.sungjuk.length === 0) { return; } const scoreDatas = appModule.$data.sungjuk; let htmlCode = ''; let trCode = ''; const labelList = []; const majorScoreList = []; const notMajorScoreList = []; const allScoreList = []; for (let i = scoreDatas.length - 1; i >= 0; i--) { const year = scoreDatas[i].thisYear; const semester = scoreDatas[i].hakgi; const scoreInfo = scoreDatas[i].sungjukList; // 계절 학기의 경우 계산에서 제외 if (semester > 2) continue; // 표 순서대로 평점 정보 기록 const gpaInfo = scoreInfo.reduce((acc, cur) => { const changer = { 'A+': 4.5, 'A0': 4.0, 'B+': 3.5, 'B0': 3.0, 'C+': 2.5, 'C0': 2.0, 'D+': 1.5, 'D0': 1.0, 'F': 0, 'P': 0, 'NP': 0 }; const code = cur.codeName1.trim(); const credit = parseInt(cur.hakjumNum); const grade = cur.getGrade.trim(); const isMajor = code[0] === '전'; const isPass = ['A+', 'A0', 'B+', 'B0', 'C+', 'C0', 'D+', 'D0', 'P'].includes(grade); const isIncludeF = ['A+', 'A0', 'B+', 'B0', 'C+', 'C0', 'D+', 'D0', 'F', 'NP'].includes(grade); const isNotIncludeF = ['A+', 'A0', 'B+', 'B0', 'C+', 'C0', 'D+', 'D0'].includes(grade); acc[0] += isPass ? credit : 0; // 취득 학점 acc[1] += isMajor && isIncludeF ? changer[grade] * credit : 0; // 전공 총점 (F 포함) acc[2] += isMajor && isIncludeF ? credit : 0; // 전공 취득 학점 (F 포함) acc[3] += isMajor && isNotIncludeF ? changer[grade] * credit : 0; // 전공 총점 (F 미포함) acc[4] += isMajor && isNotIncludeF ? credit : 0; // 전공 취득 학점 (F 미포함) acc[5] += !isMajor && isIncludeF ? changer[grade] * credit : 0; // 전공 외 총점 (F 포함) acc[6] += !isMajor && isIncludeF ? credit : 0; // 전공 외 취득 학점 (F 포함) acc[7] += !isMajor && isNotIncludeF ? changer[grade] * credit : 0; // 전공 외 총점 (F 미포함) acc[8] += !isMajor && isNotIncludeF ? credit : 0; // 전공 외 취득 학점 (F 미포함) acc[9] += isIncludeF ? changer[grade] * credit : 0; // 전체 총점 (F 포함) acc[10] += isIncludeF ? credit : 0; // 전체 취득 학점 (F 포함) acc[11] += isNotIncludeF ? changer[grade] * credit : 0; // 전체 총점 (F 미포함) acc[12] += isNotIncludeF ? credit : 0; // 전체 취득 학점 (F 미포함) return acc; }, Array(13).fill(0)); if (gpaInfo[0] === 0) { gpaInfo[0] = '-'; } // 평점 계산 for (let j = 1; j < gpaInfo.length; j += 2) { gpaInfo[j] = gpaInfo[j + 1] > 0 ? floorFixed(gpaInfo[j] / gpaInfo[j + 1]) : '-'; } trCode += ` <tr> <td>${year}학년도 ${semester}학기</td> <td>${gpaInfo[0]}</td> <td>${gpaInfo[1]}</td> <td>${gpaInfo[3]}</td> <td>${gpaInfo[5]}</td> <td>${gpaInfo[7]}</td> <td>${gpaInfo[9]}</td> <td>${gpaInfo[11]}</td> </tr> `; // 차트 데이터 생성 if (gpaInfo[0] !== '-') { labelList.push([`${year}학년도`, `${semester}학기`]); majorScoreList.push(gpaInfo[1]); notMajorScoreList.push(gpaInfo[5]); allScoreList.push(gpaInfo[9]); } } htmlCode += ` <table class="tablegw"> <colgroup> <col width="25%"> <col width="15%"> <col width="10%"> <col width="10%"> <col width="10%"> <col width="10%"> <col width="10%"> <col width="10%"> </colgroup> <thead> <tr> <th rowspan="2">학기</th> <th rowspan="2">취득 학점</th> <th colspan="2">전공 평점</th> <th colspan="2">전공 외 평점</th> <th colspan="2">평균 평점</th> </tr> <tr> <th>F 포함</th> <th>미포함</th> <th>F 포함</th> <th>미포함</th> <th>F 포함</th> <th>미포함</th> </tr> </thead> <tbody> ${trCode} </tbody> </table> `; // 평점 정보 렌더링 document.querySelector('table[width="100%"]').before(createElement('div', `<br>${htmlCode}<br>`)); // 평점 차트 그리기 if (labelList.length > 0) { document.querySelector('table[width="100%"]').before(createElement('div', ` <div style="margin-bottom: 20px"> <canvas id="chart-score"></canvas> </div> `)); const ctx = document.getElementById('chart-score'); ctx.height = 80; const chart = new Chart(ctx, { type: 'line', data: { labels: labelList, datasets: [ { label: '전공 평점', data: majorScoreList, borderColor: '#E74C3C', borderWidth: 1, fill: false, lineTension: 0, pointBackgroundColor: 'white', pointRadius: 5 }, { label: '전공 외 평점', data: notMajorScoreList, borderColor: '#2980B9', borderWidth: 1, fill: false, lineTension: 0, pointBackgroundColor: 'white', pointRadius: 5 }, { label: '평균 평점', data: allScoreList, borderColor: '#BDC3C7', borderWidth: 2, fill: false, lineTension: 0, pointBackgroundColor: 'white', pointRadius: 5 } ] }, options: { scales: { yAxes: [{ ticks: { suggestedMin: 2, suggestedMax: 4.5, stepSize: 0.5 } }] }, tooltips: { callbacks: { title: (tooltipItem, data) => { const xLabel = tooltipItem[0].xLabel; return xLabel[0] + ' ' + xLabel[1]; } } } } }); } clearInterval(scoreTimer); }, 100); }, // 석차 조회 '/std/cps/inqire/StandStdPage.do': () => { // 재학했던 학기의 모든 석차 조회 $('.tablegw').after(` <div style="margin-top: 10px"> <button type="button" class="btn2 btn-learn btn-ranking">이전 석차 내역 불러오기</button> </div> `); $('.btn-ranking').click(() => { // 현재 연도, 학기, 입학연도 획득 let nowYear = appModule.$data.selectYear; let nowSemester = appModule.$data.selectHakgi; const admissionYear = parseInt(appModule.$data.info[0].hakbun.substring(0, 4)); // 버튼 숨기기 $('.btn-ranking').hide(); // 비동기 문제로 타이머 사용 const syncTimer = setInterval(() => { if (nowSemester === '2') { nowSemester = '1'; } else { nowYear--; nowSemester = '2'; } const params = { 'selectYearhakgi': nowYear + ',' + nowSemester, 'selectChangeYn': 'Y' }; // 석차 조회 axios.post('/std/cps/inqire/StandStdList.do', params).then(function (response) { if (response.data) { $('table.AType > tbody').append(` <tr> <td>${response.data.thisYear}</td> <td>${response.data.hakgi}</td> <td>${response.data.applyHakjum}</td> <td>${response.data.applySum}</td> <td>${response.data.applyPoint}</td> <td>${response.data.pcnt}</td> <td>${response.data.classOrder} / ${response.data.manNum}</td> <td>${response.data.warningOpt ? response.data.warningOpt : ''}</td> </tr> `); return; } if (nowYear < admissionYear) { clearInterval(syncTimer); alert('석차 정보를 모두 불러왔습니다.'); } }.bind(this)); }, 500); }); }, // 강의 종합 '/std/lis/evltn/LctrumHomeStdPage.do': () => { // 인증 팝업 무시 lrnCerti.certiCheck = function (grcode, subj, year, hakgi, bunban, module, lesson, oid, starting, contentsType, weeklyseq, weeklysubseq, width, height, today, sdate, edate, ptype, totalTime, prog, gubun) { console.log(grcode, subj, year, hakgi, bunban, module, lesson, oid, starting, contentsType, weeklyseq, weeklysubseq, width, height, today, sdate, edate, ptype, totalTime, prog, gubun); let self = this; self.grcode = grcode; self.subj = subj; self.weeklyseq = weeklyseq; self.gubun = gubun; axios.post('/std/lis/evltn/CertiStdCheck.do', self.$data) .then(function () { appModule.goViewCntnts(grcode, subj, year, hakgi, bunban, module, lesson, oid, starting, contentsType, weeklyseq, weeklysubseq, width, height, today, sdate, edate, ptype, totalTime, prog); }.bind(this)); }; }, // 온라인 강의 컨텐츠 보기 '/std/lis/evltn/OnlineCntntsStdPage.do': () => { // 2분 쿨타임 제거 버튼 생성 $('#appModule > table:not(#prjctList) > tbody').append(` <tr><td> <div style="margin-bottom: 5px">※ 2분 쿨타임을 제거할 수 있습니다. 단, 동시에 여러 컨텐츠 학습을 하지 않도록 주의해 주세요.</div> <button type="button" class="btn2 btn-learn btn-cooldown">2분 쿨타임 제거</button> </td></tr> `); // 2분 쿨타임 제거 버튼에 이벤트 설정 $('.btn-cooldown').click(() => { appModule.getLrnSttus = function (param) { let self = this; axios.post('/std/lis/evltn/SelectLrnSttusStd.do', self.$data).then(function (response) { self.lrnSttus = response.data; if (response.data === 'Y' || response.data === 'N') { if (ios) { $('#viewForm').prop('target', '_blank').prop('action', '/spv/lis/lctre/viewer/LctreCntntsViewSpvPage.do').submit(); } else { let popup = window.open('', 'previewPopup', 'resizable=yes, scrollbars=yes, top=100px, left=100px, height=' + self.height + 'px, width= ' + self.width + 'px'); $('#viewForm').prop('target', 'previewPopup').prop('action', '/spv/lis/lctre/viewer/LctreCntntsViewSpvPage.do').submit().prop('target', ''); popup.focus(); } } else if (response.request.responseURL.includes('LoginForm.do')){ linkUrl(response.request.responseURL); } }.bind(this)); }; alert('2분 쿨타임이 제거되었습니다.'); }); // 저작권 안내 문구 렌더링 $('#appModule > table:not(#prjctList) > tbody').append(` <tr> <td> <div style="color: red; margin-top: 10px">※ 온라인 강의 시 사용되는 강의 내용을 공유 및 배포하는 것은 저작권을 침해하는 행위이므로 꼭 개인 소장 용도로만 이용해 주시기 바랍니다.</div> </td> </tr> `); // 인증 팝업 무시 lrnCerti.certiCheck = function (grcode, subj, year, hakgi, bunban, module, lesson, oid, starting, contentsType, weeklyseq, weeklysubseq, width, height, today, sdate, edate, ptype, totalTime, prog, gubun) { console.log(grcode, subj, year, hakgi, bunban, module, lesson, oid, starting, contentsType, weeklyseq, weeklysubseq, width, height, today, sdate, edate, ptype, totalTime, prog, gubun); let self = this; self.grcode = grcode; self.subj = subj; self.weeklyseq = weeklyseq; self.gubun = gubun; axios.post('/std/lis/evltn/CertiStdCheck.do', self.$data) .then(function () { appModule.goViewCntnts(grcode, subj, year, hakgi, bunban, module, lesson, oid, starting, contentsType, weeklyseq, weeklysubseq, width, height, today, sdate, edate, ptype, totalTime, prog); }.bind(this)); }; // 온라인 강의 고유 번호 파싱 /* appModule.$watch('list', function (watchValue) { const videoCodes = []; let videoCount = 0; for (let i = 0; i < watchValue.length; i++) { videoCount += watchValue[i].hasOwnProperty('starting'); } for (let i = 0; i < watchValue.length; i++) { const videoInfo = watchValue[i]; let videoCode = ''; if (!videoInfo.hasOwnProperty('starting')) { continue; } // 예외인 고유 번호는 직접 파싱해서 처리 if (videoInfo.starting === null || videoInfo.starting === 'default.htm') { const postData = []; for (const key in videoInfo) postData.push(`${key}=${videoInfo[key]}`); axios.post('/spv/lis/lctre/viewer/LctreCntntsViewSpvPage.do', postData.join('&')).then(function (response) { if (response.data.indexOf('kwcommons.kw.ac.kr/em/') === -1) { videoCode = undefined; } else { videoCode = response.data.split('kwcommons.kw.ac.kr/em/')[1].split('"')[0]; } }); } else { videoCode = videoInfo.starting.split('/'); videoCode = videoCode[videoCode.length - 1]; } const syncTimer = setInterval(() => { if (videoCode === undefined) { videoCount--; clearInterval(syncTimer); } else if (videoCode !== '') { videoCodes.push({ index: i, videoCode }); clearInterval(syncTimer); } }, 100); } // table 태그에 고유 번호 저장 const syncTimer = setInterval(() => { if (videoCount === videoCodes.length) { document.querySelector('#prjctList').setAttribute('data-video-codes', JSON.stringify(videoCodes)); clearInterval(syncTimer); } }, 100); }); */ /////////////////////////////////////////////////////////////////// // 이 아래 부분은 온라인 강의 다운로드 버튼을 생성하는 부분이며 업데이트 문제로 놔뒀다가 나중에 없앨 예정 /////////////////////////////////////////////////////////////////// $('#appModule > table:not(#prjctList) > tbody').append(` <tr> <td> <div style="margin-bottom: 5px">※ 앞으로 인강 다운로드 기능은 동영상을 시청하는 페이지에서만 다운로드가 가능하도록 바뀔 예정입니다. <a href="https://github.com/nbsp1221/klas-helper#%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8" target="_blank" style="color: blue;">여기</a>를 참고하여 스크립트를 업데이트해주세요.</div> <button type="button" class="btn2 btn-learn btn-lecture-down">인강 다운로드 버튼 생성</button> </td> </tr> `); $('.btn-lecture-down').click(() => { $('.btn-lecture-down').hide(); const videoCodes = []; let videoCount = 0; const watchValue = appModule.$data.list; for (let i = 0; i < watchValue.length; i++) { videoCount += watchValue[i].hasOwnProperty('starting'); } for (let i = 0; i < watchValue.length; i++) { const videoInfo = watchValue[i]; let videoCode = ''; if (!videoInfo.hasOwnProperty('starting')) { continue; } // 예외인 고유 번호는 직접 파싱해서 처리 if (videoInfo.starting === null || videoInfo.starting === 'default.htm') { const postData = []; for (const key in videoInfo) postData.push(`${key}=${videoInfo[key]}`); axios.post('/spv/lis/lctre/viewer/LctreCntntsViewSpvPage.do', postData.join('&')).then(function (response) { if (response.data.indexOf('kwcommons.kw.ac.kr/em/') === -1) { videoCode = undefined; } else { videoCode = response.data.split('kwcommons.kw.ac.kr/em/')[1].split('"')[0]; } }); } else { videoCode = videoInfo.starting.split('/'); videoCode = videoCode[videoCode.length - 1]; } const syncTimer = setInterval(() => { if (videoCode === undefined) { videoCount--; clearInterval(syncTimer); } else if (videoCode !== '') { videoCodes.push({ index: i, videoCode }); clearInterval(syncTimer); } }, 100); } // table 태그에 고유 번호 저장 const syncTimer = setInterval(() => { if (videoCount === videoCodes.length) { document.querySelector('#prjctList').setAttribute('data-video-codes', JSON.stringify(videoCodes)); clearInterval(syncTimer); } }, 100); }); // 표 디자인 수정 document.querySelector('#prjctList > colgroup > col:nth-of-type(6)').setAttribute('width', '5%'); document.querySelector('#prjctList > colgroup > col:nth-of-type(7)').setAttribute('width', '15%'); }, // 온라인 강의 화면 '/spv/lis/lctre/viewer/LctreCntntsViewSpvPage.do': () => { // 온라인 강의 고유 번호 파싱 const videoURL = chkOpen.toString().split('https://kwcommons.kw.ac.kr/em/')[1].split('"')[0]; document.body.setAttribute('data-video-code', videoURL); } };