// ==UserScript==
// @name yuntech tronclass util
// @namespace no
// @version 0.3.4
// @description 一鍵觀看影片,一鍵完成每周影音教材觀看,一鍵觀看投影片
// @author someone
// @match https://eclass.yuntech.edu.tw/course/*
// @icon https://cdn.discordapp.com/avatars/755351137577599016/5ccfb5d525fb3d304c7d61c8d51c6777.png?size=1024
// @grant none
// @license MIT
// ==/UserScript==
;(function () {
'use strict'
let tglobal = {
process: 0,
processmax: 0,
persent: 0,
videoispress: false,
courseispress: false,
closeonfinish: false
}
function contains(selector, text) {
var elements = document.querySelectorAll(selector)
return Array.prototype.filter.call(elements, function (element) {
return RegExp(text).test(element.textContent)
})
}
function tempAlert(msg, duration) {
var el = document.createElement('div')
el.innerHTML = `<div class="lol_alert alert-box success radius" data-alert>
${msg}
</div>
<style>
.lol_alert {
position: absolute;
top: 40%;
left: 20%;
z-index: 99;
}
</style>`
setTimeout(function () {
el.parentNode.removeChild(el)
}, duration)
document.body.appendChild(el)
}
function updateprocessbar() {
try {
let processbar = document.getElementById('watch-process')
let persent = (tglobal.process / tglobal.processmax) * 100
processbar.style = `width: ${persent}%`
} catch (e) {}
}
function finishprocessbar() {
try {
let processbar = document.getElementById('watch-process-div')
processbar.parentNode.removeChild(processbar)
} catch (e) {}
}
function extractVideoId(url) {
try {
const parsedUrl = new URL(url)
const videoId = parsedUrl.searchParams.get('v') || parsedUrl.pathname.split('/').pop()
return videoId
} catch (error) {
console.error('獲取影片ID出錯:', error)
return null
}
}
async function get_youtube_length() {
const iframe = document.querySelector('iframe[src*="youtube.com"]')
if (!iframe) {
reject('No YouTube iframe')
return
}
const videoId = extractVideoId(iframe.src)
if (!videoId) {
reject('無法獲取影片ID')
return
}
console.log('找到影片ID:', videoId)
const apifetch = await fetch(
'https://youtube_videotime_worker.phillychi3.workers.dev/api/video?url=' + videoId,
{
mode: 'no-cors'
}
)
if (!apifetch.ok) {
console.error('API錯誤:', apifetch.status)
reject('API錯誤')
} else {
const apidata = await apifetch.json()
return apidata.length
}
}
async function circle_watch(fast = 1000) {
const video = document.querySelector('video')
//*[@id="player"]
let max = 10000
if (document.getElementById('player')) {
max = await get_youtube_length()
} else {
max = video.duration
}
if (!video) {
setTimeout(() => {
circle_watch(fast)
}, 1000)
console.error('video not found')
return
}
const maxrun = 60
let lasttime = 0
const thisvideoid = document.URL.split('/').splice(-1).toString()
tglobal.processmax = max
fetch(`https://eclass.yuntech.edu.tw/api/activities/${thisvideoid}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
cookie: document.cookie
})
.then((response) => response.json())
.then((data) => {
let ct = 0
for (let i = 0; i < max; i = i + maxrun) {
ct++
setTimeout(() => {
watchthevideo(i, i + maxrun, data)
tglobal.process = i + maxrun
updateprocessbar()
if (max - i < maxrun) {
console.log('last')
watchthevideo(i, max, data)
tglobal.process = max
updateprocessbar()
finishprocessbar()
tglobal.videoispress = false
tempAlert('aleardy watch the video', 2000)
if (tglobal.closeonfinish) {
window.close()
}
}
}, fast * ct)
lasttime = i + maxrun
}
})
}
function watchthevideo(start, end, videodata) {
let student = globalData.user
let course = globalData.course
let dep = globalData.dept
fetch(`https://eclass.yuntech.edu.tw/api/course/activities-read/${videodata.id}`, {
method: 'POST',
headers: {
Origin: 'https://eclass.yuntech.edu.tw',
Referer: `https://eclass.yuntech.edu.tw/course/${course.id}/learning-activity/full-screen`,
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify({
start: start,
end: end
}),
cookie: document.cookie
}).then((response) => response.json())
fetch('https://eclass.yuntech.edu.tw/statistics/api/online-videos', {
method: 'POST',
headers: {
Connection: 'keep-alive',
Origin: 'https://eclass.yuntech.edu.tw',
Referer: `https://eclass.yuntech.edu.tw/course/${course.id}/learning-activity/full-screen`,
'Content-Type': 'Typetext/plain;charset=UTF-8'
},
cookie: document.cookie,
body: JSON.stringify({
user_id: student.id,
org_id: 1,
course_id: course.id,
module_id: videodata.moduls_id,
syllabus_id: videodata.syllabus_id,
activity_id: videodata.id,
upload_id: videodata.uploads[0].id,
reply_id: null,
comment_id: null,
forum_type: '',
action_type: 'play',
is_teacher: false,
is_student: true,
ts: Date.now(),
user_agent: 'Mozilla/5.0 (X11; Linux x86_64; rv:120.0) Gecko/20100101 Firefox/120.0',
meeting_type: 'online_video',
start_at: start,
end_at: end,
duration: end - start,
master_course_id: 0,
org_name: student.orgName,
user_no: student.userNo,
user_name: student.name,
course_code: course.courseCode,
course_name: course.name,
dep_id: dep.id,
dep_name: dep.name,
dep_code: dep.code
})
}).then((response) => {
if (response.ok) {
console.log('success')
} else {
console.log(response.status)
}
})
}
function watchthefile() {
const activity_id = document.URL.split('/').splice(-1).toString()
fetch(`https://eclass.yuntech.edu.tw/api/activities/${activity_id}?sub_course_id=0`, {
headers: {
Origin: 'https://eclass.yuntech.edu.tw',
Referer: `https://eclass.yuntech.edu.tw/course/${course.id}/learning-activity/full-screen`,
'Content-Type': 'application/json;charset=UTF-8'
},
cookie: document.cookie
})
.then((response) => response.json())
.then((data) => {
tglobal.processmax = data.uploads.length
tglobal.process = 0
data.uploads.forEach((element) => {
fetch(`https://eclass.yuntech.edu.tw/api/course/activities-read/${data.id}`, {
method: 'POST',
headers: {
Origin: 'https://eclass.yuntech.edu.tw',
Referer: `https://eclass.yuntech.edu.tw/course/${course.id}/learning-activity/full-screen`,
'Content-Type': 'application/json;charset=UTF-8'
},
cookie: document.cookie,
body: JSON.stringify({
upload_id: element.reference_id
})
})
// fetch(
// `https://eclass.yuntech.edu.tw/api/uploads/reference/document/${element.reference_id}/url?preview=true&refer_id=${data.id}}&refer_type=learning_activity`,
// {
// headers: {
// Origin: "https://eclass.yuntech.edu.tw",
// Referer: `https://eclass.yuntech.edu.tw/course/${course.id}/learning-activity/full-screen`,
// "Content-Type": "application/json;charset=UTF-8",
// },
// cookie: document.cookie,
// }
// )
.then((response) => response.json())
tglobal.process = tglobal.process + 1
updateprocessbar()
finishprocessbar()
})
})
updateprocessbar()
finishprocessbar()
tglobal.videoispress = false
}
// 影片頁按鈕
function makevideopanel() {
let panel = document.createElement('div')
panel.style = 'padding: 20px;margin-top: -40px;'
panel.innerHTML = `
<div class="panel" id="eclassutilpanel">
<div class="panel-heading">
<h4>tronclass util</h4>
</div>
<div class="panel-body">
<div class="panel-buttons" id="buttons">
<button class="btn btn-default glow-button" id="btn1">Watch Video</button>
<button class="btn btn-default glow-button" id="btn2">Watch Video Fast(useless)</button>
</div>
</div>
</div>
<style>
.panel {
margin-bottom: 23px;
background: rgba(255, 255, 255, 0.9);
}
.panel-heading {
padding: 0px;
}
.glow-button {
position: relative;
background: #428bca;
color: white;
border: none;
box-shadow: 0 0 10px #428bca;
animation: permanentGlow 2s ease-in-out infinite;
}
.glow-button:hover {
transform: translateY(-2px);
animation: permanentGlowHover 2s ease-in-out infinite;
background: #3071a9;
color: white;
}
</style>
`
document.querySelector('div.fullscreen-right').appendChild(panel)
let btn1 = document.getElementById('btn1')
btn1.addEventListener('click', function () {
if (!tglobal.videoispress) {
let processbar = document.createElement('div')
processbar.innerHTML = `
<div class="panel-progress" id="watch-process-div">
<div class="progress-meter" id="watch-process" style="width: ${tglobal.persent}%"></div>
</div>
<style>
.panel-progress {
margin-top: 20px;
padding: 6px;
border-radius: 30px;
background: rgba(0, 0, 0, 0.25);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.25),
0 1px rgba(255, 255, 255, 0.08);
}
.progress-meter {
animation: progressAnimation 6s;
background-color: #ef416f;
height: 10px;
border-radius: 30px;
transition: 0.4s ease-out;
}
`
document.querySelector('div.panel-body').appendChild(processbar)
tglobal.videoispress = true
circle_watch()
}
})
let btn2 = document.getElementById('btn2')
btn2.addEventListener('click', function () {
circle_watch(10)
})
let hash = window.location.hash.substring(1)
let autowatch = false
if (hash.includes('?')) {
let hashParams = new URLSearchParams(hash.split('?')[1])
autowatch = hashParams.get('autowatch')
}
if (autowatch === 'true') {
tglobal.closeonfinish = true
circle_watch()
}
}
function makefilepanel() {
let panel = document.createElement('div')
panel.style = 'padding: 20px;margin-top: -40px;'
panel.innerHTML = `
<div class="panel" id="eclassutilpanel">
<div class="panel-heading">
<h4>tronclass util</h4>
</div>
<div class="panel-body">
<div class="panel-buttons" id="buttons">
<button class="btn btn-default" id="btn1">Read All Files</button>
</div>
</div>
</div>
<style>
.panel {
margin-bottom: 23px;
background: rgba(255, 255, 255, 0.9);
}
.panel-heading {
padding: 0px;
}
</style>
`
document.querySelector('div.fullscreen-right').appendChild(panel)
let btn1 = document.getElementById('btn1')
btn1.addEventListener('click', function () {
if (!tglobal.videoispress) {
let processbar = document.createElement('div')
processbar.innerHTML = `
<div class="panel-progress" id="watch-process-div">
<div class="progress-meter" id="watch-process" style="width: ${tglobal.persent}%"></div>
</div>
<style>
.panel-progress {
margin-top: 20px;
padding: 6px;
border-radius: 30px;
background: rgba(0, 0, 0, 0.25);
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.25),
0 1px rgba(255, 255, 255, 0.08);
}
.progress-meter {
animation: progressAnimation 6s;
background-color: #ef416f;
height: 10px;
border-radius: 30px;
transition: 0.4s ease-out;
}
`
document.querySelector('div.panel-body').appendChild(processbar)
tglobal.videoispress = true
watchthefile()
}
})
let hash = window.location.hash.substring(1)
let autowatch = false
if (hash.includes('?')) {
let hashParams = new URLSearchParams(hash.split('?')[1])
autowatch = hashParams.get('autowatch')
}
if (autowatch === 'true') {
tglobal.closeonfinish = true
watchthefile()
}
}
// 首頁按鈕
function makecoursepanel() {
let panel = document.createElement('div')
panel.innerHTML = `
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a class="collapsed" data-toggle="collapse" data-parent="#accordion" aria-expanded="false">tronclass util</a>
</h4>
</div>
<div class="buttons" id="buttons">
<button class="btn btn-default" id="btn1">watch all video(not finish</button>
<button class="btn btn-default" id="btn2">wait...</button>
</div>
</div>
<style>
.panel {
margin-bottom: 23px;
}
.panel-heading {
padding: 0px;
}
.panel-title {
padding: 10px;
}
`
let target = document.querySelector('.collapse')
target.parentNode.insertBefore(panel, target)
let btn1 = document.getElementById('btn1')
let btn2 = document.getElementById('btn2')
btn1.addEventListener('click', function () {
console.log('click1')
})
btn2.addEventListener('click', function () {
console.log('click2')
})
}
// 觀看這周 按鈕
function makeweekvideopanel() {
let syllabus = document.getElementsByClassName('syllabus-list')
Array.from(syllabus).forEach((element) => {
let titleElement = element.querySelector('.title.ng-binding')
if (titleElement && titleElement.innerText == '影音教材') {
let activities = element.parentElement.getElementsByClassName('learning-activity')
let activityIds = Array.from(activities)
.map((activity) => {
let match = activity.id.match(/learning-activity-(\d+)/)
return match ? match[1] : null
})
.filter((id) => id !== null)
let button = document.createElement('button')
button.className = 'button-green'
button.innerText = '觀看這周'
button.style.marginRight = '10px'
button.addEventListener('click', (event) => {
event.stopPropagation()
if (tglobal.courseispress) {
return
}
tglobal.courseispress = true
let container = document.createElement('div')
container.style.display = 'flex'
container.style.alignItems = 'center'
container.style.gap = '10px'
button.parentNode.insertBefore(container, button)
container.appendChild(button)
let processbar = document.createElement('div')
processbar.className = 'loader'
processbar.style.display = 'none'
processbar.innerHTML = `
<style>
.loader {
width: 30px;
height: 30px;
border: 5px solid #0000;
box-sizing: border-box;
background:
radial-gradient(farthest-side,#000 98%,#0000) 0 0/5px 5px,
radial-gradient(farthest-side,#000 98%,#0000) 100% 0/5px 5px,
radial-gradient(farthest-side,#000 98%,#0000) 100% 100%/5px 5px,
radial-gradient(farthest-side,#000 98%,#0000) 0 100%/5px 5px,
linear-gradient(#000 0 0) 50%/10px 10px,
#fff;
background-repeat: no-repeat;
filter: blur(2px) contrast(10);
animation: l12 0.8s infinite;
}
@keyframes l12 {
100% {background-position:100% 0,100% 100%,0 100%,0 0,center}
}
`
container.appendChild(processbar)
processbar.style.display = 'block'
alert(
'自動觀看即將執行 請勿觸碰頁面,第一次使用請手動同意跳出過多窗口(瀏覽器右上角會有警示,並且重新執行自動觀看),如有頁面長時間並無自動關閉請重新整理並手動按下觀看按鈕'
)
activityIds.forEach((id, index) => {
setTimeout(() => {
window.open(
`https://eclass.yuntech.edu.tw/course/${globalData.course.id}/learning-activity/full-screen#/${id}?autowatch=true`
)
console.log(id)
}, index * 6000)
})
setTimeout(() => {
tglobal.courseispress = false
processbar.style.display = 'none'
}, activityIds.length * 6000)
})
titleElement.parentNode.appendChild(button)
}
})
}
var observer = new MutationObserver(resetTimer)
var timer = setTimeout(action, 1000, observer)
observer.observe(document, { childList: true, subtree: true })
function resetTimer(changes, observer) {
clearTimeout(timer)
timer = setTimeout(action, 1000, observer)
}
function modifyLearningActivities() {
const learningActivities = document.querySelectorAll('.learning-activity')
learningActivities.forEach((activity) => {
const activityId = activity.id.replace('learning-activity-', '')
const clickableArea = activity.querySelector('.clickable-area')
if (clickableArea) {
const newClickableArea = clickableArea.cloneNode(true)
clickableArea.parentNode.replaceChild(newClickableArea, clickableArea)
const courseId = window.location.pathname.split('/')[2]
newClickableArea.addEventListener('click', function (e) {
e.preventDefault()
e.stopPropagation()
window.open(
`https://eclass.yuntech.edu.tw/course/${courseId}/learning-activity#/${activityId}`,
'_blank'
)
})
newClickableArea.style.cursor = 'pointer'
}
})
}
function waitForElement(selector, text, maxAttempts = 5) {
return new Promise((resolve) => {
let attempts = 0
const checkElement = () => {
const elements = contains(selector, text)
if (elements.length > 0) {
resolve(true)
} else if (attempts < maxAttempts) {
attempts++
setTimeout(checkElement, 100)
} else {
resolve(false)
}
}
checkElement()
})
}
async function action(observer) {
observer.disconnect()
if (document.URL.match(/https?:\/\/eclass.yuntech.edu.tw\/course\/[0-9]{1,6}\/content#\//)) {
makecoursepanel()
makeweekvideopanel()
modifyLearningActivities()
} else if (
document.URL.match(
/https?:\/\/eclass.yuntech.edu.tw\/course\/[0-9]{1,6}\/learning-activity\/full-screen/
)
) {
const hasWatchRequirement = await waitForElement('span', '需累積觀看')
if (hasWatchRequirement) {
makevideopanel()
}
const hasDownloadOption = await waitForElement('span', '觀看或下載')
if (hasDownloadOption) {
makefilepanel()
}
}
}
console.log(
'%c eclass Util %c https://github.com/phillychi3/loltronclass ',
'color: white; background: #e9546b; padding:5px 0;',
'padding:4px;border:1px solid #e9546b;'
)
})()