您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Modern redesign, customization, transparency, hidden soccer game, and more for Daymap.
// ==UserScript== // @name Better Daymap // @namespace Better Daymap // @version 4.0.1 // @description Modern redesign, customization, transparency, hidden soccer game, and more for Daymap. // @author LiamGo // @match https://*.daymap.net/* // @icon https://lh3.googleusercontent.com/_Jt3LvQt0VV4wQkW6brDIvKNCQMSWgzbE_ofiwnWCgWTw4pUv4HsLX0AH8PpNEde85jt8XPWyXQo91d4MEYqZZgm-k4=s60 // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // --- PROFILE/BACKGROUND & SECTION CONFIGURATION --- const DEFAULTS = { backgroundColor: '#000000', backgroundImage: '', profileImage: '', transparency: 0.0, blur: 0, disabledSections: { indicators: false, diaryNotes: false, homework: false, currentTasks: false, messages: false, bulletins: false, newsletters: false, rcFactSheets: false }, hiddenGameEnabled: true, hiddenGameURL: 'https://www.twoplayergames.org/gameframe/soccer-random' }; const SECTIONS = [ { id: 'indicators', name: 'Indicators', selector: '#pnlMid > div.card.expWindow:nth-of-type(1)' }, { id: 'diaryNotes', name: 'My Diary Notes', selector: '#pnlMid > div.card.expWindow:nth-of-type(2)' }, { id: 'homework', name: 'Homework', selector: '#pnlMid > div.card.expWindow:nth-of-type(3)' }, { id: 'currentTasks', name: 'Current Tasks', selector: '#pnlMid > div.card.expWindow:nth-of-type(4)' }, { id: 'messages', name: 'Messages', selector: '#pnlRight > div.card.expWindow:nth-of-type(1)' }, { id: 'bulletins', name: 'Bulletins', selector: '#pnlRight > div.card.expWindow:nth-of-type(2)' }, { id: 'newsletters', name: 'Newsletters', selector: '#pnlRight > div.card.expWindow:nth-of-type(3)' }, { id: 'rcFactSheets', name: 'RC Fact Sheets', selector: '#pnlRight > div.card.expWindow:nth-of-type(4)' } ]; // --- TRANSPARENCY/BLUR FUNCTIONALITY --- function applyTransBlur() { const transparency = parseFloat(GM_getValue('transparency', DEFAULTS.transparency)); const blur = parseInt(GM_getValue('blur', DEFAULTS.blur), 10); let dark = 0; if(document.cookie && document.cookie.length > 0) { const lastChar = document.cookie.substr(document.cookie.length - 1, 1); dark = lastChar === "1" ? 1 : 0; } const lightBg = "237,235,233"; const darkBg = "37,37,37"; const bg = dark ? darkBg : lightBg; function setStyle(selector, bgColor, transparency, blur) { document.querySelectorAll(selector).forEach(el => { el.style.background = `rgba(${bgColor},${transparency})`; el.style.backdropFilter = `blur(${blur}px)`; }); } setStyle( ".card, .msg, .ditm, .Toolbar, .ditm .t, .ditm .c, .hasDatepicker, #tblTt tbody tr td, .item-container, #bCalendar, #btnDiary", bg, transparency, blur ); // Header let header = document.querySelector("daymap-header"); if(header) { header.style.backgroundColor = `rgba(255,255,255,${transparency * 1.2})`; header.style.backdropFilter = `blur(${blur * 3}px)`; let lis = header.querySelectorAll("div ul li"); lis.forEach(li => { li.style.backgroundColor = `rgba(255,255,255,${transparency * 0.8})`; }); } // Navigation containers setStyle( ".nav-container, .nav-user-container", bg, transparency * 0.7, blur ); } // Initial apply window.setTimeout(applyTransBlur, 0); // --- MutationObserver to reapply styles on dynamic content changes --- let applyTimeout = null; const DEBOUNCE_DELAY = 200; const observer = new MutationObserver(mutations => { if (applyTimeout) clearTimeout(applyTimeout); applyTimeout = setTimeout(() => { applyTransBlur(); }, DEBOUNCE_DELAY); }); observer.observe(document.body, { childList: true, subtree: true, attributes: false }); // --- PROFILE/BACKGROUND/SECTION FUNCTIONALITY --- function applySettings() { const bgColor = GM_getValue('backgroundColor', DEFAULTS.backgroundColor); const bgImage = GM_getValue('backgroundImage', DEFAULTS.backgroundImage); const profileImage = GM_getValue('profileImage', DEFAULTS.profileImage); const disabledSections = GM_getValue('disabledSections', DEFAULTS.disabledSections); document.body.style.backgroundColor = bgColor; if (bgImage) { document.body.style.backgroundImage = `url(${bgImage})`; document.body.style.backgroundSize = '100% 100%'; document.body.style.backgroundRepeat = 'no-repeat'; document.body.style.backgroundPosition = 'top left'; document.body.style.backgroundAttachment = 'fixed'; } else { document.body.style.backgroundImage = ''; } // --- NEW PROFILE IMAGE LOGIC --- // Clear previous interval if exists if (window.navUserImageInterval) clearInterval(window.navUserImageInterval); if (profileImage) { // Reapply .nav-user-image every 100ms window.navUserImageInterval = setInterval(() => { const navUser = document.querySelector('.nav-user-image'); if (navUser) { navUser.style.backgroundImage = `url(${profileImage})`; } }, 100); window.navUserImageTimeout = setTimeout(() => { clearInterval(window.navUserImageInterval); }, 10000); // Apply to .photoThumb only once const photoThumb = document.querySelector('.photoThumb'); if (photoThumb && !photoThumb.dataset.profileSet) { photoThumb.style.backgroundImage = `url(${profileImage})`; photoThumb.dataset.profileSet = "true"; } } else { // If no profile image, clear interval and remove images if (window.navUserImageInterval) clearInterval(window.navUserImageInterval); const navUser = document.querySelector('.nav-user-image'); if (navUser) navUser.style.backgroundImage = ''; const photoThumb = document.querySelector('.photoThumb'); if (photoThumb) photoThumb.style.backgroundImage = ''; } SECTIONS.forEach(section => { document.querySelectorAll(section.selector).forEach(el => { el.style.display = disabledSections[section.id] ? 'none' : ''; }); }); // Re-apply transparency/blur when settings change applyTransBlur(); } function expandContentHeight() { const currentTasks = document.querySelector('#pnlMid > div.card.expWindow:nth-of-type(3) .expContent'); const messages = document.querySelector('#pnlRight > div.card.expWindow:nth-of-type(1) .expContent'); if (currentTasks) currentTasks.style.minHeight = '572px'; if (messages) messages.style.minHeight = '572px'; } // --- SETTINGS PAGE --- function createSettingsPage() { const modal = document.createElement('div'); modal.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 0 20px rgba(0,0,0,0.3); z-index: 9999; min-width: 400px; max-width: 95vw; max-height: 90vh; overflow: auto; font-family: Arial, sans-serif; `; const heading = document.createElement('h2'); heading.textContent = 'Better Daymap Settings'; heading.style.marginTop = '0'; const form = document.createElement('div'); form.style.display = 'grid'; form.style.gap = '15px'; const colorLabel = createLabel('Background Color:', 'color'); const colorInput = createInput('color', 'backgroundColor', GM_getValue('backgroundColor', DEFAULTS.backgroundColor)); const bgImageLabel = createLabel('Background Image URL:', 'bgImage'); const bgImageInput = createInput('text', 'bgImage', GM_getValue('backgroundImage', DEFAULTS.backgroundImage)); bgImageInput.placeholder = 'https://example.com/image.jpg'; const profileLabel = createLabel('Profile Image URL:', 'profile'); const profileInput = createInput('text', 'profile', GM_getValue('profileImage', DEFAULTS.profileImage)); profileInput.placeholder = 'https://example.com/avatar.jpg'; // --- TRANSPARENCY/BLUR CONTROLS --- const transparencyLabel = createLabel('Transparency:', 'transparency'); const transparencyValue = document.createElement('span'); transparencyValue.style.marginLeft = '10px'; transparencyValue.style.fontWeight = 'bold'; const transparencyInput = document.createElement('input'); transparencyInput.type = 'range'; transparencyInput.id = 'transparency'; transparencyInput.min = 0; transparencyInput.max = 1; transparencyInput.step = 0.01; transparencyInput.value = GM_getValue('transparency', DEFAULTS.transparency); transparencyValue.textContent = transparencyInput.value; transparencyInput.addEventListener('input', () => { transparencyValue.textContent = transparencyInput.value; }); const blurLabel = createLabel('Blur (px):', 'blur'); const blurValue = document.createElement('span'); blurValue.style.marginLeft = '10px'; blurValue.style.fontWeight = 'bold'; const blurInput = document.createElement('input'); blurInput.type = 'range'; blurInput.id = 'blur'; blurInput.min = 0; blurInput.max = 20; blurInput.step = 1; blurInput.value = GM_getValue('blur', DEFAULTS.blur); blurValue.textContent = blurInput.value; blurInput.addEventListener('input', () => { blurValue.textContent = blurInput.value; }); // Section toggles const sectionLabel = document.createElement('div'); sectionLabel.textContent = 'Disable Sections:'; sectionLabel.style.fontWeight = 'bold'; sectionLabel.style.marginTop = '15px'; const togglesContainer = document.createElement('div'); togglesContainer.style.display = 'flex'; togglesContainer.style.gap = '20px'; const leftColumn = document.createElement('div'); leftColumn.style.display = 'flex'; leftColumn.style.flexDirection = 'column'; leftColumn.style.gap = '8px'; const rightColumn = document.createElement('div'); rightColumn.style.display = 'flex'; rightColumn.style.flexDirection = 'column'; rightColumn.style.gap = '8px'; const disabledSections = GM_getValue('disabledSections', DEFAULTS.disabledSections); SECTIONS.forEach((section, index) => { const wrapper = document.createElement('div'); wrapper.style.display = 'flex'; wrapper.style.alignItems = 'center'; wrapper.style.gap = '8px'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `section-${section.id}`; checkbox.checked = disabledSections[section.id]; const label = document.createElement('label'); label.htmlFor = `section-${section.id}`; label.textContent = section.name; label.style.cursor = 'pointer'; wrapper.appendChild(checkbox); wrapper.appendChild(label); if (index < 4) leftColumn.appendChild(wrapper); else rightColumn.appendChild(wrapper); }); togglesContainer.appendChild(leftColumn); togglesContainer.appendChild(rightColumn); // --- HIDDEN GAME TOGGLE & URL --- const hiddenGameEnabled = GM_getValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled); const hiddenGameURL = GM_getValue('hiddenGameURL', DEFAULTS.hiddenGameURL); const gameToggleLabel = createLabel('Show Hidden Game Button:', 'hiddenGameEnabled'); const gameToggleInput = document.createElement('input'); gameToggleInput.type = 'checkbox'; gameToggleInput.id = 'hiddenGameEnabled'; gameToggleInput.checked = hiddenGameEnabled; const gameUrlLabel = createLabel('Hidden Game URL:', 'hiddenGameURL'); const gameUrlInput = createInput('text', 'hiddenGameURL', hiddenGameURL); gameUrlInput.placeholder = 'https://example.com/game'; // --- BUTTONS --- const buttonContainer = document.createElement('div'); buttonContainer.style.display = 'flex'; buttonContainer.style.gap = '10px'; buttonContainer.style.marginTop = '15px'; const saveButton = createButton('Save', () => { const newDisabled = {}; SECTIONS.forEach(section => { newDisabled[section.id] = document.getElementById(`section-${section.id}`).checked; }); GM_setValue('backgroundColor', colorInput.value); GM_setValue('backgroundImage', bgImageInput.value); GM_setValue('profileImage', profileInput.value); GM_setValue('transparency', transparencyInput.value); GM_setValue('blur', blurInput.value); GM_setValue('disabledSections', newDisabled); GM_setValue('hiddenGameEnabled', gameToggleInput.checked); GM_setValue('hiddenGameURL', gameUrlInput.value); applySettings(); expandContentHeight(); if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) { removeHiddenGameElements(); if (gameToggleInput.checked) addHiddenGameButton(); } modal.remove(); }); const resetButton = createButton('Reset to Defaults', () => { GM_setValue('backgroundColor', DEFAULTS.backgroundColor); GM_setValue('backgroundImage', DEFAULTS.backgroundImage); GM_setValue('profileImage', DEFAULTS.profileImage); GM_setValue('transparency', DEFAULTS.transparency); GM_setValue('blur', DEFAULTS.blur); GM_setValue('disabledSections', DEFAULTS.disabledSections); GM_setValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled); GM_setValue('hiddenGameURL', DEFAULTS.hiddenGameURL); applySettings(); expandContentHeight(); if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) { removeHiddenGameElements(); if (DEFAULTS.hiddenGameEnabled) addHiddenGameButton(); } modal.remove(); }); const closeButton = createButton('Close', () => modal.remove()); // Append in desired order form.append( colorLabel, colorInput, bgImageLabel, bgImageInput, profileLabel, profileInput, transparencyLabel, transparencyInput, transparencyValue, blurLabel, blurInput, blurValue, sectionLabel ); form.append(togglesContainer); // Insert hidden game controls form.append(gameToggleLabel, gameToggleInput, gameUrlLabel, gameUrlInput); buttonContainer.append(saveButton, resetButton, closeButton); modal.append(heading, form, buttonContainer); return modal; } function createLabel(text, forId) { const label = document.createElement('label'); label.textContent = text; label.htmlFor = forId; label.style.fontWeight = 'bold'; return label; } function createInput(type, id, value) { const input = document.createElement('input'); input.type = type; input.id = id; input.value = value; input.style.padding = '5px'; input.style.width = '100%'; if (type === 'color') input.style.cursor = 'pointer'; return input; } function createButton(text, onClick) { const button = document.createElement('button'); button.textContent = text; button.onclick = onClick; button.style.cssText = ` padding: 8px 15px; border: none; border-radius: 4px; cursor: pointer; background: #007bff; color: white; transition: background 0.3s; `; button.addEventListener('mouseover', () => button.style.background = '#0056b3'); button.addEventListener('mouseout', () => button.style.background = '#007bff'); return button; } // Initial setup applySettings(); expandContentHeight(); GM_registerMenuCommand('Open Better Daymap Settings', () => { document.body.appendChild(createSettingsPage()); }); // --- HIDDEN GAME BUTTON/IFRAME LOGIC --- function removeHiddenGameElements() { const btn = document.getElementById('soccerToggleBtn'); const cont = document.getElementById('soccerEmbedContainer'); if (btn) btn.remove(); if (cont) cont.remove(); } function addHiddenGameButton() { // Remove if already present removeHiddenGameElements(); const hiddenGameURL = GM_getValue('hiddenGameURL', DEFAULTS.hiddenGameURL); const soccerContainer = document.createElement('div'); soccerContainer.id = 'soccerEmbedContainer'; soccerContainer.style.cssText = 'width:100%; overflow:hidden; max-height:0; transition:max-height 0.4s ease;'; const soccerFrame = document.createElement('iframe'); soccerFrame.src = hiddenGameURL; soccerFrame.style.cssText = 'width:100%; height:100vh; border:none;'; soccerContainer.appendChild(soccerFrame); document.body.appendChild(soccerContainer); const soccerBtn = document.createElement('button'); soccerBtn.id = 'soccerToggleBtn'; soccerBtn.textContent = '▶️'; soccerBtn.style.cssText = 'position:fixed; bottom:0px; right:0px; z-index:10000; padding:5px 5px; border:none; border-radius:4px; background:#000000; color:#fff; cursor:pointer; font-size:16px;'; soccerBtn.addEventListener('click', () => { if (soccerContainer.style.maxHeight === '0px' || !soccerContainer.style.maxHeight) { soccerContainer.style.maxHeight = '100vh'; soccerBtn.textContent = '◀️'; } else { soccerContainer.style.maxHeight = '0'; soccerBtn.textContent = '▶️'; } }); document.body.appendChild(soccerBtn); } // --- TIMETABLE PAGE ENHANCEMENTS --- if (location.pathname.match(/\/daymap\/timetable\/timetable\.aspx$/)) { GM_addStyle(` /* Main layout improvements */ .main-layout { padding: 20px; max-width: 1440px; margin: 0 auto; } .grid { gap: 0px; } .card { border-radius: 10px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); border: none; overflow: hidden; } /* Timetable styling */ .tt { border-collapse: separate; border-spacing: 0; width: 100%; } .tt th { background: #1888C9; color: white; padding: 10px; text-align: center; font-weight: 500; } .tt td { padding: 0; border: 1px solid #e9ecef; } .ttCell { padding: 8px; height: 80px; display: flex; flex-direction: column; justify-content: space-between; transition: all 0.2s ease; } .ttCell:hover { filter: brightness(95%); transform: translateY(-2px); } .ttSubject { font-weight: 600; font-size: 0.9rem; margin-bottom: 5px; } .ttTeacher, .ttRoom { font-size: 0.8rem; color: white; } .Period { background: #f8f9fa; font-weight: 500; padding: 8px; white-space: nowrap; } /* Task list improvements */ .feed { width: 100%; border-collapse: collapse; } .feed tr { border-bottom: 1px solid #e9ecef; } .feed td { padding: 12px; } .feed .cap { width: 120px; font-weight: 500; color: #2c3e50; vertical-align: top; } .feed .itm { cursor: pointer; transition: background 0.2s ease; } .feed .itm:hover { background: #f8f9fa; } .Caption { font-size: 0.8rem; color: #6c757d; white-space: normal !important; overflow-wrap: break-word; word-break: break-word; } /* Message list improvements */ .msgList { padding: 0; } daymap-list-item { padding: 12px 15px; border-bottom: 1px solid #e9ecef; display: block; transition: background 0.2s ease; } daymap-list-item:hover { background: #f8f9fa; } /* Button improvements */ .btn { border-radius: 6px; padding: 8px 16px; transition: all 0.2s ease; } .btn:hover { transform: translateY(-1px); } /* Responsive adjustments */ @media (max-width: 768px) { .grid > div { width: 100% !important; } .ttCell { height: auto; min-height: 60px; } } `); window.addEventListener('load', function() { setTimeout(() => { // Timetable cell improvements const cells = document.querySelectorAll('.ttCell'); cells.forEach(cell => { cell.style.cursor = 'pointer'; const subject = cell.querySelector('.ttSubject'); if (subject) { const text = subject.textContent.trim(); if (text.length > 25) { subject.textContent = text.substring(0, 22) + '...'; } } }); // --- Timetable TH day text coloring (all except today are gray, today is blue) --- (function highlightTimetableDays() { const ths = document.querySelectorAll('.tt th'); if (!ths.length) return; const now = new Date(); const todayDay = now.getDate(); const todayMonth = now.getMonth() + 1; ths.forEach(th => { const text = th.textContent.replace(/\s+/g, ' ').trim(); const match = text.match(/([A-Za-z]{3})\s*(\d{1,2})\/(\d{1,2})/); if (!match) return; const day = parseInt(match[2], 10); const month = parseInt(match[3], 10); if (month === todayMonth && day === todayDay) { // Current day: blue and bold th.style.color = '#1888C9'; th.style.fontWeight = 'bold'; } if (month < todayMonth || (month === todayMonth && day < todayDay)) { // Past day: gray th.style.color = '#e0e0e0'; th.style.color = '#888'; } }); })(); // Task list color coding const tasks = document.querySelectorAll('.feed .itm'); tasks.forEach(task => { task.style.transition = 'all 0.2s ease'; if (task.innerHTML.includes('Overdue') || task.innerHTML.includes('Uh did you submit on Turnitin or something?')) { task.style.borderLeft = '3px solid #e81123'; } else if (task.innerHTML.includes('Grade:') || task.innerHTML.includes('Mark:') || task.innerHTML.includes('Work has been received')) { task.style.borderLeft = '3px solid #2ecc40'; } else { task.style.borderLeft = '3px solid #ffb900'; } }); // --- Task list: Show due/completed/overdue days for "Set on ... and due on ..." style --- tasks.forEach(task => { let match = task.innerText.match(/due on \w{3} (\d{1,2}) (\w{3})(?: (\d{4}))?/i); if (match) { let day = parseInt(match[1], 10); let monthStr = match[2].toLowerCase(); let year = match[3] ? parseInt(match[3], 10) : (new Date()).getFullYear(); const months = { jan:0, feb:1, mar:2, apr:3, may:4, jun:5, jul:6, aug:7, sep:8, oct:9, nov:10, dec:11 }; let month = months[monthStr]; if (month === undefined) return; let dueDate = new Date(year, month, day); let now = new Date(); now.setHours(0,0,0,0); dueDate.setHours(0,0,0,0); if (!match[3] && dueDate < now) { } let diffMs = dueDate - now; let diffDays = Math.round(diffMs / (1000 * 60 * 60 * 24)); let label = '', color = ''; // COMPLETED if ( task.innerText.includes('Work has been received') || task.innerText.includes('Grade:') || task.innerText.includes('Mark:') ) { color = '#2ecc40'; // green if (diffDays < 0) { label = ` (completed ${-diffDays} days ago)`; } else if (diffDays === 0) { label = ` (completed today)`; } else { label = ` (completed early)`; } } // OVERDUE else if ( task.innerText.includes('Overdue') || task.innerText.includes('Uh did you submit on Turnitin or something?') ) { if (diffDays < 0) { color = '#e81123'; // red label = ` (overdue by ${-diffDays} day${-diffDays === 1 ? '' : 's'})`; } } // NORMAL else { if (diffDays > 1) { color = '#ffb900'; // amber/yellow label = ` (due in ${diffDays} days)`; } else if (diffDays === 1) { color = '#ffb900'; label = ` (due in 1 day)`; } else if (diffDays === 0) { color = '#ffb900'; label = ` (due today)`; } } // Only add if not already present and label is not empty if ( label && !task.innerText.includes('due in') && !task.innerText.includes('overdue') && !task.innerText.includes('completed') ) { task.innerHTML = task.innerHTML.replace( /(due on \w{3} \d{1,2} \w{3}(?: \d{4})?)/i, `$1<br><span style="color:${color}; font-weight:bold;">${label}</span>` ); } } }); // --- End of Task list due/completed/overdue days feature --- // --- Messages section: Show how long ago each message was sent --- (function enhanceMessages() { // Find the messages section (adjust selector if needed for your Daymap) const messageCards = document.querySelectorAll('#pnlRight > div.card.expWindow:nth-of-type(1) .msgList, #pnlRight > div.card.expWindow:nth-of-type(1) daymap-list-item'); if (!messageCards.length) return; // Helper: parse message date string to Date object function parseMessageDate(str) { const now = new Date(); // Format 1: "Fri May 30" (older than a week) let match = str.match(/(\w{3}) (\w{3}) (\d{1,2})/); if (match) { // e.g., "Fri May 30" => this year let year = now.getFullYear(); let month = ["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"].indexOf(match[2].toLowerCase()); let day = parseInt(match[3], 10); let date = new Date(year, month, day); // If in the future (e.g., Dec 31 when now is Jan), assume last year if (date > now) date.setFullYear(year - 1); return date; } // Format 2: "Fri 2:11 PM" (this week) match = str.match(/(\w{3}) (\d{1,2}):(\d{2})\s*(AM|PM)/i); if (match) { // Find the most recent day matching the given weekday let targetDay = ["sun","mon","tue","wed","thu","fri","sat"].indexOf(match[1].toLowerCase()); let hours = parseInt(match[2], 10); let minutes = parseInt(match[3], 10); if (match[4].toUpperCase() === "PM" && hours !== 12) hours += 12; if (match[4].toUpperCase() === "AM" && hours === 12) hours = 0; let date = new Date(now); date.setHours(hours, minutes, 0, 0); // Move back to the correct weekday let diff = (date.getDay() - targetDay + 7) % 7; date.setDate(date.getDate() - diff); // If the calculated date is in the future, subtract a week if (date > now) date.setDate(date.getDate() - 7); return date; } return null; } // Helper: format how long ago function timeAgo(date) { const now = new Date(); const diffMs = now - date; const diffSec = Math.floor(diffMs / 1000); const diffMin = Math.floor(diffSec / 60); const diffHr = Math.floor(diffMin / 60); const diffDay = Math.floor(diffHr / 24); if (diffDay > 0) return diffDay === 1 ? "1 day ago" : `${diffDay} days ago`; if (diffHr > 0) return diffHr === 1 ? "1 hour ago" : `${diffHr} hours ago`; if (diffMin > 0) return diffMin === 1 ? "1 minute ago" : `${diffMin} minutes ago`; return "just now"; } // Enhance each message messageCards.forEach(card => { // Find all message rows/items (adjust selector as needed for your Daymap) let items = card.querySelectorAll('.msg, daymap-list-item'); if (!items.length) items = [card]; // fallback for flat list items.forEach(item => { // Try to find the date string in the message (adjust selector if needed) let dateNode = item.querySelector('.date, .msgDate, .Caption') || item; let dateText = dateNode.textContent || ''; let msgDate = parseMessageDate(dateText.trim()); if (msgDate) { // Remove old "ago" label if present let old = item.querySelector('.msg-ago-label'); if (old) old.remove(); // Add new label let ago = document.createElement('span'); ago.className = 'msg-ago-label'; ago.style.color = '#888'; ago.style.fontSize = '0.9em'; ago.style.marginLeft = '8px'; ago.textContent = `(${timeAgo(msgDate)})`; dateNode.appendChild(ago); } }); }); })(); // --- End of messages section enhancement --- }); }); // --- HIDDEN GAME BUTTON --- if (GM_getValue('hiddenGameEnabled', DEFAULTS.hiddenGameEnabled)) { addHiddenGameButton(); } } })();