// ==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.1.2 // @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 = `

Pixeldrain configuration

` 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'); // Load configuration values from script's local storage or use default values let config = JSON.parse(GM_getValue("pixeldrain_cfg") || defaultConfig); // Updates user interface elements with current configuration values 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, file) { const fileLink = url + file.dataset.id const fileType = file.dataset.type const fileTypeShort = fileType.split("/")[0] if (!["video", "audio", "image"].includes(fileTypeShort)) { const directDL = document.createElement('a') directDL.href = fileLink directDL.download = "" directDL.click() return } const modalContainer = document.createElement('div') modalContainer.className = "modal-container" const modalFileContainer = document.createElement('div') modalFileContainer.className = "modal-file-container" let tag; if (fileTypeShort === "video") { const tagType = document.createElement('video') tagType.controls = true tagType.autoplay = true //tagType.style.cssText = "max-width: 80%; max-height: 80%; box-shadow: 8px 8px 3px rgba(0 0 0 / 0.2); border-radius: 5px;" tagType.src = fileLink tag = tagType } if (fileTypeShort === "audio") { const tagType = document.createElement('audio') tagType.controls = true tagType.autoplay = true tagType.setAttribute('playsinline', "") //tagType.style.cssText = "width: 80%; box-shadow: 8px 8px 3px rgba(0 0 0 / 0.2); border-radius: 5px;" const source = document.createElement('source') source.src = fileLink source.type = fileType tagType.appendChild(source) tag = tagType } if (fileTypeShort === "image") { const tagType = document.createElement('img') //tagType.style.cssText = "max-width: 80%; max-height: 80%; box-shadow: 8px 8px 3px rgba(0 0 0 / 0.2); border-radius: 5px;" tagType.src = fileLink tag = tagType } const downloadBtn = document.createElement('a') downloadBtn.className = "download-file-btn" downloadBtn.download = "" downloadBtn.innerText = "Download" downloadBtn.href = fileLink downloadBtn.insertAdjacentHTML('afterbegin', downloadIcon) modalContainer.addEventListener('click', () => modalContainer.remove()) modalFileContainer.appendChild(tag) modalContainer.appendChild(modalFileContainer) modalFileContainer.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) }) //------------------------------------------------------------------------------------------ const sidebar = document.querySelector('.toolbar') const albumID = URL.split('/').pop() // fectch the API to obtain the file size, type 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 files = document.querySelectorAll('.gallery > .file') //console.log(filesInfosArrayFromAPI) files.forEach((file, i) => { const fileTitle = file.textContent const { size: fileSize, id: fileID, mime_type: fileType } = filesInfosArrayFromAPI[i] file.setAttribute("data-size", fileSize) file.setAttribute("data-id", fileID) file.setAttribute("data-type", fileType) if (viewing_file_method === "2") file.setAttribute("target", "_blank") if (show_file_size) file.insertAdjacentHTML('beforeend', `

${formatFileSize(fileSize)}

`) }) // add button to sidebar sortByFileSize(files, 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; } .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-file-container { position: absolute; width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center; -webkit-animation: scale-up-center 1s both !important; animation: scale-up-center 1s both !important; } .modal-file-container video, .modal-file-container img, .modal-file-container audio { max-width: 80%; max-height: 80%; box-shadow: 8px 8px 3px rgba(0 0 0 / 0.2); border-radius: 5px; } .modal-file-container audio { max-width: unset !important; max-height: unset !important; width: 80%; } .download-file-btn { position: relative; 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; margin-top: 15px; } .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); } .download-file-btn:hover svg { filter: invert(42%) sepia(90%) saturate(1352%) hue-rotate(190deg) brightness(85%) contrast(110%); } @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) } })()