// ==UserScript== // @name pixeldrain improved (sort by size, markers and modal) // @namespace Violentmonkey Scripts // @match https://pixeldrain.com/l/* // @match https://pixeldrain.com/api/file/* // @version 1.0 // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @author dummy_mole // @license GNU GPLv3 // @description Improves the user experience on pixeldrain by adding a sort by size button, markers on viewed files and file playback in a modal window. // @downloadURL none // ==/UserScript== (async () => { 'use strict' //-------------------------------script config panel part---------------------------------- const defaultConfig = { "show_file_size": true, "seen_file": false, "viewing_file_method": "1" } function createConfigPanel() { // DOM creation const configPanel = `
` document.body.insertAdjacentHTML('afterbegin', configPanel) //--------------------------------------------------------------------- function slideOutAndRemove(element, classOut, classIn) { element.classList.remove(classOut) element.classList.add(classIn) setTimeout(() => element.remove(), 1000) } const configPanelContainer = document.querySelector('.config-panel-container'); const showFileSizeCheckbox = document.querySelector('#show-filesize'); const seenFileCheckbox = document.querySelector('#seen-file'); const viewedFileMethodSelect = document.querySelector('#file-viewing-method'); const saveBtn = document.querySelector('.save-btn'); const resetBtn = document.querySelector('.reset-btn'); const cancelBtn = document.querySelector('.cancel-btn'); // Charge les valeurs de configuration depuis le local storage ou utilise les valeurs par défaut let config = JSON.parse(GM_getValue("pixeldrain_cfg") || defaultConfig); // Met à jour les éléments de l'interface utilisateur avec les valeurs de configuration actuelles showFileSizeCheckbox.checked = config.show_file_size; seenFileCheckbox.checked = config.seen_file; viewedFileMethodSelect.value = config.viewing_file_method; // Updates configuration when user interface elements change showFileSizeCheckbox.onchange = function () { config.show_file_size = this.checked; }; seenFileCheckbox.onchange = function () { config.seen_file = this.checked; }; viewedFileMethodSelect.onchange = function () { config.viewing_file_method = this.value; }; // Saves configuration to script's local storage when user clicks "Save" saveBtn.onclick = function () { GM_setValue("pixeldrain_cfg", JSON.stringify(config)); location.reload() }; // Resets the configuration to default values when the user clicks on "Reset". resetBtn.onclick = function () { config = defaultConfig; showFileSizeCheckbox.checked = defaultConfig.show_file_size; seenFileCheckbox.checked = defaultConfig.seen_file; viewedFileMethodSelect.value = defaultConfig.viewing_file_method; GM_setValue("pixeldrain_cfg", JSON.stringify(defaultConfig)) location.reload() }; // Cancels changes made to the configuration when the user clicks on "Cancel". cancelBtn.onclick = function () { slideOutAndRemove(configPanelContainer, "slide-in-top", "slide-out-top"); }; //--------------------config panel css style--------------------------- const configPanelStyle = ` .config-panel-container { position: fixed !important; top: 10px; right: 10px; z-index: 10001; display: flex; flex-direction: column; min-width: 250px !important; max-width: 320px !important; background: linear-gradient(0deg, rgba(20, 22, 23, 1) 0%, rgba(27, 27, 28, 0.9) 50%, rgba(33, 34, 36, 0.9) 100%) !important; box-shadow: 6px 6px 5px #00000069 !important; padding: 5px 15px !important; border: 1px solid #282828 !important; border-radius: 5px !important; font-family: inherit; backdrop-filter: blur(15px); width: 265px; } .config-panel-title { margin: 10px auto; padding-bottom: 5px; font-size: 18px; color: #159cff !important; font-size: 20px !important; font-weight: 600; } .panel { color: #aaa !important; } .seen-file-container, .show-filesize-container { margin: 10px auto; } .watch-file-method { margin: 10px auto; } .separator { display: block; width: 98%; height: 1px; background: #fdfdfd0d; } .validation-buton-container { display: flex; justify-content: space-evenly; margin: 10px auto; } .validation-buton-container > button { display: block; font-size: 16px !important; color: whitesmoke !important; background: #0087ff; border: transparent !important; border-radius: 3px !important; height: 30px !important; padding: 0 10px !important; width: 66px; box-shadow: unset !important; } #file-viewing-method { background: #2d2d2d !important; border-radius: 5px !important; border: 1px solid #424242 !important; width: 70px !important; height: 25px !important; text-align: center !important; padding: unset !important; box-shadow: unset !important; color: #aaa !important; } #file-viewing-method > option { background: #2d2d2d !important; color: #aaa !important; } .cancel-btn { background: #4a4949 !important; } /*----------animations------------*/ .slide-in-top { -webkit-animation: slide-in-top 0.9s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; animation: slide-in-top 0.9s cubic-bezier(0.250, 0.460, 0.450, 0.940) both; } .slide-out-top { -webkit-animation: slide-out-top 0.9s cubic-bezier(0.550, 0.085, 0.680, 0.530) both; animation: slide-out-top 0.9s cubic-bezier(0.550, 0.085, 0.680, 0.530) both; } @-webkit-keyframes slide-in-top { 0% { -webkit-transform: translateY(-1000px); transform: translateY(-1000px); opacity: 0; } 75% { -webkit-transform: translateY(20px); transform: translateY(20px); } 100% { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } } @keyframes slide-in-top { 0% { -webkit-transform: translateY(-1000px); transform: translateY(-1000px); opacity: 0; } 75% { -webkit-transform: translateY(20px); transform: translateY(20px); } 100% { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } } @-webkit-keyframes slide-out-top { 0% { -webkit-transform: translateY(0); transform: translateY(0); opacity: 1; } 25% { -webkit-transform: translateY(20px); transform: translateY(20px); } 100% { -webkit-transform: translateY(-1000px); transform: translateY(-1000px); opacity: 0; } } @keyframes slide-out-top { 0% { -webkit-transform: translateY(0); transform: translateY(0); } 25% { -webkit-transform: translateY(20px); transform: translateY(20px); } 100% { -webkit-transform: translateY(-1000px); transform: translateY(-1000px); } } ` GM_addStyle(configPanelStyle) } //----------------------------------------------------------------------------------------- const URL = location.href const API = 'https://pixeldrain.com/api/list/' const descendingIcon = `` const ascendingIcon = `` const downloadIcon = `` //------------------------------------------------------------------------------------------ if (/\/l\/(?![^\/]*item).*$/.test(URL)) { GM_registerMenuCommand("Pixeldrain configuration", createConfigPanel) if (!GM_getValue("pixeldrain_cfg")) GM_setValue("pixeldrain_cfg", JSON.stringify(defaultConfig)) const { show_file_size, seen_file, viewing_file_method } = JSON.parse(GM_getValue("pixeldrain_cfg")) function formatFileSize(bytes) { const units = ['o', 'Ko', 'Mo', 'Go', 'To', 'Po', 'Eo', 'Zo', 'Yo']; const exponent = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1) const size = (bytes / Math.pow(1024, exponent)).toFixed(2) return `${size} ${units[exponent]}` } //----------------------------sorting function (by size)---------------------------------- const sortByFileSize = (nodeList, targetContainer) => { const array = [...nodeList]; const compare = (a, b) => +a.dataset.size - +b.dataset.size const sortButton = document.createElement('button') sortButton.className = "toolbar_button my-button" sortButton.innerText = "Sort files" const sortIconContainer = document.createElement('div') sortIconContainer.className = "sort-icon-container" sortIconContainer.insertAdjacentHTML("afterbegin", ascendingIcon) sortButton.prepend(sortIconContainer) let reverse = false sortButton.addEventListener('click', () => { reverse = !reverse; array.sort(compare); if (reverse) { array.reverse(); } sortIconContainer.innerHTML = ""; sortIconContainer.insertAdjacentHTML("afterbegin", reverse ? descendingIcon : ascendingIcon) const parentNode = nodeList[0].parentNode parentNode.innerHTML = '' array.forEach(element => parentNode.appendChild(element)) }); targetContainer.appendChild(sortButton) } //------------------------------------------------------------------------------------------ const body = document.body const fileAPIUrl = 'https://pixeldrain.com/api/file/' // modal function function modal(url, filename) { const fileLink = url + filename const modalContainer = document.createElement('div') const modalIframeContainer = document.createElement('div') const iframe = document.createElement('iframe') modalContainer.className = "modal-container" modalIframeContainer.className = "modal-iframe-container" iframe.setAttribute('class', "modal-iframe") iframe.setAttribute('frameborder', 0) iframe.setAttribute('allowfullscreen', "") iframe.src = fileLink const downloadBtn = document.createElement('a') downloadBtn.className = "download-file-btn" downloadBtn.download = "" downloadBtn.innerText = "download" downloadBtn.href = fileLink downloadBtn.insertAdjacentHTML('beforeend', downloadIcon) modalContainer.addEventListener('click', () => modalContainer.remove()) modalIframeContainer.appendChild(iframe) modalContainer.appendChild(modalIframeContainer) modalContainer.appendChild(downloadBtn) body.appendChild(modalContainer) } // event delegation to manage actions on clicked files const gallery = document.querySelector('.gallery') gallery.addEventListener('click', e => { if (viewing_file_method === "3") e.preventDefault() const fileLink = e.target.closest('a.file') if (!fileLink) return if (seen_file && viewing_file_method !== "1") fileLink.classList.add('seen') if (viewing_file_method === "3") modal(fileAPIUrl, fileLink.dataset.id) }) //------------------------------------------------------------------------------------------ const sidebar = document.querySelector('.toolbar') const albumID = URL.split('/').pop() // fectch the API to obtain the file size and identifier, allowing us to sort files by size and view them directly in the modal window if the option is selected try { const res = await fetch(`${API}${albumID}`) const { files: filesInfosArrayFromAPI } = await res.json() const videos = document.querySelectorAll('.gallery > .file') //console.log(filesInfosArrayFromAPI) videos.forEach((video, i) => { const videoTitle = video.textContent const { size: videoSize, id: fileID } = filesInfosArrayFromAPI[i] video.setAttribute("data-size", videoSize) video.setAttribute("data-id", fileID) if (viewing_file_method === "2") video.setAttribute("target", "_blank") if (show_file_size) video.insertAdjacentHTML('beforeend', `${formatFileSize(videoSize)}
`) }) // add button to sidebar sortByFileSize(videos, sidebar) } catch (error) { console.error(error) } //------------------------------------------CSS Style------------------------------------------- const pixeldrainStyle = ` .my-button { width: calc(100% - 4px); padding: 6px 7px; } .sort-icon-container { display: flex; } .my-button svg { margin-right: 2px; filter: invert(0.18); pointer-events: none; } a.file { position: relative; height: unset !important; display: flex !important; flex-direction: column; justify-content: space-between; padding: 0 10px; } .filesize { } .seen { border: solid 3px dodgerblue; } /*----------modal----------------*/ .modal-container { display: flex !important; flex-direction: column; justify-content: center !important; align-items: center !important; position: fixed !important; top: 0px !important; left: 0px !important; z-index: 9999 !important; width: 100% !important; height: 100% !important; background: rgb(0 0 0 / 90%) !important; animation: fadeIn 0.3s ease-in !important; backdrop-filter: blur(5px) !important; font-family: system-ui !important; } .modal-iframe-container { position: relative !important; min-width: 470px !important; min-height: 310px !important; width: 70% !important; /* REGLER CETTE VALEUR POUR LE BON DIMENSIONNEMENT DE L'IFRAME*/ margin-bottom: 0px !important; padding-top: 40% !important; /* REGLER CETTE VALEUR POUR LE BON DIMENSIONNEMENT DE L'IFRAME*/ /*box-shadow: 8px 8px 5px rgb(0 0 0 / 40%) !important;*/ -webkit-animation: scale-up-center 1s both !important; animation: scale-up-center 1s both !important; } .modal-iframe { position: absolute !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; } .download-file-btn { position: relative; bottom: -10px; z-index: 10000; background: #0f0f0f; text-decoration: none; color: #595959; font-size: 18px; font-weight: 600; padding: 2px 15px; box-shadow: 0px 3px 3px rgba(0, 0, 0, 0.6); border: solid 1px #131313; border-radius: 3px; transition: background 0.2s ease; } .download-file-btn:hover { background: #151515; color: #8c8c8c; } .download-file-btn:active { margin-top: 2px; background: #0f0f0f; } .download-file-btn svg { pointer-events: none; filter: invert(0.35); } @keyframes scale-up-center { 0% { -webkit-transform: scale(0); transform: scale(0); } 100% { -webkit-transform: scale(1); transform: scale(1); } } @keyframes fadeIn { 0% { -webkit-opacity: 0; opacity: 0; } 100% { -webkit-opacity: 1; opacity: 1; } } ` GM_addStyle(pixeldrainStyle) } // adds a css style to a subdomain embedded in an iframe (useful for styling the modal and its behavior when the window is resized) if (window.self !== window.top && /api\/file\//.test(location.href)) { const iframeContentStyle = ` /*-------------sub domain------------*/ body { overflow: hidden; margin: 0px !important; height: 100vh; width: auto; text-align: center; } video { width: 100%; height: 100%; box-shadow: 8px 8px 5px rgb(0 0 0 / 40%) !important; border-radius: 5px; background: black; } ` GM_addStyle(iframeContentStyle) } })()