Last active
May 20, 2025 19:31
-
-
Save oovz/e1f36ff2bf18c0ab64180e785f837a12 to your computer and use it in GitHub Desktop.
A tampermonkey/violentmonkey script to highlight updated chapter in Jinjiang (jjwxc.net)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name 晋江章节修改高亮 | |
// @namespace http://tampermonkey.net/ | |
// @version 0.3 | |
// @description 高亮修改过的章节,区别首发时间和更新时间 | |
// @author oovz | |
// @match *://www.jjwxc.net/onebook.php?novelid=* | |
// @exclude *://www.jjwxc.net/onebook.php?novelid=*&chapterid=* | |
// @source https://greasyfork.org/en/scripts/536627-%E6%99%8B%E6%B1%9F%E7%AB%A0%E8%8A%82%E4%BF%AE%E6%94%B9%E9%AB%98%E4%BA%AE | |
// @source https://gist.github.com/oovz/e1f36ff2bf18c0ab64180e785f837a12 | |
// @license MIT | |
// @grant GM_addStyle | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
const HIGHLIGHT_COLOR = '#FFFACD'; // Light yellow | |
const TOGGLE_BUTTON_ID = 'toggle-chapter-highlight'; | |
const STORAGE_KEY = 'jjwxc-chapter-highlight-enabled'; | |
GM_addStyle(` | |
.chapter-modified { | |
background-color: ${HIGHLIGHT_COLOR} !important; | |
} | |
#${TOGGLE_BUTTON_ID} { | |
margin-left: 10px; | |
cursor: pointer; | |
} | |
#${TOGGLE_BUTTON_ID}:hover { | |
text-decoration: underline; | |
} | |
.highlight-tooltip { | |
position: relative; | |
display: inline-block; | |
} | |
.highlight-tooltip .tooltip-text { | |
visibility: hidden; | |
width: 200px; | |
background-color: #FFF8DC; | |
color: #000; | |
text-align: center; | |
border-radius: 3px; | |
border: 1px solid #DEB887; | |
padding: 3px 5px; | |
position: absolute; | |
z-index: 1000; | |
bottom: 125%; | |
left: 50%; | |
margin-left: -100px; | |
opacity: 0; | |
transition: opacity 0.3s; | |
font-size: 12px; | |
} | |
.highlight-tooltip:hover .tooltip-text { | |
visibility: visible; | |
opacity: 1; | |
} | |
`); | |
// State | |
// highlighting is enabled by default | |
const storedValue = localStorage.getItem(STORAGE_KEY); | |
let isHighlightEnabled = storedValue === null || storedValue === 'true'; // Default to enabled if not set | |
/** | |
* Find all chapter rows in the document | |
* @returns {NodeList} List of chapter rows | |
*/ | |
function findChapterRows() { | |
// latest updated chapter will have itemprop="chapter newestChapter" | |
return document.querySelectorAll('tr[itemprop*="chapter"]'); | |
} | |
/** | |
* Extract the publish time from a chapter row | |
* @param {Element} chapterRow - The TR element representing a chapter | |
* @returns {string|null} The extracted publish time or null if not found | |
*/ | |
function getPublishTimeFromChapter(chapterRow) { | |
try { | |
const titleCell = chapterRow.querySelector("td[title]"); | |
if (!titleCell || !titleCell.title) { | |
return null; | |
} | |
// Title will contain both store and publish times | |
// '章节存稿时间:2022-09-09 16:23:42\n章节首发时间:2022-09-09 16:25:00' | |
const titleText = titleCell.title; | |
// Extract the publish time after "章节首发时间:" | |
const publishTimeMatch = titleText.match(/章节首发时间:([^\n]+)/); | |
if (publishTimeMatch && publishTimeMatch[1]) { | |
return publishTimeMatch[1].trim(); | |
} | |
} catch (e) { | |
console.warn('Error extracting publish time:', e); | |
} | |
return null; // No publish time found | |
} | |
/** | |
* Extract the update time from a chapter row | |
* @param {Element} chapterRow - The TR element representing a chapter | |
* @returns {string|null} The extracted update time or null if not found | |
*/ | |
function getUpdateTimeFromChapter(chapterRow) { | |
try { | |
return chapterRow.querySelector("td[title] > span:not([id='latestUpdates'])").textContent.trim(); | |
} catch (e) { | |
console.warn('Error extracting update time:', e); | |
} | |
return null; // No update time found | |
} | |
/** | |
* Apply highlighting to chapters with different publish vs update times | |
*/ | |
function highlightModifiedChapters() { | |
const chapterRows = findChapterRows(); | |
let modifiedCount = 0; | |
chapterRows.forEach(row => { | |
try { | |
const publishTime = getPublishTimeFromChapter(row); | |
const updateTime = getUpdateTimeFromChapter(row); | |
if (publishTime && updateTime && publishTime !== updateTime) { | |
// Different times - highlight the row | |
row.classList.add('chapter-modified'); | |
// Store original classes if needed for toggling | |
if (!row.dataset.originalBg) { | |
row.dataset.originalBg = 'saved'; | |
} | |
modifiedCount++; | |
} | |
} catch (e) { | |
console.warn('Error processing chapter row:', e); | |
} | |
}); | |
console.log(`Found ${modifiedCount} modified chapters out of ${chapterRows.length} total`); | |
} | |
/** | |
* Remove all highlighting from chapter rows | |
*/ | |
function removeHighlights() { | |
const chapterRows = findChapterRows(); | |
chapterRows.forEach(row => { | |
row.classList.remove('chapter-modified'); | |
}); | |
} | |
/** | |
* Create and insert the toggle button | |
*/ | |
function createToggleButton() { | |
// Find the second tr element in the document | |
const secondRow = document.querySelector('tbody > tr:nth-of-type(2) > td'); | |
if (!secondRow) { | |
console.error('Cannot insert toggle button'); | |
} else { | |
// Create toggle button wrapper with tooltip | |
const wrapper = document.createElement('span'); | |
wrapper.className = 'highlight-tooltip'; | |
wrapper.style.marginLeft = '10px'; | |
const button = createButton(); | |
wrapper.appendChild(button); | |
const tooltip = document.createElement('span'); | |
tooltip.className = 'tooltip-text'; | |
tooltip.textContent = '高亮显示更新时间与首发时间不同的章节'; | |
wrapper.appendChild(tooltip); | |
// Insert the button at the end of the second row | |
secondRow.appendChild(wrapper); | |
} | |
} | |
/** | |
* Create the toggle button element | |
* @returns {HTMLElement} The created button | |
*/ | |
function createButton() { | |
// Create a link that looks like a button | |
const button = document.createElement('a'); | |
button.id = TOGGLE_BUTTON_ID; | |
button.href = 'javascript:void(0);'; // Make it clickable but not navigate | |
button.textContent = isHighlightEnabled ? '[插件:关闭章节高亮]' : '[插件:开启章节高亮]'; | |
button.style.color = '#FF0000'; // Red text color | |
button.style.textDecoration = 'none'; // No underline | |
// Add click handler | |
button.addEventListener('click', handleToggleClick); | |
return button; | |
} | |
/** | |
* Handle toggle button click - toggle highlighting on/off | |
*/ | |
function handleToggleClick() { | |
isHighlightEnabled = !isHighlightEnabled; | |
const button = document.getElementById(TOGGLE_BUTTON_ID); | |
if (button) { | |
button.textContent = isHighlightEnabled ? '[插件:关闭章节高亮]' : '[插件:开启章节高亮]'; | |
} | |
if (isHighlightEnabled) { | |
highlightModifiedChapters(); | |
} else { | |
removeHighlights(); | |
} | |
try { | |
localStorage.setItem(STORAGE_KEY, isHighlightEnabled ? 'true' : 'false'); | |
} catch (e) { | |
console.warn('Could not save highlight preference to localStorage', e); | |
} | |
} | |
/** | |
* Initialize the script | |
*/ | |
function initialize() { | |
console.log('Initializing chapter highlight script...'); | |
createToggleButton(); | |
if (isHighlightEnabled) { | |
highlightModifiedChapters(); | |
} | |
} | |
// Wait for the DOM to be ready before initializing | |
if (document.readyState === 'loading') { | |
document.addEventListener('DOMContentLoaded', initialize); | |
} else { | |
initialize(); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment