您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Create a To-Do List for Blackboard that shows upcoming assignments, grades, submitted status
// ==UserScript== // @name Blackboard To-Do List // @namespace http://yourwebsite.com // @version 1.1 // @license MIT // @description Create a To-Do List for Blackboard that shows upcoming assignments, grades, submitted status // @author Your Name // @match https://ccccblackboard.blackboard.com/ultra/course* // @grant none // ==/UserScript== console.log('%cLoaded Brad\'s To Do List!', 'color: yellow; font-weight: bold;font-size:400%;'); function createMenu() { const toDoList = document.getElementById('toDoList'); if (!toDoList) { const courseColumnsCurrent = document.getElementById('course-columns-current'); courseColumnsCurrent.style.width = '75%'; // Create a new 'toDoList' div const newToDoList = document.createElement('div'); newToDoList.id = 'toDoList'; courseColumnsCurrent.insertBefore(newToDoList, courseColumnsCurrent.firstChild); } const tdl = document.getElementById('toDoList'); tdl.style.cssText = 'position:absolute;left:79%;top:20%;background:white;border-radius:4px;border:1px solid #cdcdcd;width:4%;height:70%;padding: 5px;overflow-y:scroll;overflow-x:hidden;'; } async function fetchAssignments() { const startSearchDate = new Date().toISOString(); const endSearchDate = new Date(); endSearchDate.setDate(endSearchDate.getDate() + 7); const endSearchDateISO = endSearchDate.toISOString(); const response = await fetch(`https://ccccblackboard.blackboard.com/learn/api/v1/calendars/calendarItems?since=${startSearchDate}&until=${endSearchDateISO}`); const json = await response.json(); return json.results.map(e => ({ class: { id: e.calendarId, name: e.calendarNameLocalizable.rawValue, link: `https://ccccblackboard.blackboard.com/ultra/courses/${e.calendarId}/cl/outline` }, assignmentName: e.title, assignmentId: e.itemSourceId, assignmentLink: `https://ccccblackboard.blackboard.com/ultra/courses/${e.calendarId}/cl/outline?legacyUrl=~2Fwebapps~2Fcalendar~2Flaunch~2Fattempt~2F_blackboard.platform.gradebook2.GradableItem-${e.itemSourceId}`, assignmnetCategoryId: e.dynamicCalendarItemProps.categoryId, dueDate: new Date(e.endDate), assignedDate: new Date(e.startDate) })); } async function renderToDoList(assignments) { const tdl = document.getElementById('toDoList'); tdl.innerHTML = `<h1 style='font-family: Open Sans,sans-serif;text-align:center;font-weight:600;'>To Do List</h1>`; tdl.innerHTML += "<img id='reloadTDL' style='cursor:pointer;position:absolute;top:3%;' src='https://learn.content.blackboardcdn.com/3900.74.0-rel.32+80961aa/images/ci/ng/small_refresh.gif'/>" if(localStorage.completedAssignments?.length==0) { localStorage.completedAssignments=JSON.stringify([]); } tdl.innerHTML+=`<h4>Complete Now</h4>` let addedTomorrowSpacer = false; for(const assignment of assignments) { const className = assignment.class.name.split("_")[0]; const assName = assignment.assignmentName; const linkToClass = assignment.assignmentLink; let checked = localStorage.completedAssignments.includes(assName) const dueTommorrow = (assignment.dueDate - new Date()) / 1000 / 60 / 60 > 24; const grade = await getAssignmentGrade(assignment.class.id, assignment.assignmentId); const submitted = grade.toString().includes("Submitted"); if(!checked && submitted) { checked = true; } if (!addedTomorrowSpacer && dueTommorrow) { const tomorrowSpacer = document.createElement('h4'); tomorrowSpacer.textContent = 'Start Today, Complete Tomorrow'; tdl.appendChild(tomorrowSpacer); addedTomorrowSpacer = true; } const assignmentBox = document.createElement('div'); assignmentBox.classList.add('assignmentBox'); assignmentBox.style.outline = '1px solid #cdcdcd'; assignmentBox.style.width='98%' assignmentBox.style.margin = '8px'; assignmentBox.style.borderRadius = '3px'; assignmentBox.style.padding = '3px'; assignmentBox.style.background="rgb(250,250,250)"; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = assName; checkbox.value = 'test'; assignmentBox.appendChild(checkbox); const label = document.createElement('label'); label.htmlFor = assName; label.style.maxWidth='90%' const anchor = document.createElement('a'); anchor.href = linkToClass; anchor.textContent = assName; label.appendChild(anchor); assignmentBox.appendChild(label); const detailsSpan = document.createElement('span'); detailsSpan.style.color = 'gray'; detailsSpan.style.fontSize = '90%'; detailsSpan.innerHTML = `<br>Due ${formatDateTime(assignment.dueDate)} | ${className} ${grade}`; assignmentBox.appendChild(detailsSpan); tdl.appendChild(assignmentBox); checkbox.checked = checked; checkbox.oninput = () => { let completed = JSON.parse(localStorage.completedAssignments); if (checkbox.checked) { completed.push(assName) } else { completed.splice(completed.indexOf(assName), 1) } localStorage.completedAssignments = JSON.stringify(completed); } document.getElementById('reloadTDL').onclick = ()=>{ main(); } } } function formatDateTime(date) { const now = new Date(); const tomorrow = new Date(now); tomorrow.setDate(tomorrow.getDate() + 1); const hours = date.getHours()%12; const minutes = date.getMinutes(); let formattedDate; if (date.toDateString() === now.toDateString()) { formattedDate = ""; } else if (date.toDateString() === tomorrow.toDateString()) { formattedDate = "tmrw"; } else { const options = { month: "long", day: "numeric" }; formattedDate = date.toLocaleDateString(undefined, options); } const formattedTime = `${hours < 10 ? '0' : ''}${hours}:${minutes < 10 ? '0' : ''}${minutes}`; const ampm = date.getHours() >= 12 ? 'PM' : 'AM'; return `${formattedDate} at <span style='font-weight:600;'>${formattedTime} ${ampm}</span> ${calculateTimeDifference(now,date)}`; } function calculateTimeDifference(startDate, endDate) { const timeDifferenceMs = endDate - startDate; const hoursDifference = Math.floor(timeDifferenceMs / (1000 * 60 * 60)); const minutesDifference = Math.floor((timeDifferenceMs / 60000) % 60); return hoursDifference >= 8 ? '' : `(${hoursDifference || minutesDifference} ${hoursDifference ? 'hrs' : 'min'})`; } const badgeCss = 'position:absolute;right:2%;display: inline-block;width:74px;text-align:center;border-radius:5px;padding:2px;font-size:90%;' async function getAssignmentGrade(classId, assId) { let ungraded = `<span style='${badgeCss}background:#d1d1d1;color:#555;'>Incomplete</span>`; let submittedText = `<span style='${badgeCss}background:#95deb0;color:black;'>Submitted</span>`; try { const response = await fetch(`https://ccccblackboard.blackboard.com/learn/api/v1/courses/${classId}/gradebook/grades?limit=100&userId=${window.__initialContext.user.id}`); const content = await response.json(); const result = content.results.find(a => a.columnId === assId); return result ? doColorGrade(result.displayGrade.score) : ungraded; } catch (e) { try{ const response = await fetch(`https://ccccblackboard.blackboard.com/webapps/calendar/launch/attempt/_blackboard.platform.gradebook2.GradableItem-${assId}`); const content = await response.text(); const submitted = content.includes("Review Submission History"); return submitted ? submittedText : ungraded; } catch(a) { return ungraded; } } } function doColorGrade(grade) { let color = "#ff3b30"; if(grade>=90) color="#39e379"; else if(grade>=80) color="#cdee4b"; else if(grade>=70) color='#ffe300'; else if(grade>=60) color='#ff9600'; return `<span style='${badgeCss}background:${color};color:#000;'>${grade.toFixed(2)+"%"}</span>`; } let loadedMenu = false; setInterval(()=>{ if(document.getElementsByClassName('element-details summary').length<1 || location.href!="https://ccccblackboard.blackboard.com/ultra/course") { loadedMenu = false; return; } if(location.href=="https://ccccblackboard.blackboard.com/ultra/course" && !loadedMenu) { loadedMenu = true; main(); } },100) async function main() { createMenu(); const assignments = await fetchAssignments(); const sortedAssignments = assignments.filter(assignment => { const hours = (assignment.dueDate - new Date()) / 1000 / 60 / 60; return hours < 48 && hours > 0; }).sort((a, b) => (a.dueDate - b.dueDate) / 1000 / 60 / 60); renderToDoList(sortedAssignments); }