/*globals imgur, Imgur, album*/ // ==UserScript== // @name Imgur Album Ops // @namespace nImgur // @version 1.4.1 // @description Adds a few questionably useful management functions to imgur albums. // @author noccu // @match *://imgur.com/a/* // @match *://*.imgur.com // @run-at document-idle // @noframes // @grant none // @downloadURL https://update.greasyfork.icu/scripts/415755/Imgur%20Album%20Ops.user.js // @updateURL https://update.greasyfork.icu/scripts/415755/Imgur%20Album%20Ops.meta.js // ==/UserScript== var ALBUMS, ALBUM_LAST_UPDATE = 0; var OBSERVER; var SELECTED = [], //Array of selected image IDs CURRENT_ALBUM, TARGET_ALBUM, COUNTER; var DEBUG = true; function load() { return new Promise((r, x) => { if (ALBUMS) { //Already loaded, early term. r(); return; } ALBUMS = JSON.parse(localStorage.getItem("albums")); ALBUM_LAST_UPDATE = localStorage.getItem("lastUpdate") || 0; if (!ALBUMS || Date.now() - ALBUM_LAST_UPDATE > 604800000) { //7 days console.log("Updating albums."); updateAlbums().then(r, x); } else { r(); } }); } function save() { localStorage.setItem("albums", JSON.stringify(ALBUMS)); localStorage.setItem("lastUpdate", ALBUM_LAST_UPDATE); } function createXhr(method, url, load, error) { var xhr = new XMLHttpRequest(); xhr.responseType = "json"; xhr.withCredentials = true; xhr.addEventListener("load", load, {once:true, passive:true}); xhr.addEventListener("error", e => error(e), {once:true, passive:true}); xhr.open(method, url); xhr.setRequestHeader("Authorization", "Client-ID "+imgur._.apiClientId); return xhr; } function updateAlbums() { function req (r, x) { function complete(e) { if (e.target.response.success) { debugLog(e); parse(e.target.response); r(); } else { console.error(e.target.response); x(e.target.response.status); } } function parse(resp) { ALBUMS = resp.data.reduce((a, v) => {a.push({name: v.title || "Untitled", id: v.id, count: v.images_count, views: v.views, order: v.order}); return a;}, []); ALBUMS.sort((a, b) => a.order - b.order); ALBUM_LAST_UPDATE = Date.now(); save(); } let xhr = createXhr("GET", "https://api.imgur.com/3/account/me/albums?perPage=100", complete, x); xhr.send(); } return new Promise(req); } function findAlbum(id) { return ALBUMS.find(x => x.id == id); } function addToAlbum() { function req(r, x) { function complete(e) { if (e.target.response.success) { let ta = findAlbum(TARGET_ALBUM), dropdown = document.querySelector(`#albOpsAlbumSel option[value='${TARGET_ALBUM}']`); ta.count += SELECTED.length; dropdown.textContent = `${ta.name} (${ta.count} images)`; // populateAlbumList(); //update counts save(); r(); } else { console.error(e.target.response); x(e.target.response.status); } } let xhr = createXhr("POST", `https://api.imgur.com/3/album/${TARGET_ALBUM}/add`, complete, x); var form = new FormData(); for (let id of SELECTED) { form.append("ids[]", id); } xhr.send(form); } return new Promise(req); } function moveToAlbum() { return addToAlbum() .then(removeFromAlbum, function() {console.error("Error adding to album."); return;}); } function removeFromAlbum () { function req (r, x) { function complete(e) { if (e.target.response.success) { let ca = findAlbum(CURRENT_ALBUM), dropdown = document.querySelector(`#albOpsAlbumSel option[value='${CURRENT_ALBUM}']`); ca.count -= SELECTED.length; dropdown.textContent = `${ca.name} (${ca.count} images)`; save(); r(); } else { console.error(e.target.response); x(e.target.response.status); } } let xhr = createXhr("DELETE", `https://api.imgur.com/3/album/${CURRENT_ALBUM}/remove_images?ids=${SELECTED.join()}`, complete, x); xhr.send(); } function removeFromView() { //Also update Imgur's JS data, we use it sometimes and yknow, it's just polite. let imgs = Imgur.Environment.image.album_images.images; function findIdx (id) { return imgs.findIndex(x => x.hash == id); } for (let id of SELECTED) { let img = document.getElementById(id), next = img.nextElementSibling; img.remove(); if (next.nodeName == "LABEL") { next.remove(); } let idx = findIdx(id); if (idx != -1) { imgs.splice(idx, 1); } } } let upd = new Promise(req); return upd.then(removeFromView); } function clearAlbum() { SELECTED = Imgur.Environment.image.album_images.images.reduce((acc, cur) => { acc.push(cur.hash); return acc; }, []); removeFromAlbum() .then( function(){ document.querySelectorAll(".post-images label[for='add-between']") .forEach(el => el.remove()); SELECTED = []; findAlbum(CURRENT_ALBUM).count = 0; save(); } ); } function injectCSS() { if (document.getElementById("albOps-style")) { return; } var css = document.createElement("style"); css.id = "albOps-style"; css.innerHTML = `.post-image-container { position: relative; } input.albOpsMark { position: absolute; top: 0; left: 0; } .albOpsUI { margin-top: 1em; } .albOpsUI ul { padding: 0; } .albOpsHeader { background-color: #1bb76e; padding: 0.3em; text-align: center; } .danger { color: rgb(208, 2, 98); } #albOpsAlbumSel { width: 100% } .albOps-newViews { color: #1bb76e; } /* Maximizing re-order dialogue space */ /* TODO: Fix dragging span .post-grid-image { display: inline-block; transform: unset !important; margin: 3px; } .upload-global-post { width: 100%; } .upload-global-post #right-content { display: inline-block; max-width: 15%; min-width: 75px; } .upload-global-post .post-pad { display: inline-block; max-width: 85%; min-width: 50%; }*/`; document.head.append(css); } function createCheckbox (id) { var el = document.createElement("input"); el.type = "checkbox"; el.className = "albOpsMark"; el.value = id; return el; } function addSelectors () { var images = document.querySelectorAll("div.post-image-container"); for (let img of images) { addSelector(img); } } function addSelector(el) { let checkbox = createCheckbox(el.id); el.appendChild(checkbox); return checkbox; } function handleChange(mList) { //Deals with the UI adding and removing images from the DOM upon scrolling, keeps our selection checkboxes intact. function isIdInAlbum(id) { return Boolean(Imgur.Environment.image.album_images.images.find(x => x.hash == id)); } function process(node) { if (node.className == "post-image-container") { if (!node.id) { console.warn("No ID on", node); return; } if (node.getElementsByClassName("albOpsMark").length > 0) { debugInfo("Already processed", node); return; } let box = addSelector(node); if (SELECTED.includes(node.id)) { box.checked = true; } debugInfo(`Added selector for img ${node.id}`); if (!isIdInAlbum(node.id)) { //Dealing with NEW images added through the site interface. //Update Imgur JS (an attempt was made) cause it doesn't seem to update on its own(TODO: check) and I sometimes use it. Imgur.Environment.image.album_images.images.push({hash: node.id}); CURRENT_ALBUM.count++; save(); } } } for (let m of mList) { if (m.type == "childList" && m.addedNodes.length > 0) { for (let node of m.addedNodes) { process(node); } } else if (m.type == "attributes" && m.attributeName == "id") { process(m.target); } } } function clearSelected() { SELECTED = []; let marks = document.body.getElementsByClassName("albOpsMark"); for (let el of marks) { el.checked = false; } COUNTER.textContent = "0"; } function handleAction (e) { if (e.target.classList.contains("extra-option")) { switch(e.target.dataset.action) { case "move": moveToAlbum() .then(clearSelected); break; case "copy": addToAlbum() .then(clearSelected); break; case "upd": updateAlbums() .then(populateAlbumList); console.log("Manual album update triggered."); break; case "clear": clearAlbum(); } } } function injectUI() { injectCSS(); addSelectors(); let sidebar = document.querySelector("#post-options > div:first-child"); sidebar.insertAdjacentHTML("beforeend", `