您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download post images and save as a ZIP file.
当前为
// ==UserScript== // @name SaveAsZip for Patreon // @name:ja SaveAsZip for Patreon // @name::zh-cn SaveAsZip for Patreon // @name::zh-tw SaveAsZip for Patreon // @description Download post images and save as a ZIP file. // @description:ja 投稿の画像をZIPファイルとして保存する。 // @description:zh-cn 一键下载帖子内所有图片,并保存为ZIP文件。 // @description:zh-tw 一鍵下載帖子内所有圖片,並保存為ZIP文件。 // @version 1.00 // @namespace none // @match https://*.patreon.com/* // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js // @grant none // @license MIT // ==/UserScript== /* jshint esversion: 8 */ let observer; let is_post_page = location.pathname.indexOf('/posts/') == 0; let is_post_list = location.pathname.indexOf('/posts') > 0; let is_user_home = document.body.id == 'creator_membership'; addStyle(); addSaveButton(); function addSaveButton() { if (is_post_page) findCardsIn(document); else if (is_post_list || is_user_home) { observer = new MutationObserver(() => findContainer()); observer.observe(document.body, {childList: true, subtree: true}); } } function findContainer() { let container = document.querySelector('div[data-tag="post-stream-container"] ul, div[data-tag="creator-public-page-recent-posts"]'); if (container) { observer.disconnect(); findCardsIn(container); if (is_post_list) { //on load more posts const ul_observer = ul => { new MutationObserver(ms => ms.forEach(m => { if (m.addedNodes.length) findCardsIn(m.addedNodes[0]); })).observe(ul, {childList: true, subtree: false}); }; ul_observer(container); //on change post list new MutationObserver(ms => ms.forEach(m => { if (m.addedNodes.length && m.addedNodes[0].tagName == 'UL') { findCardsIn(m.addedNodes[0]); ul_observer(m.addedNodes[0]); } })).observe(container.parentNode, {childList: true, subtree: false}); } } } function findCardsIn(container) { let post_cards = container.querySelectorAll('div[data-tag="post-card"], div[data-tag="post"]'); post_cards.forEach(post_card => { let is_visible = !post_card.querySelector('a[href^="/join/"]'); let has_images = !post_card.querySelector('div[overflow="visible"]>span:first-child') && !post_card.querySelector(':scope>div>a>div:first-child'); if (is_visible && has_images) addSaveButtonTo(post_card); }); } function addSaveButtonTo(post_card) { let btn_save = document.createElement('div'); btn_save.classList.add('saveaszip'); btn_save.innerHTML = '<label><span class="save-icon">📥</span> <small class="save-text">ZIP</small></label>'; btn_save.onclick = () => savePost(btn_save, post_card); post_card.insertBefore(btn_save, post_card.firstChild); } async function savePost(btn_save, post_card) { if (btn_save.classList.contains('busy')) return; else btn_save.classList.add('busy'); let btn_text = btn_save.querySelector('.save-text'); const status = text => (btn_text.innerText = text); //get post json let post = window.patreon.bootstrap.post; //post page if (!post) { let post_href = post_card.querySelector('a[href^="/posts/"]').href; let post_page = await (await fetch(post_href)).text(); post_page = post_page.split('Object.assign(window.patreon.bootstrap, ')[1].split(');\n Object.assign')[0]; post = JSON.parse(post_page).post; } //extract post info let invalid_chars = {'\\': '\', '/': '/', '|': '|', '<': '<', '>': '>', ':': ':', '*': '*', '?': '?', '"': '"'}; let post_id = post.data.id; let post_title = post.data.attributes.title.replace(/[\/|<>:*?"\u200d]/g, v => invalid_chars[v] || ''); let user_id = post.included.find(i => i.type == 'user').id; let user_name = post.included.find(i => i.type == 'campaign').attributes.name.replace(/[\/|<>:*?"\u200d]/g, v => invalid_chars[v] || ''); let user_name_full = post.included.find(i => i.type == 'user').attributes.full_name.replace(/[\/|<>:*?"\u200d]/g, v => invalid_chars[v] || ''); let created = formatDate(post.data.attributes.created_at, 'YYYY-MM-DD'); //create zip and set filename let save_zip = new JSZip(); let save_zip_name = `patreon_${user_name}_${user_id}_${post_id}_${post_title}_images.zip`; //find medias let medias = post.included.filter(i => i.type == 'media'); let image_order = post.data.attributes.post_metadata.image_order; for (let i=0; i<medias.length; i++) { status(`${i+1} / ${medias.length}`); //download image and add to zip let order = ('000' + (image_order ? image_order.indexOf(medias[i].id) + 1 : i + 1)).slice(-3); let blob = await (await fetch(medias[i].attributes.download_url)).blob(); save_zip.file(`${order}_${medias[i].id}_${medias[i].attributes.file_name}`, blob); } //save zip status('Save'); let blob = await save_zip.generateAsync({type: 'blob'}); let blob_url = URL.createObjectURL(blob); //GM_download has some bug in tampermonkey, browser will freeze few second each download //GM_download({url: blob_url, name: save2zip_name, onload: () => URL.revokeObjectURL(blob_url)}); let link = document.createElement('a'); link.href = blob_url; link.download = save_zip_name; link.dispatchEvent(new MouseEvent('click')); setTimeout(() => URL.revokeObjectURL(blob_url), 5000); //done btn_save.classList.remove('busy'); btn_save.classList.add('done'); status('Done'); } function formatDate(i, o) { let d = new Date(i); let v = { YYYY: d.getUTCFullYear().toString(), MM: d.getUTCMonth() + 1, DD: d.getUTCDate(), hh: d.getUTCHours(), mm: d.getUTCMinutes() }; return o.replace(/(YYYY|MM|DD|hh|mm)/g, n => ('0' + v[n]).substr(-n.length)); } function addStyle() { let css = ` .saveaszip {position: absolute; z-index: 1; padding: 10px;} .saveaszip > label {background: #eee; border: 1px solid #ccc; border-radius: 8px; padding: 0 8px;} .saveaszip:hover > label {background: #fff;} .saveaszip.busy > label {background: #ffc;} .saveaszip.done > label {background: #cfc;} `; document.head.insertAdjacentHTML('beforeend', `<style>${css}</style>`); }