您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Customize MangaDex title pages by adding custom alt titles, changing the main title and cover, and adding custom tags\links. All data is stored inside userscript storage.
// ==UserScript== // @name MangaDex Customizer // @namespace https://github.com/rRoler/UserScripts // @version 1.0.2 // @description Customize MangaDex title pages by adding custom alt titles, changing the main title and cover, and adding custom tags\links. All data is stored inside userscript storage. // @author Roler // @icon https://www.google.com/s2/favicons?sz=64&domain=mangadex.org // @match https://mangadex.org/* // @match https://canary.mangadex.dev/* // @match https://demo.komga.org/* // @supportURL https://github.com/rRoler/UserScripts/issues // @require https://cdnjs.cloudflare.com/ajax/libs/validator/13.12.0/validator.min.js#sha256-d2c75e3159ceac9c14dcc8a7aeb09ea30970de6c321c89070e5b0157842c5c88 // @grant GM_getValue // @grant GM_setValue // @run-at document-end // ==/UserScript== (function() { 'use strict'; const userScriptId = `mdc-${crypto.randomUUID()}`; const storage = { mangadex: { titles: { custom_sections: { id: 'mangadex_titles_custom_sections', defaultValue: 'array' }, data: { id: 'mangadex_titles_data', defaultValue: 'object', custom_sections: { id: 'custom_sections', defaultValue: 'array' }, alt_titles: { id: 'alt_titles', defaultValue: 'array' }, main_title: { id: 'main_title', defaultValue: 'string' }, main_cover: { id: 'main_cover', defaultValue: 'string' } }, } } }; const createStorageDefaultValue = (type) => { switch (type) { case 'array': return []; case 'object': return {}; default: return ''; } }; const getStorage = (section) => GM_getValue(section.id, createStorageDefaultValue(section.defaultValue)); const setStorage = (section, value) => GM_setValue(section.id, value); const isMd = /^mangadex\.org|canary\.mangadex\.dev$/.test(window.location.hostname); const mdTitleOptions = { altTitle: { add: mdAddAltTitleOptions }, customSection: { add: mdAddCustomSectionOptions }, volumeCover: { add: mdAddVolumeCoverOptions, tab: 'art', dynamic: true } }; const mdGetTitleStorage = (titleId, section) => { const storedData = getStorage(storage.mangadex.titles.data); return storedData[titleId] && storedData[titleId][section.id] || createStorageDefaultValue(section.defaultValue); } const mdSetTitleStorage = (titleId, section, value, del = false, append = false) => { const storedData = getStorage(storage.mangadex.titles.data); if (!storedData[titleId]) storedData[titleId] = {}; if (append) { if (!storedData[titleId][section.id]) storedData[titleId][section.id] = []; if (del) { const index = storedData[titleId][section.id].indexOf(value); if (index > -1) storedData[titleId][section.id].splice(index, 1); } else { storedData[titleId][section.id].push(value); } } else { if (del) delete storedData[titleId][section.id]; else storedData[titleId][section.id] = value; } try { if (storedData[titleId][section.id] && Object.keys(storedData[titleId][section.id]).length < 1) delete storedData[titleId][section.id]; if (Object.keys(storedData[titleId]).length < 1) delete storedData[titleId]; } catch (e) {} setStorage(storage.mangadex.titles.data, storedData); } const mdGetTitleId = (url = window.location.pathname) => { const titleIdMatch = url.match(/\/(?:title|manga|covers)\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/); return titleIdMatch && titleIdMatch[1]; } const mdGetCoverFileName = (url) => { const fileNameMatch = url.match(/\/covers\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[A-Za-z]+)(\.[0-9]+\.[A-Za-z]+)?/); return { fileName: fileNameMatch && fileNameMatch[1], size: fileNameMatch && fileNameMatch[2] } } const mdGetAltTitlesSectionElement = (infoElement) => { const fullWidthSections = infoElement.querySelectorAll('.w-full'); return Array.from(fullWidthSections).find(section => section.querySelector('.alt-title')); } const mdGetInfoElement = (titleId) => { let infoElement = document.querySelector('.flex.flex-wrap.gap-x-4.gap-y-2'); if (!infoElement) return; infoElement = window.getComputedStyle(infoElement).display === 'none' ? document.querySelector(`[id="${titleId}"]`) : infoElement; if (!infoElement) return; return infoElement; } const komgaGetSeriesId = (url = window.location.pathname) => { const seriesIdMatch = url.match(/\/series\/([0-9A-Za-z]+)/); return seriesIdMatch && seriesIdMatch[1]; } let mdTitleOptionsLoaded = false; let komgaCurrentSeriesId; let scriptErrored = false; observeElement(async (mutations, observer) => { if (scriptErrored) { observer.disconnect(); alert('The MangaDex Customizer userscript has encountered an error.\nPlease reload the page or disable the userscript if this error persists.'); return; } if (isMd && !window.location.pathname.includes('edit')) { if (!document.querySelector('.md-content')) return; const titleId = mdGetTitleId(); if (titleId) { const currentTabMatch = window.location.search.match(/tab=([a-z]+)/); const currentTab = currentTabMatch && currentTabMatch[1] || 'chapters'; for (const optionId in mdTitleOptions) { const option = mdTitleOptions[optionId]; if (!option.tab || option.tab === currentTab) { if (option.dynamic || !option.loaded || option.loadedId !== titleId || option.loadedTab !== currentTab) { try { option.loaded = option.add(titleId); if (option.loaded) { option.loadedId = titleId; option.loadedTab = currentTab; mdTitleOptionsLoaded = true; } } catch (e) { console.error(e); scriptErrored = true; return; } } } } } else if (mdTitleOptionsLoaded) { for (const optionId in mdTitleOptions) { const option = mdTitleOptions[optionId]; option.loaded = false; option.loadedId = ''; option.loadedTab = ''; if (option.storage) delete option.storage; } mdTitleOptionsLoaded = false; } try { mdReplaceTitles(); mdReplaceVolumeCovers(titleId); } catch (e) { console.error(e); scriptErrored = true; } } else { if (!document.querySelector('.container')) return; const seriesId = komgaGetSeriesId(); if (seriesId) { if (seriesId === komgaCurrentSeriesId) return; try { if (komgaAutoMatch(seriesId)) komgaCurrentSeriesId = seriesId; } catch (e) { console.error(e); scriptErrored = true; } } else { komgaCurrentSeriesId = ''; } } }); function mdAddCustomSectionOptions(titleId) { const infoElement = mdGetInfoElement(titleId); if (!infoElement) return false; const infoSectionElement = infoElement.querySelector('.mb-2:not(.hidden)'); if (!infoSectionElement) return false; const sectionInfoElement = infoSectionElement.querySelector('div.flex.flex-wrap'); if (!sectionInfoElement) return false; const sectionInfoLinkElement = sectionInfoElement.querySelector('a'); if (!sectionInfoLinkElement) return false; const altTitlesSectionElement = mdGetAltTitlesSectionElement(infoElement); if (!altTitlesSectionElement) return false; const createSectionElement = (sectionData, required = false) => { const sectionIdAttribute = `${userScriptId}-section-id`; const sectionExists = !!document.querySelector(`[${sectionIdAttribute}="${sectionData.id}"]`); if (sectionExists) return; const newInfoSectionElement = infoSectionElement.cloneNode(true); newInfoSectionElement.setAttribute(sectionIdAttribute, sectionData.id); const newInfoNameElement = newInfoSectionElement.querySelector('div.font-bold'); const newInfoElement = newInfoSectionElement.querySelector('div.flex.flex-wrap'); newInfoNameElement.textContent = sectionData.name + (required ? '' : ' '); newInfoElement.querySelectorAll('a').forEach(element => element.remove()); if (required) return newInfoSectionElement; const newInfoRemoveElement = document.createElement('span'); newInfoRemoveElement.textContent = '⨯'; newInfoRemoveElement.classList.add('cursor-pointer'); newInfoRemoveElement.addEventListener('click', () => { if (!confirm(`Are you sure you want to delete this section?\n\n${sectionData.name}`)) return; const storedSections = getStorage(storage.mangadex.titles.custom_sections); const storedSectionIndex = storedSections.findIndex(section => section.id === sectionData.id); if (storedSectionIndex > -1) { storedSections.splice(storedSectionIndex, 1); setStorage(storage.mangadex.titles.custom_sections, storedSections); } newInfoSectionElement.remove(); }); newInfoNameElement.appendChild(newInfoRemoveElement); return newInfoSectionElement; } const createSectionButton = (sectionData, value, sectionInfoLinkElement) => { const newLink = sectionInfoLinkElement.cloneNode(true); const newLinkText = newLink.querySelector('span'); newLink.href = '#'; newLink.classList.add('gap-1'); newLinkText.textContent = value; const newLinkRemove = document.createElement('span'); newLinkRemove.textContent = '⨯'; newLinkRemove.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); if (!confirm(`Are you sure you want to delete this ${sectionData.name}?\n\n${value}`)) return; const storedTitleSections = mdGetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections); const storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id); if (storedSectionIndex > -1) { const sectionValues = storedTitleSections[storedSectionIndex].values || []; const sectionValueIndex = sectionValues.findIndex(_value => _value === value); if (sectionValueIndex > -1) { sectionValues.splice(sectionValueIndex, 1); storedTitleSections[storedSectionIndex].values = sectionValues; } if (sectionValues.length < 1) { storedTitleSections.splice(storedSectionIndex, 1); } } mdSetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections, storedTitleSections); newLink.remove(); }); newLink.appendChild(newLinkRemove); try { const valueMatch = value.match(/^\[([\s\w\-]+)]\((https?:\/\/.*)\)$/) const urlValue = valueMatch && valueMatch[2] ? valueMatch[2] : value if (!validator.isURL(urlValue)) throw new Error('Invalid URL'); const url = new URL(urlValue); newLink.href = url.href; newLinkText.textContent = valueMatch && valueMatch[2] ? valueMatch[1] : url.hostname; return newLink; } catch (e) {} newLink.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); alert(value); }); return newLink; }; const createSectionLink = (sectionData, sectionElement) => { const newInfoElement = sectionElement.querySelector('div.flex.flex-wrap'); const newInfoLinkElement = sectionInfoLinkElement.cloneNode(true); const newInfoLinkIconElement = newInfoLinkElement.querySelector('svg'); const newInfoLinkTextElement = newInfoLinkElement.querySelector('span'); newInfoLinkElement.target = '_blank'; newInfoLinkElement.rel = 'noopener noreferrer'; if (newInfoLinkIconElement) newInfoLinkIconElement.remove(); const storedTitleSections = mdGetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections); const storedSectionData = storedTitleSections.find(section => section.id === sectionData.id) || {}; const storedSectionDataValues = storedSectionData.values || []; storedSectionDataValues.forEach(value => { const newLink = createSectionButton(sectionData, value, newInfoLinkElement); if (!newLink) return; newInfoElement.appendChild(newLink); }); newInfoLinkTextElement.textContent = `+`; newInfoLinkElement.href = '#'; newInfoLinkElement.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); const storedSections = getStorage(storage.mangadex.titles.custom_sections); if (!storedSections.some(section => section.id === sectionData.id)) { storedSections.push(sectionData); setStorage(storage.mangadex.titles.custom_sections, storedSections); } const value = prompt(`Enter new ${sectionData.name} value`); if (!value) return; const newLink = createSectionButton(sectionData, value, newInfoLinkElement); if (!newLink) return; const storedTitleSections = mdGetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections); let storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id); if (storedSectionIndex < 0) { storedTitleSections.push({ id: sectionData.id }); storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id); } const sectionValues = storedTitleSections[storedSectionIndex].values || []; sectionValues.push(value); storedTitleSections[storedSectionIndex].values = sectionValues; mdSetTitleStorage(titleId, storage.mangadex.titles.data.custom_sections, storedTitleSections); newInfoElement.insertBefore(newLink, newInfoLinkElement); }); newInfoElement.appendChild(newInfoLinkElement); return newInfoElement; } const createSection = (sectionData) => { const newSectionElement = createSectionElement(sectionData); if (!newSectionElement) return; const newSectionLinkElement = createSectionLink(sectionData, newSectionElement); newSectionElement.appendChild(newSectionLinkElement); infoElement.insertBefore(newSectionElement, altTitlesSectionElement); } const addNewSectionElement = createSectionElement({ id: 'add_local_section', name: 'Custom Sections +' }, true); if (addNewSectionElement) { addNewSectionElement.querySelector('div.flex.flex-wrap').remove(); const addNewSectionTextElement = addNewSectionElement.querySelector('div.font-bold'); addNewSectionTextElement.classList.remove('mb-2'); addNewSectionTextElement.classList.add('cursor-pointer'); addNewSectionTextElement.style.setProperty('width', 'fit-content'); addNewSectionElement.classList.remove('mb-2'); addNewSectionElement.classList.add('w-full'); addNewSectionTextElement.addEventListener('click', () => { const storedSections = getStorage(storage.mangadex.titles.custom_sections); const sectionName = prompt('Enter new section name'); const trimmedSectionName = sectionName && sectionName.trim(); if (!trimmedSectionName) return; const sectionData = { id: trimmedSectionName.replace(/\s/g, '_').toLowerCase(), name: trimmedSectionName } if (storedSections.some(section => section.id === sectionData.id)) return; storedSections.push(sectionData); setStorage(storage.mangadex.titles.custom_sections, storedSections); createSection(sectionData); }); infoElement.insertBefore(addNewSectionElement, altTitlesSectionElement); } const storedSections = getStorage(storage.mangadex.titles.custom_sections); storedSections.forEach(createSection); return true; } function mdAddAltTitleOptions(titleId) { const infoElement = mdGetInfoElement(titleId); if (!infoElement) return false; if (!infoElement.querySelector('a')) return false; let altTitlesSectionElement = mdGetAltTitlesSectionElement(infoElement); if (!altTitlesSectionElement) { altTitlesSectionElement = document.createElement('div'); altTitlesSectionElement.classList.add('w-full'); infoElement.appendChild(altTitlesSectionElement); const altTitlesSectionTextElement = document.createElement('div'); altTitlesSectionTextElement.classList.add('font-bold', 'mb-1'); altTitlesSectionTextElement.textContent = 'Alternative Titles'; altTitlesSectionElement.appendChild(altTitlesSectionTextElement); const altTitleElement = document.createElement('div'); altTitleElement.classList.add('mb-1', 'flex', 'gap-x-2', 'alt-title'); altTitlesSectionElement.appendChild(altTitleElement); } const altTitlesSectionLoadedAttribute = `${userScriptId}-alt-title-section-loaded`; if (altTitlesSectionElement.hasAttribute(altTitlesSectionLoadedAttribute)) return true; altTitlesSectionElement.setAttribute(altTitlesSectionLoadedAttribute, 'true'); const altTitlesSectionTextElement = altTitlesSectionElement.querySelector('div.font-bold'); const altTitlesElements = altTitlesSectionElement.querySelectorAll('.alt-title'); const altTitleElement = altTitlesElements[0].cloneNode(true); if (!mdTitleOptions.altTitle.storage) mdTitleOptions.altTitle.storage = []; const addAltTitleStar = altTitleElement => { const storedTitle = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_title); const altTitleTextElement = altTitleElement.querySelector('span'); if (!altTitleTextElement) return; const setTitleObject = { selected: storedTitle === altTitleTextElement.textContent, element: altTitleElement, starElement: document.createElement('span'), value: altTitleTextElement.textContent } setTitleObject.starElement.textContent = setTitleObject.selected ? '★' : '☆'; setTitleObject.starElement.classList.add('cursor-pointer'); if (setTitleObject.selected) mdReplaceTitles(titleId); setTitleObject.starElement.addEventListener('click', () => { mdSetTitleStorage(titleId, storage.mangadex.titles.data.main_title, setTitleObject.value, setTitleObject.selected); mdReplaceTitles(titleId, setTitleObject.selected); setTitleObject.selected = !setTitleObject.selected; mdTitleOptions.altTitle.storage.forEach(_setTitleObject => { _setTitleObject.selected = _setTitleObject.value === setTitleObject.value && setTitleObject.selected; _setTitleObject.starElement.textContent = _setTitleObject.selected ? '★' : '☆'; }); }); mdTitleOptions.altTitle.storage.push(setTitleObject); altTitleElement.prepend(setTitleObject.starElement); }; const createAltTitle = (value) => { if (!altTitlesElements[0].querySelector('span')) altTitlesElements[0].remove(); const newAltTitleElement = altTitleElement.cloneNode(true); const newAltTitleIconElement = newAltTitleElement.querySelector('div'); let newAltTitleTextElement = newAltTitleElement.querySelector('span'); if (!newAltTitleTextElement) { newAltTitleTextElement = document.createElement('span'); newAltTitleElement.appendChild(newAltTitleTextElement); } const removeCustomAltTitleElement = document.createElement('span'); if (newAltTitleIconElement) newAltTitleIconElement.remove(); newAltTitleTextElement.textContent = value; removeCustomAltTitleElement.textContent = '⨯'; removeCustomAltTitleElement.classList.add('cursor-pointer'); removeCustomAltTitleElement.addEventListener('click', () => { if (!confirm(`Are you sure you want to delete this title?\n\n${value}`)) return; mdSetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles, value, true, true); const setTitleObjectIndex = mdTitleOptions.altTitle.storage.findIndex(setTitleObject => setTitleObject.value === value); if (setTitleObjectIndex > -1) mdTitleOptions.altTitle.storage.splice(setTitleObjectIndex, 1); const storedAltTitles = mdGetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles); const storedMainTitle = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_title); if (storedMainTitle === value && !storedAltTitles.some(altTitle => altTitle === value)) { mdSetTitleStorage(titleId, storage.mangadex.titles.data.main_title, value, true); mdReplaceTitles(titleId, true); } newAltTitleElement.remove(); }); newAltTitleElement.appendChild(removeCustomAltTitleElement); addAltTitleStar(newAltTitleElement); altTitlesSectionElement.appendChild(newAltTitleElement); }; altTitlesElements.forEach(addAltTitleStar); altTitlesSectionTextElement.textContent = `${altTitlesSectionTextElement.textContent} +` altTitlesSectionTextElement.classList.add('cursor-pointer'); altTitlesSectionTextElement.style.setProperty('width', 'fit-content'); altTitlesSectionTextElement.addEventListener('click', () => { const value = prompt('Enter new title'); if (!value) return; mdSetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles, value, false, true); createAltTitle(value); }); const storedAltTitles = mdGetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles); if (storedAltTitles) storedAltTitles.forEach(createAltTitle); const storedTitle = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_title); if (storedTitle && !mdTitleOptions.altTitle.storage.some(setTitleObject => setTitleObject.selected)) { mdSetTitleStorage(titleId, storage.mangadex.titles.data.alt_titles, storedTitle, false, true); createAltTitle(storedTitle); } return true; } function mdReplaceTitles(titleId, useDefaultTitle) { if (titleId) { const titlePageTitleElement = document.querySelector('div.title > p'); if (!titlePageTitleElement) return; const defaultTitleAttribute = `${userScriptId}-default-title`; if (!titlePageTitleElement.hasAttribute(defaultTitleAttribute)) titlePageTitleElement.setAttribute(defaultTitleAttribute, titlePageTitleElement.textContent); const defaultTitle = useDefaultTitle && titlePageTitleElement.getAttribute(defaultTitleAttribute); const storedMainTitle = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_title); titlePageTitleElement.textContent = defaultTitle || storedMainTitle || 'undefined'; return; } const titleLinkElements = document.querySelectorAll( 'a.title, a.chapter-feed__title, .dense-manga-container a, .swiper-slide a, .manga-draft-container a, a[class=""]' ); titleLinkElements.forEach(titleLinkElement => { const titleReplacedAttribute = `${userScriptId}-title-replaced`; if (titleLinkElement.hasAttribute(titleReplacedAttribute)) return; titleLinkElement.setAttribute(titleReplacedAttribute, 'true'); let textElement = titleLinkElement; const hasTextNode = () => textElement && textElement.childNodes && Array.from(textElement.childNodes).some(text => text.data); if (!hasTextNode()) textElement = titleLinkElement.querySelector('span, h6'); if (!hasTextNode() && titleLinkElement.parentElement) textElement = titleLinkElement.parentElement.querySelector('span, h2, div.font-bold'); if (!hasTextNode()) return; if (textElement.parentElement && textElement.parentElement.tagName === 'BUTTON') return; const mdTitleId = mdGetTitleId(titleLinkElement.getAttribute('href')); if (!mdTitleId) return; const storedMainTitle = mdGetTitleStorage(mdTitleId, storage.mangadex.titles.data.main_title); if (!storedMainTitle) return; textElement.childNodes.forEach((text) => { if (text.data) text.data = storedMainTitle; }); }); } function mdAddVolumeCoverOptions(titleId) { if (document.querySelector('div[role="alert"]')) return true; if (document.querySelectorAll(`a[href*="covers/${titleId}"]`).length < 2) return false; const volumeCoverLoadedAttribute = `${userScriptId}-volume-cover-loaded`; const volumeCoverLinkElements = document.querySelectorAll(`a[href*="covers/${titleId}"]:not([${volumeCoverLoadedAttribute}])`); if (!mdTitleOptions.volumeCover.storage) mdTitleOptions.volumeCover.storage = []; volumeCoverLinkElements.forEach(volumeCoverLinkElement => { volumeCoverLinkElement.setAttribute(volumeCoverLoadedAttribute, 'true'); const volumeSubtitleElement = volumeCoverLinkElement.querySelector('.subtitle'); if (!volumeSubtitleElement) return; volumeSubtitleElement.textContent = ` ${volumeSubtitleElement.textContent}`; const volumeCoverLink = volumeCoverLinkElement.getAttribute('href'); if (!volumeCoverLink) return; const volumeCoverFilename = mdGetCoverFileName(volumeCoverLink); if (!volumeCoverFilename.fileName) return; const storedVolumeCover = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_cover); const setCoverObject = { selected: volumeCoverFilename.fileName === storedVolumeCover, element: volumeCoverLinkElement, starElement: document.createElement('span'), value: volumeCoverFilename.fileName } setCoverObject.starElement.textContent = setCoverObject.selected ? '★' : '☆'; setCoverObject.starElement.classList.add('cursor-pointer'); setCoverObject.starElement.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); mdSetTitleStorage(titleId, storage.mangadex.titles.data.main_cover, setCoverObject.value, setCoverObject.selected); setCoverObject.selected = !setCoverObject.selected; mdTitleOptions.volumeCover.storage.forEach(_setCoverObject => { _setCoverObject.selected = _setCoverObject.value === setCoverObject.value && setCoverObject.selected; _setCoverObject.starElement.textContent = _setCoverObject.selected ? '★' : '☆'; }); mdReplaceVolumeCovers(titleId, !setCoverObject.selected); }); volumeSubtitleElement.prepend(setCoverObject.starElement); mdTitleOptions.volumeCover.storage.push(setCoverObject); }); return true; } function mdReplaceVolumeCovers(titleId, useDefault) { const coverLinkElement = document.querySelector(`.md-content > .manga a[href*="covers/${titleId}"]`); const replaceCoverUrl = (titleId, urlToReplace, storedCover) => { if (!titleId || !storedCover) return; const urlToReplaceFilename = mdGetCoverFileName(urlToReplace); if (!urlToReplaceFilename.size) urlToReplaceFilename.size = ''; const newUrl = `https://mangadex.org/covers/${titleId}/${storedCover}${urlToReplaceFilename.size}`; if (newUrl !== urlToReplace) return newUrl; } if (coverLinkElement) { const defaultCoverAttribute = `${userScriptId}-default-cover`; if (!coverLinkElement.hasAttribute(defaultCoverAttribute)) { const coverLinkFileName = mdGetCoverFileName(coverLinkElement.getAttribute('href')); if (coverLinkFileName.fileName) coverLinkElement.setAttribute(defaultCoverAttribute, coverLinkFileName.fileName); } const storedCover = mdGetTitleStorage(titleId, storage.mangadex.titles.data.main_cover); const defaultCover = useDefault && coverLinkElement.getAttribute(defaultCoverAttribute); const newCover = defaultCover || storedCover; if (newCover) { const newCoverLinkUrl = replaceCoverUrl(titleId, coverLinkElement.getAttribute('href'), newCover); if (newCoverLinkUrl) coverLinkElement.setAttribute('href', newCoverLinkUrl); const coverImageElement = coverLinkElement.querySelector(`img[src*="covers/${titleId}"]`); if (coverImageElement) { const newCoverImageUrl = replaceCoverUrl(titleId, coverImageElement.getAttribute('src'), newCover); if (newCoverImageUrl) coverImageElement.setAttribute('src', newCoverImageUrl); } const bannerImageElement = document.querySelector('.banner-image'); if (bannerImageElement) { const newBannerImageUrl = replaceCoverUrl(titleId, bannerImageElement.style.getPropertyValue('background-image'), newCover); if (newBannerImageUrl) bannerImageElement.style.setProperty('background-image', `url("${newBannerImageUrl}")`); } } } const coverLoadedAttribute = `${userScriptId}-cover-loaded`; const imageElements = document.querySelectorAll(`img:not([${coverLoadedAttribute}])`); imageElements.forEach(imageElement => { imageElement.setAttribute(coverLoadedAttribute, 'true'); const imageUrl = imageElement.getAttribute('src'); if (!imageUrl) return; const mdTitleId = mdGetTitleId(imageUrl); if (!mdTitleId || mdTitleId === titleId) return; const storedCover = mdGetTitleStorage(mdTitleId, storage.mangadex.titles.data.main_cover); const newCoverUrl = replaceCoverUrl(mdTitleId, imageUrl, storedCover); if (newCoverUrl) imageElement.setAttribute('src', newCoverUrl); }); } function komgaAutoMatch(seriesId) { if (!document.querySelector(`.v-image__image[style*="${seriesId}"]`)) return false; const linkElements = document.querySelectorAll(`a.v-chip--link`); if (linkElements < 1) return false; const sectionData = { id: 'local_links', name: 'Local Links' } const storedSections = getStorage(storage.mangadex.titles.custom_sections); if (!storedSections.some(section => section.id === sectionData.id)) { storedSections.push(sectionData); setStorage(storage.mangadex.titles.custom_sections, storedSections); } linkElements.forEach(link => { const mdTitleId = mdGetTitleId(link.href); if (!mdTitleId) return; const storedTitleSections = mdGetTitleStorage(mdTitleId, storage.mangadex.titles.data.custom_sections); let storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id); if (storedSectionIndex < 0) { storedTitleSections.push({ id: sectionData.id }); storedSectionIndex = storedTitleSections.findIndex(section => section.id === sectionData.id); } const sectionValues = storedTitleSections[storedSectionIndex].values || []; if (sectionValues.some(link => seriesId === komgaGetSeriesId(link))) return; const sectionLink = `[Komga](${window.location.href.replace(/\?.*$/, '')})`; sectionValues.push(sectionLink); storedTitleSections[storedSectionIndex].values = sectionValues; mdSetTitleStorage(mdTitleId, storage.mangadex.titles.data.custom_sections, storedTitleSections); }); return true; } function observeElement(onChange, element = document.body) { const observer = new MutationObserver(onChange); onChange(); observer.observe(element, { childList: true, subtree: true, }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址