// ==UserScript==
// @name 雲科一鍵填寫工作日誌
// @namespace Anong0u0
// @version 1.3.1
// @description 😡垃圾學校不讓人輕鬆填寫
// @author Anong0u0
// @match https://webapp.yuntech.edu.tw/workstudy/StudWorkRecord/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=www.yuntech.edu.tw
// @grant GM_getValue
// @grant GM_setValue
// @license Beerware
// ==/UserScript==
const getWorkContent = async (planName) =>
{
const t = GM_getValue(planName)
if(t) return t
const workListHTML = document.createElement("div"),
workInfoHTML = document.createElement("div"),
workInfo = {}
workListHTML.innerHTML = await fetch(`/workstudy/StudWorkRecord/ContractList?date=${new Date(new Date().getFullYear(), new Date().getMonth(), 1).toLocaleDateString("ja").replace(/\//g,"%2F")}`).then((e)=>e.text())
workInfoHTML.innerHTML = await fetch("/workstudy/Stud/ContractList").then((e)=>e.text());
for(const e of [...workInfoHTML.querySelectorAll("tbody > tr")].map((tr)=>[...tr.querySelectorAll("td")].map((e)=>e?.innerText.trim() || e.querySelector("a")?.href.match(/(?<=ApplyId=)\d+/))))
{
const text = await fetch(`/workstudy/Stud/JobContractInfo?ApplyId=${e[10]}`).then((e)=>e.text()),
desc = String(text.match(/(?<=工作內容:\r\n).+/)).trim()
workInfo[e[2]] = desc
}
for(const tr of workListHTML.querySelectorAll("tbody > tr"))
{
const workList = [...tr.querySelectorAll("td")].map((e)=>e.innerText.trim() || e.querySelector("a").href.match(/(?<=ContractId=)\d+/))
const pn = workList[2],
workID = workList[8]
GM_setValue(pn, {id:workID, desc:workInfo[pn]})
}
return GM_getValue(planName)
}
(() =>
{
const btn = document.createElement("input"), YM = new Date().toLocaleDateString("en-za").slice(0,-3)
btn.type = "button"
btn.value = `😎一鍵填寫 ${YM} 工作日誌`
btn.className = "btn btn-success"
document.querySelector(".panel-footer").append(btn)
btn.onclick = async () =>
{
btn.value = "🔄載入資料中..."
btn.disabled = true
const monthStart = new Date(new Date().getFullYear(), new Date().getMonth(), 1);
const monthEnd = new Date(new Date().getFullYear(), new Date().getMonth()+1, 1);
let allWork = (await fetch("/workstudy/Stud/CalendarData").then((e)=>e.json())).filter((e)=>(!e.backgroundColor || e.title.indexOf("月保:")===0) && new Date(e.start)>monthStart && new Date(e.start)<monthEnd)
if (allWork?.[0]?.title.indexOf("月保:")===0)
{
const title = allWork[0].title.replace("月保:", ""),
year = new Date().getFullYear(),
month = new Date().getMonth()
allWork = Array(new Date(new Date().getFullYear(), month + 1, 0).getDate()-1)
.fill().map((_, i) => new Date(year, month, i + 2, 8))
.filter(day => day.getDay() % 6 !== 0).map((day)=>{return {title: title, start: day.toISOString(), end:new Date(year, month, day.getDate(), 17).toISOString()}});
}
let tableText = ""
for(const work of allWork)
{
const start = Number(new Date(work.start)),
end = Number(new Date(work.end)),
planName = work.title.split("by")[0]
let remain = (end-start)/3600000,
offset = 0
while(remain>0)
{
const t = Math.min(remain, 4)
const s = new Date(start+offset*3600000-Math.floor((Math.random()*5))*60000).toLocaleTimeString("sv").slice(0,-3),
e = new Date(start+(t+offset)*3600000+Math.floor((Math.random()*5))*60000).toLocaleTimeString("sv").slice(0,-3)
remain -= t
offset += t
if(t==4)
{
offset++
remain--
}
tableText += `
<tr>
<td>${planName}</td>
<td>${new Date(start).toLocaleDateString("en-za")}</td>
<td><input class="form-control time start" value="${s}"></td>
<td><input class="form-control time end" value="${e}"></td>
<td class="hour">${t}</td>
<td><input class="form-control workDesc" value="${(await getWorkContent(planName))?.desc}"></td>
</tr>`
}
}
const form = document.createElement("div")
form.innerHTML = `
<style>td>input {width:100% !important}</style>
<div class="dataTables_wrapper form-inline dt-bootstrap no-footer" style="padding-bottom:16px">
<h4><b>${YM}</b> 預計填寫內容💌 (${new Date(Math.max(Date.now()-604800000, monthStart)).toLocaleDateString("en-za").slice(5)}-${new Date().toLocaleDateString("en-za").slice(5)}以外時間可能無法正常填寫)</h4>
<table class="table table-striped table-bordered table-hover dataTable no-footer dtr-inline">
<thead>
<tr>
<th>計畫</th>
<th>工作日期</th>
<th>開始時間</th>
<th>結束時間</th>
<th>時數</th>
<th>工作內容</th>
</tr>
</thead>
<tbody>
${tableText}
</tbody>
</table>
</div>`
form.querySelectorAll("tbody > tr").forEach((tr)=>
{
const start = tr.querySelector(".start"),
end = tr.querySelector(".end"),
hour = tr.querySelector(".hour")
start.oninput = () =>
{
hour.innerText = Math.floor((new Date(`2000/1/1 ${end.value}`)-new Date(`2000/1/1 ${start.value}`))/3600000) || "錯誤"
}
end.oninput = start.oninput
})
document.querySelector(":is(form[action='/workstudy/StudWorkRecord/ApplyAction'],#page-wrapper > .row:last-child)").insertAdjacentElement("afterend", form)
btn.value = "🔽等待確認中..."
const cancel = document.createElement("input"),
submit = document.createElement("input"),
change = document.createElement("input"),
del = document.createElement("input")
cancel.className = "btn btn-danger"
cancel.type = "button"
cancel.value = "🤔取消"
cancel.style = "margin: 0 4px;"
submit.className = "btn btn-success"
submit.type = "button"
submit.value = "🤗確認送出"
change.style = "margin: 0 4px;"
change.className = "btn btn-info"
change.type = "button"
change.value = "🧐修改所有工作內容描述"
del.className = "btn btn-danger"
del.type = "button"
del.value = "😨刪除特定工作"
document.querySelector(".panel-footer").append(cancel)
document.querySelector(".panel-footer").append(submit)
document.querySelector(".panel-footer").append(change)
document.querySelector(".panel-footer").append(del)
del.onclick = () =>
{
const desc = prompt(`輸入需刪除的工作,可匹配所有欄位\n✅支援正規表達式\n刪除14-27號範例:${YM}/(1[4-9]|2[0-7])`, `${YM}/(1[4-9]|2[0-7])`)
if(!desc) return
let checkText = "", delArray = []
form.querySelectorAll("tbody > tr").forEach((tr)=>
{
const work = [...tr.querySelectorAll("td")].map((e)=>e.innerText || e.querySelector("input")?.value)
if(work.join("\n").match(desc))
{
checkText += `${work[0]}-${work[1]}-${work[2]}\n`
delArray.push(tr)
}
})
if(delArray.length==0) return
checkText = `檢測到${delArray.length}筆刪除資料,是否繼續刪除?\n` + checkText
if(confirm(checkText)) delArray.forEach((e)=>e.remove())
}
change.onclick = () =>
{
const desc = prompt("輸入需修改的描述", document.querySelector(".workDesc").value)
if(desc) document.querySelectorAll(".workDesc").forEach((e)=>{e.value=desc})
}
cancel.onclick = () =>
{
cancel.remove()
submit.remove()
change.remove()
del.remove()
form.remove()
btn.value = `😎一鍵填寫 ${YM} 工作日誌`
btn.disabled = false
}
submit.onclick = async () =>
{
if([...form.querySelectorAll(".time")].some((e)=>!e.value.match(/^\d{2}:\d{2}$/)) || [...form.querySelectorAll(".hour")].some((e)=>!Number(e.innerText))) if(!confirm("偵測到填寫錯誤,是否繼續送出?")) return
submit.disabled = true
cancel.remove()
change.remove()
del.remove()
btn.remove()
submit.value = "😋處理送出中..."
for(const tr of form.querySelectorAll("tbody > tr"))
{
const [planName, date, start, end, hour, desc] = [...tr.querySelectorAll("td")].map((e)=>e.innerText || e.querySelector("input")?.value)
const body = `DateContract=${date.replace(/\//g,"%2F")}%2C${(await getWorkContent(planName))?.id}&`+
`StartHour=${Number(start.split(":")[0])}&StartMin=${Number(start.split(":")[1])}&`+
`EndHour=${Number(end.split(":")[0])}&EndMin=${Number(end.split(":")[1])}&`+
`IsAnnualLeave=false&WorkContent=${encodeURIComponent(desc)}&Hours=${hour}`;
tr.querySelector("td").innerText += await fetch("https://webapp.yuntech.edu.tw/workstudy/StudWorkRecord/ApplyAction", {
"headers": {"content-type": "application/x-www-form-urlencoded",},
"body": body,
"method": "POST",
}).then((e)=>e.text()).then((e)=>e.indexOf("填寫完成")!=-1 ? "✅":` ❌${String(e.match(/(?<=×<\/button>)[^<]+/)??"未知錯誤").trim()}`);
}
submit.value = "😎填寫完成"
}
}
console.log("it's works!")
})()