Forces Toonily to load all manga images at once and dynamically loads them into a single page strip with a stats window.
// ==UserScript== // @name Toonily Manga Loader // @namespace github.com/longkidkoolstar // @version 1.1 // @description Forces Toonily to load all manga images at once and dynamically loads them into a single page strip with a stats window. // @author longkidkoolstar // @match https://toonily.com/webtoon/* // @require https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/js/all.min.js // @grant GM.setValue // @grant GM.getValue // @grant GM.deleteValue // @license MIT // ==/UserScript== (function() { 'use strict'; let imageUrls = []; let loadedImages = 0; let totalImages = 0; let reloadMode = false; // Function to simulate click on the "Load All Images" switch function enableLoadAllImages() { const loadAllImagesButton = document.querySelector('#btn-lazyload-controller'); if (loadAllImagesButton && loadAllImagesButton.querySelector('.fa-toggle-off')) { loadAllImagesButton.click(); // Simulate clicking to enable "Load All Images" console.log("Forcing 'Load All Images'..."); } } // Hooking into XMLHttpRequest to capture all image URLs from AJAX responses function interceptAjaxRequests() { const originalOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, ...args) { this.addEventListener('load', function() { if (url.includes('/chapters/manga')) { const parser = new DOMParser(); const doc = parser.parseFromString(this.responseText, 'text/html'); const imgs = doc.querySelectorAll('img.wp-manga-chapter-img'); imgs.forEach(img => { const imgUrl = img.src.trim(); if (!imageUrls.includes(imgUrl)) { imageUrls.push(imgUrl); } }); } }); return originalOpen.apply(this, [method, url, ...args]); }; } // Function to remove all other HTML elements except for the manga container function removeOtherElements() { const bodyChildren = Array.from(document.body.children); bodyChildren.forEach(child => { if (!child.id || child.id !== 'manga-container') { child.remove(); } }); } // Helper function to create a page container for each image function createPageContainer(pageUrl) { const container = document.createElement('div'); container.className = 'manga-page-container'; const img = document.createElement('img'); img.src = pageUrl; img.style.maxWidth = '100%'; img.style.display = 'block'; img.style.margin = '0px auto'; //Note: The 0px dictates the space between images and the auto center them img.onload = function() { loadedImages++; updateStats(); // Update the stats when an image is fully loaded }; addClickEventToImage(img); container.appendChild(img); return container; } // Function to extract already loaded image URLs from the page function extractImageUrlsFromPage() { const images = document.querySelectorAll('.reading-content img.wp-manga-chapter-img'); images.forEach(img => { const src = img.src.trim(); if (src.startsWith('https://cdn.toonily.com/chapters/manga') && !imageUrls.includes(src)) { imageUrls.push(src); } }); totalImages = imageUrls.length; } // Helper function to create an exit button function createExitButton() { const exitButton = document.createElement('button'); exitButton.textContent = 'Exit'; exitButton.style.backgroundColor = '#e74c3c'; exitButton.style.color = '#fff'; exitButton.style.border = 'none'; exitButton.style.padding = '10px'; exitButton.style.margin = '10px auto'; exitButton.style.display = 'block'; // Center the button exitButton.style.cursor = 'pointer'; exitButton.style.borderRadius = '5px'; exitButton.addEventListener('click', function() { window.location.reload(); // Reload the page when clicked }); return exitButton; } // Function to load all manga images into a single strip function loadMangaImages() { const mangaContainer = document.createElement('div'); mangaContainer.id = 'manga-container'; document.body.appendChild(mangaContainer); imageUrls.forEach((url, index) => { const pageContainer = createPageContainer(url); // Add exit button to the first loaded page if (index === 0) { const topExitButton = createExitButton(); mangaContainer.appendChild(topExitButton); } mangaContainer.appendChild(pageContainer); // Add exit button to the last loaded page if (index === imageUrls.length - 1) { const bottomExitButton = createExitButton(); mangaContainer.appendChild(bottomExitButton); } }); removeOtherElements(); // Remove all other page elements after loading } // Function to update the stats window function updateStats() { const statsPages = document.querySelector('.ml-stats-pages'); const loadingImages = document.querySelector('.ml-loading-images'); const totalImagesDisplay = document.querySelector('.ml-total-images'); if (statsPages) statsPages.textContent = `${loadedImages}/${totalImages} loaded`; if (loadingImages) loadingImages.textContent = `${totalImages - loadedImages} images loading`; if (totalImagesDisplay) totalImagesDisplay.textContent = `${totalImages} images in chapter`; } // Function to create the stats window async function createStatsWindow() { const statsWindow = document.createElement('div'); statsWindow.className = 'ml-stats'; statsWindow.style.position = 'fixed'; statsWindow.style.bottom = '10px'; statsWindow.style.right = '10px'; statsWindow.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; statsWindow.style.padding = '1px'; statsWindow.style.color = 'white'; statsWindow.style.borderRadius = '8px'; statsWindow.style.zIndex = '9999'; statsWindow.style.transition = 'opacity 0.3s'; statsWindow.style.opacity = '0.5'; statsWindow.addEventListener('mouseenter', function() { statsWindow.style.opacity = '1'; }); statsWindow.addEventListener('mouseleave', function() { statsWindow.style.opacity = '0.5'; }); const statsWrapper = document.createElement('div'); statsWrapper.style.display = 'flex'; statsWrapper.style.alignItems = 'center'; // Center vertically const collapseButton = document.createElement('span'); collapseButton.className = 'ml-stats-collapse'; collapseButton.title = 'Hide stats'; collapseButton.textContent = '>>'; collapseButton.style.cursor = 'pointer'; collapseButton.style.marginRight = '10px'; // Space between button and content collapseButton.addEventListener('click', async function() { contentContainer.style.display = contentContainer.style.display === 'none' ? 'block' : 'none'; collapseButton.textContent = contentContainer.style.display === 'none' ? '<<' : '>>'; await GM.setValue('statsCollapsed', contentContainer.style.display === 'none'); }); (async () => { const isCollapsed = await GM.getValue('statsCollapsed', false); contentContainer.style.display = isCollapsed ? 'none' : 'block'; collapseButton.textContent = isCollapsed ? '<<' : '>>'; })(); const contentContainer = document.createElement('div'); contentContainer.className = 'ml-stats-content'; const statsText = document.createElement('span'); statsText.className = 'ml-stats-pages'; statsText.textContent = `0/0 loaded`; // Initial stats const infoButton = document.createElement('i'); infoButton.innerHTML = '<i class="fas fa-question-circle"></i>'; infoButton.title = 'See userscript information and help'; infoButton.style.marginLeft = '5px'; infoButton.style.marginRight = '5px'; // Add space to the right infoButton.addEventListener('click', function() { alert('This userscript loads manga pages in a single view. Click on an image to reload.'); }); const moreStatsButton = document.createElement('i'); moreStatsButton.innerHTML = '<i class="fas fa-chart-pie"></i>'; moreStatsButton.title = 'See detailed page stats'; moreStatsButton.style.marginRight = '5px'; // Add space to the right moreStatsButton.addEventListener('click', function() { const statsBox = document.querySelector('.ml-floating-msg'); statsBox.style.display = statsBox.style.display === 'block' ? 'none' : 'block'; }); const refreshButton = document.createElement('i'); refreshButton.innerHTML = '<i class="fas fa-sync-alt"></i>'; refreshButton.title = 'Click an image to reload it.'; refreshButton.addEventListener('click', function() { reloadMode = !reloadMode; refreshButton.style.color = reloadMode ? 'orange' : ''; console.log(`Reload mode is now ${reloadMode ? 'enabled' : 'disabled'}.`); }); const miniExitButton = document.createElement('button'); miniExitButton.innerHTML = '<i class="fas fa-sign-out-alt"></i>'; miniExitButton.title = 'Exit the Manga Loader'; miniExitButton.style.marginLeft = '10px'; // Space between other buttons miniExitButton.style.backgroundColor = '#e74c3c'; // Red color for the button miniExitButton.style.color = '#fff'; miniExitButton.style.border = 'none'; miniExitButton.style.padding = '1px 5px'; miniExitButton.style.borderRadius = '5px'; miniExitButton.style.cursor = 'pointer'; miniExitButton.addEventListener('click', function() { window.location.reload(); // Refresh the page }); contentContainer.appendChild(statsText); contentContainer.appendChild(infoButton); contentContainer.appendChild(moreStatsButton); contentContainer.appendChild(refreshButton); contentContainer.appendChild(miniExitButton); // Add mini exit button to the content statsWrapper.appendChild(collapseButton); statsWrapper.appendChild(contentContainer); statsWindow.appendChild(statsWrapper); const statsBox = document.createElement('pre'); statsBox.className = 'ml-box ml-floating-msg'; statsBox.innerHTML = `<strong>Stats:</strong><br><span class="ml-loading-images">0 images loading</span><br><span class="ml-total-images">0 images in chapter</span><br><span class="ml-loaded-pages">0 pages parsed</span>`; statsBox.style.display = 'none'; // Initially hidden statsWindow.appendChild(statsBox); document.body.appendChild(statsWindow); } // Add the click event to images function addClickEventToImage(image) { image.addEventListener('click', function() { if (reloadMode) { const imgSrc = image.dataset.src || image.src; image.src = ''; // Clear the src to trigger reload setTimeout(() => { image.src = imgSrc; // Retry loading after clearing }, 100); // Short delay to ensure proper reload } }); } // Create and add the "Load Manga" button const loadMangaButton = document.createElement('button'); loadMangaButton.textContent = 'Load Manga'; loadMangaButton.style.position = 'fixed'; loadMangaButton.style.bottom = '10px'; loadMangaButton.style.right = '10px'; loadMangaButton.style.zIndex = '9999'; loadMangaButton.style.padding = '10px'; loadMangaButton.style.backgroundColor = '#f39c12'; loadMangaButton.style.border = 'none'; loadMangaButton.style.borderRadius = '5px'; loadMangaButton.style.cursor = 'pointer'; // Add hover effect to the button loadMangaButton.addEventListener('mouseover', function() { loadMangaButton.style.backgroundColor = '#ff5500'; }); loadMangaButton.addEventListener('mouseout', function() { loadMangaButton.style.backgroundColor = '#f39c12'; }); const mangaInfo = document.querySelector("body > div.wrap > div > div > div > div.profile-manga.summary-layout-1 > div > div > div > div.tab-summary"); if (!mangaInfo) { document.body.appendChild(loadMangaButton); loadMangaButton.addEventListener('click', async function() { enableLoadAllImages(); // Force the site to load all images interceptAjaxRequests(); // Hook into AJAX requests to capture image URLs extractImageUrlsFromPage(); // Initially extract image URLs from the page loadMangaImages(); // Load all collected images loadMangaButton.remove(); // Remove the button after loading await createStatsWindow(); // Create the stats window }); } // Wait for the DOM to finish loading before adding the button window.addEventListener('DOMContentLoaded', function() { if (!mangaInfo) { document.body.appendChild(loadMangaButton); } }); })();