您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Download gallery from waifubitches.com, ososedki.com
// ==UserScript== // @name waifubitches.com, ososedki.com Downloader // @description Download gallery from waifubitches.com, ososedki.com // @namespace chimichanga // @author chimichanga // @icon https://waifubitches.com/favicon.ico // @version 2.4 // @license MIT // @match https://waifubitches.com/*gallery/* // @match https://ososedki.com/*photos/* // @require https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.7.0.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.4/FileSaver.min.js // @noframes // @connect self // @connect userapi.com // @run-at document-idle // @grant GM_xmlhttpRequest // ==/UserScript== /* jshint esversion: 11 */ // based on the 8muses.com downloader script var downBtn; var downStatus; const State = { NOT_STARTED: Symbol('not_started'), DOWNLOADING: Symbol('downloading'), COMPRESSING: Symbol('compressing'), DONE: Symbol('done') }; var state = State.NOT_STARTED; var data; var zip = new JSZip(); var resultBlob; var completed = 0; var failed = 0; $(document).ready(function () { data = selectData(); if (data.sources.length == 0) { console.warn("No images found to download"); return; } console.info("download data:", data); downBtn = $(`<a class="btn btn-sm btn-warning"><i class="bi bi-download"></i> <span>DOWNLOAD (${data.sources.length})</span></a>`); downStatus = $(downBtn).find('span'); data.buttonParent.append(downBtn); $(downBtn).click(download); }); function selectData() { let mapping = { 'waifubitches.com': waifubitchesSelector, 'ososedki.com': osodekiSelector, }; return mapping[document.location.host](); } function waifubitchesSelector() { let sources = $('div.grid .grid-item a').map((_, { href }) => href).get(); let thumbnails = $('div.grid .grid-item img.img-fluid').map((_, { src }) => src).get(); return { sources: sources.map((s, i) => ( {url: s, thumb: thumbnails[i], name: s.split(/(?:\/(?:a|impg)\/|\?)/)[1]?.replaceAll('/', '-') })) .filter(s => s.url.includes('//waifubitches.com') || s.url.includes('userapi.com')), buttonParent: $('body > div:nth-child(1) > div.pb-2 > center > noindex'), zipName: window.location.href.split('/gallery/')[1] + '+' + $('body > div:nth-child(1) > h1').get(0).innerText + '.zip', }; } function osodekiSelector() { let sources = $('div.thumbs a').map((_, { href }) => href).get(); let thumbnails = $('div.thumbs a img').map((_, { src }) => src).get(); let data = { sources: sources.map((s, i) => ({url: s, thumb: thumbnails[i], name: s.split(/(?:\/(?:a|impg)\/|\?)/)[1]?.replaceAll('/', '-') })) .filter(s => s.url.includes('//ososedki.com') || s.url.includes('userapi.com')), buttonParent: $('body > div:nth-child(1) > div.pb-2 > center > noindex'), zipName: window.location.href.split('/photos/')[1] + '+' + $('body > div:nth-child(1) > h1').get(0).innerText + '.zip', }; return data; } function updateState(newState, progress) { state = newState; $(downBtn).toggleClass('btn-success', state == State.DONE); $(downBtn).toggleClass('btn-warning', state == State.NOT_STARTED || state == State.COMPRESSING || state == State.DOWNLOADING); $(downBtn).toggleClass('btn-danger', failed > 0); let messages = { [State.NOT_STARTED]: `DOWNLOAD`, [State.DOWNLOADING]: `DOWNLOADING`, [State.COMPRESSING]: `COMPRESSING`, [State.DONE]: `ZIP READY`, }; $(downStatus).html(messages[state] + (failed > 0 ? ` (${failed} failed)` : '') + (progress ? ` ${progress}` : '')); } function download() { if (state == State.DONE && failed == 0) { saveZip(); return; } if (state == State.DOWNLOADING || state == State.COMPRESSING) return; updateState(State.DOWNLOADING); completed = 0; failed = 0; Promise.allSettled( data.sources.map(({url, thumb, name}) => fetch(url).catch((cause) => { console.log(`can't fetch original image, ${cause}: ${url}`); return fetch(thumb); }).then(({ response, url }) => { completed++; updateState(State.DOWNLOADING, `${completed}/${data.sources.length}`); zip.file(name, response); }).catch((cause) => { console.log(`can't fetch thumbnail image, ${cause}: ${url}`); failed++; }))) .then(saveZip); } function fetch(url) { return new Promise((resolve, reject) => GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'arraybuffer', onload: ({ response, status }) => status == 200 ? resolve({ response: response, url: url }) : reject('missing'), onerror: () => reject('error'), onabort: () => reject('abort'), ontimeout: () => reject('timeout'), })); } function saveZip() { if (state == State.DONE) { saveAs(resultBlob, data.zipName); return; } zip.generateAsync( { type: 'blob' }, ({ percent }) => updateState(State.COMPRESSING, `${percent.toFixed(2)}%`) ).then(function (blob) { updateState(State.DONE, `${completed}/${data.sources.length}`); resultBlob = blob; saveAs(resultBlob, data.zipName); }); }