云听下载高音质版本

云听点播节目批量下载,实时显示下载进度,高低音质切换,文件名自动重命名,复制链接到剪贴板功能

// ==UserScript==
// @name         云听下载高音质版本
// @version      2.4
// @description  云听点播节目批量下载,实时显示下载进度,高低音质切换,文件名自动重命名,复制链接到剪贴板功能
// @author       darkduck9
// @match        https://www.radio.cn/*
// @grant        none
// @V2.4         修复:1.无法下载当日低音质音频,2.切换音质后“全选”复选框不选中。‘复制所有’调整为‘复制所选’
// @license      MIT
// @namespace http://example.com
// ==/UserScript==

(function() {
    'use strict';
 let submenuContainer;
    let isHighQuality = false;
  if (/channelname=\d+|name=\d+/.test(window.location.href)) {
        createSubmenu(); 

         function createSubmenu() {
             if (submenuContainer) {
                 document.body.removeChild(submenuContainer);
            }

            submenuContainer = document.createElement('div');
            submenuContainer.style.position = 'fixed';
            submenuContainer.style.top = '50%';
            submenuContainer.style.right = '1rem';
            submenuContainer.style.transform = 'translateY(-50%)';
            submenuContainer.style.backgroundColor = '#00000080';
            submenuContainer.style.padding = '10px';
            submenuContainer.style.zIndex = '9999';
            submenuContainer.style.borderRadius = '10px';

             const submitButton = document.createElement('button');
            submitButton.textContent = 'Submit';

            const refreshButton = document.createElement('button');
            refreshButton.textContent = '刷新';
            refreshButton.style.color = 'white';
            refreshButton.style.fontWeight = 'bold';
            refreshButton.style.marginRight = '5px';
            refreshButton.style.cursor = 'pointer';
            refreshButton.style.backgroundColor = '#00000080';refreshButton.style.border= 'none';
             const downloadButtonsContainer = document.createElement('div');
            downloadButtonsContainer.style.height = '60vh';
            downloadButtonsContainer.style.overflow = 'auto';
            downloadButtonsContainer.style.marginTop = '10px';

            const qualityToggleButton = document.createElement('button');
            setQualityButtonText(); // Set initial button text
             const copyLinksButton = document.createElement('button');
            copyLinksButton.textContent = '复制所选链接';
            copyLinksButton.style.color = 'white';
            copyLinksButton.style.fontWeight = 'bold';
            copyLinksButton.style.marginRight = '5px';
            copyLinksButton.style.cursor = 'pointer';
            copyLinksButton.style.backgroundColor = '#00000080';copyLinksButton.style.border= 'none';
             qualityToggleButton.addEventListener('click', function() {
                isHighQuality = !isHighQuality; 
                setQualityButtonText();
                const downloadUrls = getDownloadUrls();
                displayDownloadButtons(downloadUrls);
                 const selectAllCheckbox = document.querySelector('#select-all-checkbox');
    if (selectAllCheckbox && !selectAllCheckbox.checked) {
        selectAllCheckbox.checked = true;
        const event = new Event('change');
        selectAllCheckbox.dispatchEvent(event);
    }
            });

 function setQualityButtonText() {
  qualityToggleButton.textContent = isHighQuality ? '当前低音质' : '当前高音质';
            qualityToggleButton.style.color = 'white';
            qualityToggleButton.style.fontWeight = 'bold';
            qualityToggleButton.style.marginRight = '5px';
            qualityToggleButton.style.cursor = 'pointer';
            qualityToggleButton.style.backgroundColor = '#00000080';qualityToggleButton.style.border= 'none';
  if (isHighQuality) {
    qualityToggleButton.textContent = '当前低音质';
  } else {
    qualityToggleButton.textContent = '当前高音质';
  }
}
             submitButton.addEventListener('click', function() {
                const downloadUrls = getDownloadUrls();
                displayDownloadButtons(downloadUrls);
            });

             refreshButton.addEventListener('click', function() {
                const currentQuality = isHighQuality;
createSubmenu();
submitButton.click(); 
isHighQuality = currentQuality; 
setQualityButtonText(); 
            });

             copyLinksButton.addEventListener('click', function() {
    const checkboxes = downloadButtonsContainer.querySelectorAll('input[type="checkbox"]');
    const selectedUrls = [];
    checkboxes.forEach(checkbox => {
        if (checkbox.checked) {
            selectedUrls.push(checkbox.value);
        }
    });
    const links = selectedUrls.join('\n');
    copyToClipboard(links);
});
   // Create the progress bar
      const progressBar = document.createElement('div');
      progressBar.style.width = '0%';
      progressBar.style.height = '10px';
      progressBar.style.backgroundColor = '#E9E9E9';
      progressBar.style.marginTop = '5px';
      progressBar.className = 'progress-bar'; // Add a class name for easy selection

       const fileCountIndicator = document.createElement('div');
      fileCountIndicator.style.marginTop = '5px';
      fileCountIndicator.style.fontWeight = 'bold';
      fileCountIndicator.className = 'file-count-indicator'; // Add a class name for easy selection

       submenuContainer.appendChild(progressBar);
      submenuContainer.appendChild(fileCountIndicator);
const fileSizeIndicator = document.createElement('div');
fileSizeIndicator.style.marginTop = '5px';
fileSizeIndicator.style.fontWeight = 'bold';
fileSizeIndicator.className = 'file-size-indicator'; // Add a class name for easy selection

const indicatorsContainer = document.createElement('div');
indicatorsContainer.style.display = 'flex';
indicatorsContainer.style.alignItems = 'center';
indicatorsContainer.style.marginTop = '5px';
indicatorsContainer.appendChild(fileCountIndicator);
indicatorsContainer.appendChild(fileSizeIndicator);

 submenuContainer.appendChild(indicatorsContainer);

 function cleanURL(url) {
    try {
        const parsedUrl = new URL(url);
         parsedUrl.searchParams.delete('e');
        parsedUrl.searchParams.delete('ps');
        parsedUrl.searchParams.delete('r');
        return parsedUrl.toString();
    } catch (error) {
        console.error("URL无效:", error);
        return url; 
    }
}           // Function to get download URLs
           function getDownloadUrls() {
    const downloadUrls = [];
    const trElements = document.querySelectorAll('tr');
    trElements.forEach(tr => {
        const dateTd = tr.querySelector('td:first-child');
        const linkTd = tr.querySelector('td:nth-child(2)');
        const downloadLink = tr.querySelector('td:nth-child(3) a');
        const fileNameLink = linkTd ? linkTd.querySelector('a') : null;

        if (dateTd && downloadLink && fileNameLink) {
            const onclickValue = downloadLink.getAttribute('onclick');
            if (onclickValue) {
                const date = dateTd.textContent.trim();
                const url = isHighQuality ? cleanURL(getDownloadUrlFromDownLiveRecord(onclickValue)) : cleanURL(fileNameLink.getAttribute('data-url'));
                const fileName = getFileName(date, fileNameLink.textContent.trim());
                const downloadUrl = {
                    url: url,
                    fileName: fileName
                };
                downloadUrls.push(downloadUrl);
            }
        }
    });
    return downloadUrls;
}

            // Function to get the file name
            function getFileName(date, text) {
                const httpDateMatches = text.match(/\/(\d{8})\//);
                if (httpDateMatches && httpDateMatches.length > 1) {
                    const httpDate = httpDateMatches[1];
                    const formattedDate = formatDate(httpDate);
                    return formattedDate + '_' + text;
                }
                return date + '_' + text;
            }

            // Function to format HTTP date to 'YYYY/MM/DD' format
            function formatDate(httpDate) {
                const year = httpDate.substring(0, 4);
                const month = httpDate.substring(4, 6);
                const day = httpDate.substring(6, 8);
                return year + '/' + month + '/' + day;
            }

             function getDownloadUrlFromDownLiveRecord(onclickValue) {
    if (!onclickValue) return '';
    const matches = onclickValue.match(/downLiveRecord\('([^']+)'/);
    if (matches && matches.length > 1) {
        return matches[1];
    }
    return '';
}

            // Function to display download buttons
            function displayDownloadButtons(downloadUrls) {
                 downloadButtonsContainer.innerHTML = '';

                downloadUrls.forEach(downloadUrl => {
                if (!downloadUrl.url) {
               return;
              }
                     const checkbox = document.createElement('input');
                    checkbox.type = 'checkbox';
                    checkbox.value = downloadUrl.url;
                    checkbox.checked = true; 
                     const buttonElement = document.createElement('button');
                    buttonElement.textContent = downloadUrl.fileName;
                    buttonElement.style.backgroundColor = '#00000080';
                    buttonElement.style.color = 'white';buttonElement.style.border= 'none';
                    buttonElement.style.borderBottom = '2px solid #fff';
                    buttonElement.style.cursor = 'pointer';
                    buttonElement.addEventListener('click', function() {
                        downloadFile(downloadUrl.url, downloadUrl.fileName);
                    });

                    const container = document.createElement('div');
                    container.appendChild(checkbox);
                    container.appendChild(buttonElement);
                    container.style.borderRadius = '5px';
                    container.style.marginBottom = '5px';
                    downloadButtonsContainer.appendChild(container);
                });

                if (!submenuContainer.querySelector('#select-all-checkbox')) {
                     const selectAllCheckbox = document.createElement('input');
                    selectAllCheckbox.type = 'checkbox';
                    selectAllCheckbox.id = 'select-all-checkbox';
                    selectAllCheckbox.checked = true;
                     const selectAllLabel = document.createElement('label');
                    selectAllLabel.textContent = '全选';
                    selectAllLabel.style.color = 'white';
                    selectAllLabel.htmlFor = 'select-all-checkbox';
                    selectAllCheckbox.addEventListener('change', function() {
                        const checkboxes = downloadButtonsContainer.querySelectorAll('input[type="checkbox"]');
                        checkboxes.forEach(checkbox => {
                            checkbox.checked = selectAllCheckbox.checked;
                        });
                    });
                    submenuContainer.appendChild(selectAllCheckbox);
                    submenuContainer.appendChild(selectAllLabel);
                }

                 if (!submenuContainer.querySelector('#download-selected-button')) {
                    // Create the download selected button
                    const downloadSelectedButton = document.createElement('button');
                    downloadSelectedButton.textContent = '下载所选';
                    downloadSelectedButton.style.color = 'white';
            downloadSelectedButton.style.fontWeight = 'bold';
            downloadSelectedButton.style.marginRight = '5px';
            downloadSelectedButton.style.cursor = 'pointer';
            downloadSelectedButton.style.backgroundColor = '#00000080';
            downloadSelectedButton.style.border= 'none';
                    downloadSelectedButton.id = 'download-selected-button';
                    downloadSelectedButton.addEventListener('click', function() {
                       const checkboxes = downloadButtonsContainer.querySelectorAll('input[type="checkbox"]');
                        const selectedUrls = [];
                        checkboxes.forEach(checkbox => {
                            if (checkbox.checked) {
                                selectedUrls.push(checkbox.value);
                            }
                        });
                        downloadSelectedUrls(selectedUrls);
                    });
                    submenuContainer.appendChild(downloadButtonsContainer);
                    submenuContainer.appendChild(downloadSelectedButton);
                }
                submenuContainer.appendChild(copyLinksButton);
                submenuContainer.appendChild(qualityToggleButton);
            }
function updateProgress(percentComplete, currentFileSize, downloadSpeed) {
  const progressBar = submenuContainer.querySelector('.progress-bar');
  progressBar.style.width = percentComplete + '%';
 
 const fileSizeIndicator = submenuContainer.querySelector('.file-size-indicator');
fileSizeIndicator.textContent = `已下载: ${formatFileSize(event.loaded)} / 共: ${currentFileSize}`;
fileSizeIndicator.style.color = 'white';
}

  function updateFileCount(currentFile, totalFiles) {
    const fileCountIndicator = submenuContainer.querySelector('.file-count-indicator');
    fileCountIndicator.textContent = `${currentFile}/${totalFiles}`;
    fileCountIndicator.style.color = 'white';
  }

function downloadFile(url, fileName) {
  const a = document.createElement('a');
  a.style.display = 'none';
  a.href = url;

  const fileExtension = getFileExtension(url);
  // Modify the file name with the obtained file extension
  const fullFileName = fileName + '.' + fileExtension;
  a.setAttribute('download', fullFileName);
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}

 // Function to extract the file extension from the URL
function getFileExtension(url) {
  return url.split('.').pop();
}

function downloadSelectedUrls(urls) {
  const divs = document.getElementsByTagName('div');
  const totalFiles = urls.length;

  function getButtonInnerText(div) {
    const button = div.querySelector('button');
    if (button) {
      return button.textContent.trim();
    }
    return '';
  }

  function getCheckboxValue(div) {
    const checkbox = div.querySelector('input[type="checkbox"]');
    if (checkbox) {
      return checkbox.value;
    }
    return '';
  }

  function getDivContainingUrl(url) {
    for (let i = 0; i < divs.length; i++) {
      const div = divs[i];
      const checkbox = div.querySelector('input[type="checkbox"]');
      if (checkbox && checkbox.value === url) {
        return div;
      }
    }
    return null;
  }

  let index = 0;

  function downloadNext() {
    if (index < totalFiles) {
      const url = urls[index];
      const fileName = getFileNameFromUrl(url);
      const fileExtension = getFileExtension(url);
      const div = getDivContainingUrl(url);

      if (div) {
        const buttonText = getButtonInnerText(div);
        const checkboxValue = getCheckboxValue(div);
        const fullFileName = buttonText + '.' + fileExtension;

        if (checkboxValue === url) {
          const xhr = new XMLHttpRequest();
         const secureUrl = url.replace('http://', 'https://');
          xhr.open('GET', secureUrl);

          xhr.responseType = 'blob';

          // Add progress event listener
          xhr.addEventListener('progress', function(event) {
            if (event.lengthComputable) {
              const percentComplete = (event.loaded / event.total) * 100;
              const currentFileSize = formatFileSize(event.total);
              updateProgress(percentComplete,currentFileSize);
            }
          });

          xhr.onload = function() {
            if (xhr.status === 200) {
              const blob = new Blob([xhr.response]);
              const fileUrl = URL.createObjectURL(blob);
              const a = document.createElement('a');
              a.style.display = 'none';
              a.href = fileUrl;
              a.setAttribute('download', fullFileName);

              document.body.appendChild(a);
              a.click();
              document.body.removeChild(a);
              URL.revokeObjectURL(fileUrl);
              index++;

              updateFileCount(index, totalFiles);

              setTimeout(downloadNext, 1000); // Delay between downloads (1 second)
            }
          };

          xhr.send();
        } else {
          console.log('Checkbox value does not match URL:', url);
          index++;
          downloadNext();
        }
      } else {
        console.log('Div not found for URL:', url);
        index++;
        downloadNext();
      }
    }
  }

  downloadNext();
}
function formatFileSize(bytes) {
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  if (bytes === 0) return '0 Bytes';
  const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
  return `${(bytes / Math.pow(1024, i)).toFixed(1)} ${sizes[i]}`;
}

            function getFileNameFromUrl(url) {
                const matches = url.match(/\/([^/]+)$/);
                if (matches && matches.length > 1) {
                    return matches[1];
                }
                return '';
            }

            // Function to copy text to clipboard
            function copyToClipboard(text) {
                const textarea = document.createElement('textarea');
                textarea.value = text;
                document.body.appendChild(textarea);
                textarea.select();
                document.execCommand('copy');
                document.body.removeChild(textarea);
            }

            document.body.appendChild(submenuContainer);
            submenuContainer.appendChild(refreshButton);
        }

        submitButton.click();
       }
})();