Enhancements for Avistaz
// ==UserScript== // @name Avistaz+ // @version 1.0 // @description Enhancements for Avistaz // @author Mio. // @namespace https://github.com/dear-clouds/mio-userscripts // @supportURL https://github.com/dear-clouds/mio-userscripts/issues // @icon https://www.google.com/s2/favicons?sz=64&domain=avistaz.to // @license GPL-3.0 // @match *://*.avistaz.to/* // @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 none // ==/UserScript== (function() { 'use strict'; const username = document.querySelector("a[href*='/profile/']").getAttribute('href').split('/').pop(); const STORAGE_KEY_FULL_SCAN_COMPLETED = `avistaz+_${username}_full_scan_completed`; let stopFetching = false; // Function to simulate a click on the "Thank Uploader" button function clickThankButton(torrentId) { const thankButton = document.querySelector(`button#btn-torrent-thank[data-id='${torrentId}']`); if (thankButton) { thankButton.click(); console.log("Thanked uploader for torrent ID: " + torrentId); } } // Function to delete full scan completed state from localStorage function resetFullScanState() { localStorage.removeItem(STORAGE_KEY_FULL_SCAN_COMPLETED); console.log("Full scan state has been reset. You can now retry a full scan."); } // Add a click event listener to each download link document.querySelectorAll("a.btn.btn-xs.btn-primary").forEach(downloadLink => { downloadLink.addEventListener("click", function(event) { const torrentIdMatch = this.href.match(/\/download\/torrent\/(\d+)/); if (torrentIdMatch && torrentIdMatch[1]) { const torrentId = torrentIdMatch[1]; // Set a small timeout to ensure the "Thank Uploader" button is available setTimeout(() => { clickThankButton(torrentId); }, 500); } }); }); // Function to bypass anon.to redirects function bypassAnonRedirects() { const allLinks = document.querySelectorAll('a'); allLinks.forEach(link => { if (link.href.startsWith('https://anon.to/?')) { const realURL = link.href.split('?')[1]; if (realURL) { link.href = decodeURIComponent(realURL); if (window.location.href.startsWith('https://anon.to/')) { window.location.href = decodeURIComponent(realURL); } } } }); } bypassAnonRedirects(); // Function to gather all Hit & Run entries from history pages async function collectHitAndRuns() { let currentPage = 1; const collectedHitAndRuns = []; let isFullScan = false; const fullScanCompleted = localStorage.getItem(STORAGE_KEY_FULL_SCAN_COMPLETED) === 'true'; let totalPages = null; const noHnrMessage = document.querySelector('.table-responsive h3'); if (noHnrMessage) { noHnrMessage.innerHTML = '<div class="loading-icon"><i class="fa fa-spinner fa-spin"></i> Searching for Hit & Run torrents... <strong>(01/??)</strong></div>'; } else { console.log("No 'No Hit & Run Torrents' message found, skipping Hit & Run collection."); return; } function updateProgress(current, total) { const loadingIcon = document.querySelector('.loading-icon'); if (loadingIcon) { loadingIcon.innerHTML = `<i class="fa fa-spinner fa-spin"></i> Searching for Hit & Run torrents... <strong>(${current}/${total})</strong>`; } } async function fetchPage(pageNumber) { if (stopFetching) { console.log(`Skipping page ${pageNumber} as stop condition was met.`); return; } console.log(`Fetching page ${pageNumber}`); try { const response = await fetch(`https://avistaz.to/profile/${username}/history?page=${pageNumber}`); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const hitAndRunRows = doc.querySelectorAll('tr'); // Get total number of pages from pagination element if (totalPages === null) { const pagination = doc.querySelector('ul.pagination'); if (pagination) { const pageNumbers = Array.from(pagination.querySelectorAll('a[href*="?page="]')) .map(link => parseInt(link.textContent.trim(), 10)) .filter(page => !isNaN(page)); if (pageNumbers.length > 0) { totalPages = Math.max(...pageNumbers); console.log(`Total pages detected: ${totalPages}`); } } if (!totalPages) { totalPages = 1; // Fallback if pagination is missing } } for (const row of hitAndRunRows) { if (stopFetching) { console.log(`Stopping processing of rows on page ${pageNumber} because stop condition was met.`); return; } // Specifically select the "Added" column to find the date const addedDateCell = row.querySelectorAll('td')[8]; if (addedDateCell) { const addedDateElement = addedDateCell.querySelector('span[data-toggle="tooltip"]'); if (addedDateElement) { const addedDateText = addedDateElement.textContent.trim().toLowerCase(); const originalTitleText = addedDateElement.getAttribute('data-original-title')?.toLowerCase().trim() || ""; // console.log(`Checking date text: "${addedDateText}" and data-original-title: "${originalTitleText}" on page: ${pageNumber}`); // Check if either the text content or the attribute contains "1 month" if (fullScanCompleted && (addedDateText.includes("1 month") || originalTitleText.includes("1 month"))) { stopFetching = true; console.log(`Stopping fetch, found torrent added over a month ago on page: ${pageNumber}`); removeLoadingIcon(); break; } } } if (row.classList.contains('danger')) { collectedHitAndRuns.push(row.outerHTML); updateHitAndRunTable(row); } } if (!fullScanCompleted) { isFullScan = true; // Indicate this is a full scan } updateProgress(pageNumber, totalPages); currentPage++; } catch (err) { console.error(`Error fetching history page ${pageNumber}: `, err); } } // Fetch pages in parallel but with proper stopping control async function fetchNextPages(startPage, pagesToFetch = 5) { if (stopFetching) { removeLoadingIcon(); return; } const fetchPromises = []; for (let i = 0; i < pagesToFetch; i++) { if (startPage + i <= (totalPages ?? 1) && !stopFetching) { fetchPromises.push(fetchPage(startPage + i)); } } await Promise.all(fetchPromises); if (!stopFetching && currentPage <= totalPages) { console.log(`Finished batch, continuing from page ${currentPage}`); await fetchNextPages(currentPage, pagesToFetch); } else { if (isFullScan && !stopFetching) { console.log("Reached the last page, updating cache as full scan completed."); localStorage.setItem(STORAGE_KEY_FULL_SCAN_COMPLETED, 'true'); } removeLoadingIcon(); } } console.log(`Starting Hit & Run collection from page ${currentPage}`); await fetchNextPages(currentPage, 5); } // Function to update Hit & Run entries as they are found function updateHitAndRunTable(hitAndRunRow) { const tableContainer = document.querySelector('.table-responsive'); if (tableContainer) { // Check if we need to replace the "No Hit & Run Torrents" message const noHnrMessage = tableContainer.querySelector('h3'); if (noHnrMessage) { noHnrMessage.remove(); } // If the table doesn't already exist, create it let table = tableContainer.querySelector('table'); if (!table) { let tableHtml = '<table class="table table-bordered"><thead><tr><th>Type</th><th>Torrent</th><th>Download</th><th>Seeding</th><th>Completed</th><th>Uploaded</th><th>Downloaded</th><th>Ratio</th><th>Added</th><th>Updated</th><th>Seed Time</th><th>Hit & Run</th></tr></thead><tbody></tbody></table>'; tableContainer.insertAdjacentHTML('beforeend', tableHtml); } const tableBody = tableContainer.querySelector('tbody'); if (tableBody) { const newHitAndRunRow = document.createElement('tr'); newHitAndRunRow.className = hitAndRunRow.className; newHitAndRunRow.innerHTML = hitAndRunRow.innerHTML; // Ensure the button triggers the original modal using PopAlert.warning const clearButton = newHitAndRunRow.querySelector('.btn_clear_hitnrun'); if (clearButton) { clearButton.addEventListener('click', function(event) { event.preventDefault(); // Fetch the necessary data for PopAlert const id = clearButton.getAttribute('data-id'); const hnrPoints = clearButton.getAttribute('data-hnr'); if (typeof PopAlert !== 'undefined' && PopAlert.warning) { PopAlert.warning( `Clear this H&R for ${hnrPoints} BP?`, `Are you sure, you want to clear this Hit & Run for ${hnrPoints} Bonus Points?`, BASEURL + `/profile/${username}/hnr/clear`, { id: id, action: "clear_hnr" } ); } else { console.error("PopAlert is not defined. Cannot trigger modal."); } }); if (typeof $ !== 'undefined' && $.fn.tooltip) { $(clearButton).tooltip(); } const dataTitle = clearButton.getAttribute('data-title'); if (dataTitle) { clearButton.setAttribute('title', dataTitle); } } tableBody.appendChild(newHitAndRunRow); console.log(`Added a new Hit & Run entry to the table.`); } // Keep the loading message visible below the table while fetching if (!document.querySelector('.loading-icon')) { tableContainer.insertAdjacentHTML('beforeend', '<div class="loading-icon"><i class="fa fa-spinner fa-spin"></i> Searching for Hit & Run torrents... <strong>(${current}/${total})</strong> <small>(This will take a while the first time it runs. Just keep the tab open.)</small></div>'); } } } // Function to remove the loading icon function removeLoadingIcon() { const loadingIcon = document.querySelector('.loading-icon'); if (loadingIcon) { loadingIcon.remove(); console.log("Removed loading icon after completing history search."); } } // Automatically collect Hit & Runs when navigating to the H&R page if (window.location.href.includes(`/profile/${username}/history?hnr=1`)) { const noHnrMessage = document.querySelector('.table-responsive h3'); if (noHnrMessage && noHnrMessage.textContent.includes('No Hit & Run Torrents')) { console.log("Detected H&R history page with no existing H&R, starting Hit & Run collection."); collectHitAndRuns(); } else { console.log("No 'No Hit & Run Torrents' message found, skipping Hit & Run collection."); } } else if (window.location.href.includes(`/torrent`)) { // Function to calculate the seeding time based on file size function seedingTimeInHours(sizeGB) { if (sizeGB <= 1) { return 72; // Minimum seeding time for sizes less than or equal to 1GB } else if (sizeGB < 50) { return 72 + 2 * sizeGB; // For sizes between 1GB and 50GB } else { return 100 * Math.log(sizeGB) - 219.2023; // For sizes greater than or equal to 50GB } } // Function to format hours into days, hours, and minutes function formatTime(hours) { const totalMinutes = Math.round(hours * 60); const days = Math.floor(totalMinutes / (24 * 60)); const remainingMinutes = totalMinutes % (24 * 60); const hoursPart = Math.floor(remainingMinutes / 60); const minutesPart = remainingMinutes % 60; return `${days} days, ${hoursPart} hours, ${minutesPart} minutes`; } // Get the file size element from the page const fileSizeRow = document.evaluate( "//tr[td/strong[text()='File Size']]", document, null, XPathR###lt.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; if (fileSizeRow) { const sizeText = fileSizeRow.children[1].textContent.trim(); const sizeGB = parseFloat(sizeText.split(" ")[0]); if (!isNaN(sizeGB)) { // Calculate the required seeding time in hours const seedingTimeHours = seedingTimeInHours(sizeGB); // Format the seeding time into days, hours, and minutes const formattedTime = formatTime(seedingTimeHours); const seedingTimeContainer = document.createElement("span"); seedingTimeContainer.style.fontWeight = "bold"; seedingTimeContainer.style.marginLeft = "10px"; const staticText = document.createElement("span"); staticText.textContent = "(You need to seed this for "; // Have the formatted time with the class `text-green` const timeSpan = document.createElement("span"); timeSpan.className = "text-green"; timeSpan.textContent = formattedTime; const closingText = document.createElement("span"); closingText.textContent = ")"; seedingTimeContainer.appendChild(staticText); seedingTimeContainer.appendChild(timeSpan); seedingTimeContainer.appendChild(closingText); // Append the seeding time next to the file size fileSizeRow.children[1].appendChild(seedingTimeContainer); } } } // Adding manual reset functionality window.resetFullScanState = resetFullScanState; })();