Download video and bulk images from myreadingmanga manga/doujin page.
目前為
// ==UserScript==
// @name MRM Downloader
// @namespace https://nyt92.eu.org
// @version 2024-12-08
// @description Download video and bulk images from myreadingmanga manga/doujin page.
// @author nyt92
// @match https://myreadingmanga.info/*
// @exclude https://myreadingmanga.info/about/
// @exclude https://myreadingmanga.info/cats/*
// @exclude https://myreadingmanga.info/pairing/*
// @exclude https://myreadingmanga.info/group/*
// @exclude https://myreadingmanga.info/privacy-policy/
// @exclude https://myreadingmanga.info/dmca-notice/
// @exclude https://myreadingmanga.info/contact/
// @exclude https://myreadingmanga.info/terms-service/
// @exclude https://myreadingmanga.info/sitemap/
// @exclude https://myreadingmanga.info/my-bookmark/
// @exclude https://myreadingmanga.info/tag/*
// @exclude https://myreadingmanga.info/genre/*
// @exclude https://myreadingmanga.info/status/*
// @exclude https://myreadingmanga.info/lang/*
// @exclude https://myreadingmanga.info/yaoi-manga/*
// @exclude https://myreadingmanga.info/manhwa/*
// @connect myreadingmanga.info
// @connect i1.myreadingmanga.info
// @connect i2.myreadingmanga.info
// @connect i3.myreadingmanga.info
// @connect i4.myreadingmanga.info
// @connect i5.myreadingmanga.info
// @connect i6.myreadingmanga.info
// @supportURL https://gist.github.com/NYT92/1247c6421ad95bfc2a0b10d912fb56f0
// @icon https://www.google.com/s2/favicons?sz=64&domain=myreadingmanga.info
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @grant GM_cookie
// @grant GM_xmlhttpRequest
// @license GPLv3
// ==/UserScript==
// THIS USERSCRIPT IS LICENSE UNDER THE GPLv3 License
(function() {
'use strict';
const title = document.querySelector('.entry-header h1.entry-title')?.textContent.trim() || 'Untitled';
const imageDlBtn = document.createElement('button');
imageDlBtn.id = 'downloadImagesBtn';
imageDlBtn.textContent = 'Download Images (.zip)';
imageDlBtn.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 9999;';
const videoDlBtn = document.createElement('button');
videoDlBtn.id = 'downloadVideoBtn';
videoDlBtn.textContent = 'Download Video';
videoDlBtn.style.cssText = 'position: fixed; top: 10px; right: 10px; z-index: 9999;';
const progressBar = document.createElement('div');
progressBar.id = 'downloadProgress';
progressBar.style.cssText = 'position: fixed; top:80px; right: 10px; width: 235px; right: 10px; height: 20px; background-color: #f0f0f0; display: none; z-index: 9999;';
const progressInner = document.createElement('div');
progressInner.style.cssText = 'width: 0%; height: 100%; background-color: #4CAF50; transition: width 0.5s;';
progressBar.appendChild(progressInner);
const progressText = document.createElement('div');
progressText.style.cssText = 'position: fixed; top: 105px; right: 10px; z-index: 9999; display: none;';
progressText.textContent = 'Preparing download...';
const checkIfVid = document.querySelector(".entry-categories a");
if (!checkIfVid) {
console.log("no media found!")
} else if (checkIfVid.textContent.includes("Video")) {
document.body.appendChild(videoDlBtn);
} else {
document.body.appendChild(imageDlBtn);
}
document.body.appendChild(progressBar);
document.body.appendChild(progressText);
imageDlBtn.addEventListener('click', function() {
imageDlBtn.disabled = true;
imageDlBtn.textContent = 'Downloading...';
const images = document.querySelectorAll('.img-myreadingmanga');
let imageSources = [];
imageSources = Array.from(images).map(img => img.dataset.src).filter(Boolean);
if (imageSources.length === 0) {
imageSources = Array.from(images).map(img => img.src).filter(Boolean);
}
if (imageSources.length === 0) {
const nestedImages = document.querySelectorAll('.img-myreadingmanga img');
imageSources = Array.from(nestedImages).map(img => img.src || img.dataset.src).filter(Boolean);
}
if (imageSources.length === 0) {
alert("No images found on this page. Check the console for debugging information.");
imageDlBtn.disabled = false;
imageDlBtn.textContent = 'Download Images (.zip)';
return;
}
const pageElement = document.querySelector('.post-page-numbers.current');
const page = pageElement ? pageElement.textContent.trim() : '1';
const zip = new JSZip();
progressBar.style.display = 'block';
progressText.style.display = 'block';
progressInner.style.width = '0%';
function getExtensionFromMimeType(mimeType) {
const mimeToExt = {
'image/jpeg': 'jpg',
'image/png': 'png',
'image/gif': 'gif',
'image/webp': 'webp',
'image/jpg': 'jpg'
};
return mimeToExt[mimeType.toLowerCase()] || 'jpg';
}
function addImageToZip(src, index) {
return new Promise((resolve, reject) => {
progressText.textContent = `Downloading image ${index + 1} of ${imageSources.length}...`;
GM_xmlhttpRequest({
method: "GET",
url: src,
responseType: "arraybuffer",
onload: function(response) {
try {
const arrayBuffer = response.response;
const byteArray = new Uint8Array(arrayBuffer);
let mimeType = 'image/jpeg';
try {
const contentTypeMatch = response.responseHeaders.match(/Content-Type:\s*(\S+)/i);
if (contentTypeMatch && contentTypeMatch[1]) {
mimeType = contentTypeMatch[1];
}
} catch (headerError) {
console.warn(`Could not parse Content-Type header for ${src}:`, headerError);
}
const blob = new Blob([byteArray], {type: mimeType});
const ext = getExtensionFromMimeType(blob.type);
const fileName = `image_${index + 1}.${ext}`;
zip.file(fileName, blob, {binary: true});
console.log(`Added ${fileName} to ZIP (${blob.size} bytes, type: ${blob.type})`);
const progress = ((index + 1) / imageSources.length) * 100;
progressInner.style.width = `${progress}%`;
resolve();
} catch (error) {
console.error(`Error processing ${src}:`, error);
reject(error);
}
},
onerror: function(error) {
console.error(`Error fetching ${src}:`, error);
reject(error);
}
});
});
}
Promise.all(imageSources.map(addImageToZip))
.then(() => {
progressText.textContent = 'Creating ZIP file...';
return zip.generateAsync({type: "blob"});
})
.then(function(content) {
const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
const fileName = `${safeTitle}_ch${page}.zip`;
saveAs(content, fileName);
console.log("ZIP file saved successfully");
progressBar.style.display = 'none';
progressText.style.display = 'none';
imageDlBtn.disabled = false;
imageDlBtn.textContent = 'Download Images (.zip)';
})
.catch(error => {
console.error("Error creating ZIP file:", error);
alert("An error occurred while creating the ZIP file. Please check the console for details.");
progressBar.style.display = 'none';
progressText.style.display = 'none';
imageDlBtn.disabled = false;
imageDlBtn.textContent = 'Download Images (.zip)';
});
});
videoDlBtn.addEventListener('click', function() {
videoDlBtn.disabled = true;
videoDlBtn.textContent = 'Downloading...';
const videoElement = document.querySelector("#MRM_video > video > source");
if (!videoElement) {
alert("No video found on this page.");
videoDlBtn.disabled = false;
videoDlBtn.textContent = 'Download Video';
return;
}
const videoSrc = videoElement.src;
if (!videoSrc) {
alert("Unable to find video source.");
videoDlBtn.disabled = false;
videoDlBtn.textContent = 'Download Video';
return;
}
progressBar.style.display = 'block';
progressText.style.display = 'block';
progressText.textContent = 'Starting video download...';
progressInner.style.width = '0%';
GM_xmlhttpRequest({
method: "GET",
url: videoSrc,
responseType: "arraybuffer",
onprogress: function(progress) {
if (progress.lengthComputable) {
const percentComplete = (progress.loaded / progress.total) * 100;
progressInner.style.width = percentComplete + '%';
const downloadedMB = (progress.loaded / (1024 * 1024)).toFixed(2);
const totalMB = (progress.total / (1024 * 1024)).toFixed(2);
progressText.textContent = `Downloaded: ${downloadedMB}MB / ${totalMB}MB`;
}
},
onload: function(response) {
const blob = new Blob([response.response], {type: 'video/mp4'});
const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
const fileName = `${safeTitle}.mp4`;
saveAs(blob, fileName);
console.log("Video downloaded successfully");
progressBar.style.display = 'none';
progressText.style.display = 'none';
videoDlBtn.disabled = false;
videoDlBtn.textContent = 'Download Video';
},
onerror: function(error) {
console.error("Error downloading video:", error);
alert("An error occurred while downloading the video. Please check the console for details.");
progressBar.style.display = 'none';
progressText.style.display = 'none';
videoDlBtn.disabled = false;
videoDlBtn.textContent = 'Download Video';
}
});
})
})();