您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Help you to download onlyfans' media
当前为
// ==UserScript== // @name Onlyfans downloader // @namespace http://tampermonkey.net/ // @version 0.1 // @description Help you to download onlyfans' media // @author Unknownman // @match https://onlyfans.com/* // @icon https://static.onlyfans.com/theme/onlyfans/spa/icons/favicon-notification-32x32.png // @license GNU GPLv3 // @grant GM_download // ==/UserScript== (function (){ const parentId = createRandomStr(); const statusId = createRandomStr(); const scrollToEndCheckboxId = createRandomStr(); const videoIncludeCheckboxId = createRandomStr(); const imageIncludeCheckboxId = createRandomStr(); const downloadButtonId = createRandomStr(); const closeButtonId = createRandomStr(); const forceStartDownloadButtonId = createRandomStr(); const listWrapperSelector = '#content > div.l-wrapper > div.l-wrapper__content > div.b-feed-content.g-sides-gaps > div.b-photos.g-negative-sides-gaps'; const scrollToEndMaxRetryTimes = 20; const sleepMs = 500; let isForceStartToDownload = false; let hasSetProxy = false; let data = { videos: 0, images: 0, collectedVideos: 0, collectedImages: 0, downloadedVideos: 0, downloadedImages: 0, }; const dataIds = { videos: createRandomStr(), images: createRandomStr(), collectedVideos: createRandomStr(), collectedImages: createRandomStr(), downloadedVideos: createRandomStr(), downloadedImages: createRandomStr(), }; const needToDownloadVideosUrls = []; const needToDownloadImagesUrls = []; function createRandomNumber(min = 5, max = 30) { return Math.floor(Math.random() * (max - min + 1) + min) } function createRandomStr(len = createRandomNumber()) { let result = ''; const string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'; for (let i = 0; i < len; i++) { result += string[createRandomNumber(0, string.length)]; } return result; } const sleep = ms => new Promise(r => setTimeout(r, ms)); /** * create a div * @param {string} element * @param {string} id * @param {string} className * @param {HTMLElement} parentDom if set, the created div will append to the parentDom * @param {object} style * @param {object} attributes */ function createElement(element, id = '', className = '', parentDom = undefined, style= undefined , attributes = undefined) { const el = document.createElement(element); id && el.setAttribute('id', id); className && el.setAttribute('class', className); if (attributes) { Object.entries(attributes).forEach(([key, value]) =>{ el.setAttribute(key, value); }); } if (style) { Object.entries(style).forEach(([key, value]) =>{ el.style[key] = value; }); } if (parentDom) { parentDom.appendChild(el); } return el; } /** * get a element * @param {string} id * @returns {HTMLElement|null} */ function getElement(id) { return document.getElementById(id); } /** * create or find a div * @param {string} element * @param {string} id * @param {[string, HTMLElement, object, object]} args */ function createOrGetElement(element, id, ...args) { return getElement(id) || createElement(element, id, ...args); } /** * remove a element * @param {string} id */ function remoteElement(id) { const el = document.getElementById(id); if (el) { el.parentElement.removeChild(el); } } /** * create an element and set its innerHTML * @param {string|number} text * @param {[string, string, string, HTMLElement, object, object]} args * @returns {HTMLElement} */ function createElementAndSetInnerHtml(text, ...args) { const el = createElement(...args); el.innerHTML = text; return el; } /** * create a button * @param {string} id * @param {string} text * @param {HTMLElement} parentEl * @param {string} appendClassName * @returns {HTMLElement} */ function createButton(id, text, parentEl = undefined, appendClassName) { const button = createElement('button', id, 'm-rounded g-btn' + (appendClassName ? ` ${appendClassName}` : ''), parentEl); button.innerHTML = text; return button; } /** * create a button with wrapper * @param {string} id * @param {string} text * @param {HTMLElement} parentEl * @param {string} buttonAppendClassName * @returns {HTMLElement} the button element */ function createButtonWithWrapper(id, text, parentEl = undefined, buttonAppendClassName = '') { const div = createElement('div', '', 'b-offer-wrapper', parentEl); return createButton(id, text, div, buttonAppendClassName); } /** * create a label with checkbox inside * @param {string} id * @param {boolean} checked * @param {string} labelText * @param {string} description * @param {HTMLElement} parentNode * @returns {HTMLElement} */ function createCheckboxWithLabel(id, checked, labelText, description, parentNode) { const label = createElement('label', '', 'b-users-lists__item__label m-collection-item b-input-radio__container'); label.innerHTML = `<input id="${id}" type="checkbox" class="b-input-radio m-only-by-class"> <label for="${id}" class="b-input-radio__label"> <svg class="g-icon" aria-hidden="true"> <use xlink:href="#icon-done" href="#icon-done"></use> </svg> <span class="b-input-ripple"></span> </label> <div class="b-users-lists__item m-with-rectangle-hover m-tb-sm m-border-full-width"> <div class="b-users-lists__item__text"> <div class="b-users-lists__item__name m-size-sm-text"> ${labelText}</div> <div class="b-users-lists__item__count"> ${description}</div> </div> </div>`; parentNode.appendChild(label); /** * @type {HTMLInputElement|null} */ const input = getElement(id); input.addEventListener('change', (e) => { if (e.target.checked) { input.classList.add('m-checked'); } else { input.classList.remove('m-checked'); } }); if (checked) { input.click(); } } /** * check if checkbox checked * @param id * @returns {boolean} */ function isCheckboxChecked(id) { /** * @type {HTMLInputElement} */ const checkbox = getElement(id); return checkbox.checked; } /** * create an item block * @param {string} title * @param {HTMLElement} parentEl * @param {string} contentClassName * @param {string} wrapperAppendClassName * @returns {HTMLElement} */ function createItemBlock(title, parentEl, contentClassName = 'b-offer-join', wrapperAppendClassName = '') { const block = createElement('li', '', `b-profile__content__item${wrapperAppendClassName ? ' ' + wrapperAppendClassName : ''}`, parentEl); const listOffers = createElement('div', '', 'list-offers m-offer-bottom-gap-reset', block); const wrapper = createElement('div', '', 'b-offer-wrapper m-start-campaign m-started-campaign', listOffers); const titleDiv = createElement('div', '', 'b-profile__content__item__title', wrapper); const linkDiv = createElement('div', '', 'b-profile__bio__link', titleDiv); linkDiv.innerHTML = title; return createElement('div', '', contentClassName, wrapper); } function createStatusBlock(parentEl) { const content = createItemBlock('Status', parentEl); const line1 = createElement('span', '', 'b-users__item__subscription-date', content); createElementAndSetInnerHtml('', 'span', statusId, 'b-users__item__subscription-date__label', line1); setCurrentStatus('Waiting to start'); } function setCurrentStatus(text) { getElement(statusId).innerText = text; } /** * create check block * @param parentEl */ function createCheckBlock(parentEl) { const content = createItemBlock('Settings', parentEl, 'b-offer-join', 'b-users-lists m-collections-list m-native-custom-scrollbar m-scrollbar-y m-invisible-scrollbar'); createCheckboxWithLabel(scrollToEndCheckboxId, true, 'Scroll To End', 'Scroll to End to load all media', content); createCheckboxWithLabel(videoIncludeCheckboxId, true, 'Include Video', 'Download all videos', content); createCheckboxWithLabel(imageIncludeCheckboxId, true,'Include Image', 'Download all images', content); } function setInfoBlock(parentEl) { const content = createItemBlock('Information', parentEl); const line1 = createElement('span', '', 'b-users__item__subscription-date', content); const line2 = createElement('span', '', 'b-users__item__subscription-date', content); const line3 = createElement('span', '', 'b-users__item__subscription-date', content); const line4 = createElement('span', '', 'b-users__item__subscription-date', content); const line5 = createElement('span', '', 'b-users__item__subscription-date', content); const line6 = createElement('span', '', 'b-users__item__subscription-date', content); createElementAndSetInnerHtml('Recognized Videos:', 'span', '', 'b-users__item__subscription-date__label', line1); createElementAndSetInnerHtml(data.videos, 'span', dataIds.videos, 'b-users__item__subscription-date__value', line1); createElementAndSetInnerHtml('Recognized Images:', 'span', '', 'b-users__item__subscription-date__label', line2); createElementAndSetInnerHtml(data.images, 'span', dataIds.images, 'b-users__item__subscription-date__value', line2); createElementAndSetInnerHtml('Collected Videos:', 'span', '', 'b-users__item__subscription-date__label', line3); createElementAndSetInnerHtml(data.collectedVideos, 'span', dataIds.collectedVideos, 'b-users__item__subscription-date__value', line3); createElementAndSetInnerHtml('Collected Images:', 'span', '', 'b-users__item__subscription-date__label', line4); createElementAndSetInnerHtml(data.collectedImages, 'span', dataIds.collectedImages, 'b-users__item__subscription-date__value', line4); createElementAndSetInnerHtml('Downloaded Videos:', 'span', '', 'b-users__item__subscription-date__label', line5); createElementAndSetInnerHtml(data.downloadedVideos, 'span', dataIds.downloadedVideos, 'b-users__item__subscription-date__value', line5); createElementAndSetInnerHtml('Downloaded Images:', 'span', '', 'b-users__item__subscription-date__label', line6); createElementAndSetInnerHtml(data.downloadedImages, 'span', dataIds.downloadedImages, 'b-users__item__subscription-date__value', line6); } function createDownloadBlock(parentEl) { const content = createItemBlock('Methods', parentEl, 'b-tab-container'); const wrapper = createElement('div', '', 'b-offer-wrapper', content); const downloadStartButton = createButtonWithWrapper(downloadButtonId, 'Start Download', wrapper); const forceStartDownload = createButtonWithWrapper(forceStartDownloadButtonId, 'Force To Start Download', wrapper, 'm-danger'); const closeButton = createButtonWithWrapper(closeButtonId, 'Close Downloader', wrapper, 'm-danger'); downloadStartButton.addEventListener('click', () => { download().then(() => { // }); }); forceStartDownload.addEventListener('click', () => { isForceStartToDownload = true; }); closeButton.addEventListener('click', () => { remoteElement(parentId); }) } function scrollToEnd() { let retryTimes = 0; if (isCheckboxChecked(scrollToEndCheckboxId)) { return new Promise((resolve) => { function smoothScroll() { setCurrentStatus('Scrolling to the end'); if (isForceStartToDownload) { return resolve(); } const currentScroll = document.documentElement.scrollTop || document.body.scrollTop; const clientHeight = document.documentElement.clientHeight; const scrollHeight = document.documentElement.scrollHeight; if (scrollHeight - 10 > currentScroll + clientHeight) { window.requestAnimationFrame(smoothScroll); window.scrollTo(0, currentScroll + (scrollHeight - currentScroll - clientHeight) / 2); setInterval(() => { const loadingContainer = document.getElementsByClassName('infinite-loading-container'); if (loadingContainer && loadingContainer[0]) { if (loadingContainer[0].firstElementChild.style.display === 'none') { return resolve(); } } if (document.documentElement.scrollHeight > scrollHeight) { retryTimes = 0; smoothScroll(); } else { if (retryTimes < scrollToEndMaxRetryTimes) { retryTimes ++; smoothScroll(); } else { return resolve(); } } }, 1500); } } smoothScroll(); }); } } function switchToMediaPage() { const tab = document.querySelector('#content > div.l-wrapper > div.l-wrapper__content > div.l-profile-container > div > ul > li:nth-child(2) > a'); tab && tab.click(); } /** * * @param {HTMLElement} item * @returns {HTMLElement} */ async function openMedia(item) { item.click(); const container = await waitToGetElementByClassName(document.body, 'pswp__container'); return container.children[1]; } async function closeDialog() { const button = await waitToGetElementByClassName(document.body, 'pswp__button--close'); button.click(); } /** * @param {HTMLElement} wrapper * @param {string} tag * @returns {Promise<HTMLElement>} */ async function waitToGetElementByTag(wrapper, tag) { while(1) { const target = wrapper.getElementsByTagName(tag); if (target && target[0]) { return target[0]; } else { await sleep(200); } } } /** * @param {HTMLElement} wrapper * @param {string} className * @returns {Promise<HTMLElement>} */ async function waitToGetElementByClassName(wrapper, className) { while(1) { const target = wrapper.getElementsByClassName(className); if (target && target[0]) { return target[0]; } else { await sleep(200); } } } /** * @param {HTMLElement[]} videos */ async function downloadVideos(videos) { for (let i = 0; i < videos.length; i++){ setCurrentStatus(`Collect ${i + 1} video(s)`); data.collectedVideos++; const item = await openMedia(videos[i]); const tag = await waitToGetElementByTag(item, 'video'); needToDownloadVideosUrls.push(tag.getAttribute('src')); await closeDialog(); await sleep(sleepMs); } } /** * @param {HTMLElement[]} images */ async function downloadImages(images) { for (let i = 0; i < images.length; i++){ setCurrentStatus(`Collect ${i + 1} image(s)`); data.collectedImages++; const item = await openMedia(images[i]); const tag = await waitToGetElementByTag(item, 'img'); needToDownloadImagesUrls.push(tag.getAttribute('src')); await closeDialog(); await sleep(sleepMs); } } async function collectMedia() { const includeVideo = isCheckboxChecked(videoIncludeCheckboxId); const includeImage = isCheckboxChecked(imageIncludeCheckboxId); const videos = []; const images = []; const list = document.querySelector(listWrapperSelector); if (list) { for (const item of list.children) { if (item.classList.contains('m-video-item')) { if (includeVideo) { setCurrentStatus('Recognize a video'); data.videos ++; videos.push(item); } } else if (item.classList.contains('b-photos__item')) { if (includeImage) { setCurrentStatus('Recognize a image'); data.images ++; images.push(item); } } } if (videos.length) { await downloadVideos(videos); } if (images.length) { await downloadImages(images); } downloadAllUrls(); } else { alert('Cannot find media list wrapper. Please check whether if you are subscribe to him.') } } function downloadAllUrls() { setCurrentStatus(`Download media`); for (let i = 0; i < needToDownloadVideosUrls.length; i++){ GM_download({ url: needToDownloadVideosUrls[i], name: window.document.title + '_' + (i + 1) + '.mp4', saveAs: false, onload: () => { data.downloadedVideos++; }, }); } for (let i = 0; i < needToDownloadImagesUrls.length; i++){ GM_download({ url: needToDownloadImagesUrls[i], name: window.document.title + '_' + (i + 1) + '.jpg', saveAs: false, onload: () => { data.downloadedImages++; }, }); } } function initData() { setCurrentStatus('Init data'); data.videos = 0; data.images = 0; data.collectedVideos = 0; data.collectedImages = 0; data.downloadedImages = 0; data.downloadedVideos = 0; isForceStartToDownload = false; needToDownloadVideosUrls.splice(0, needToDownloadVideosUrls.length); if (!hasSetProxy) { data = new Proxy(data, { set(target, p, value) { const el = getElement(dataIds[p]); if (el) { el.innerText = value; } Reflect.set(target, p, value); } }); hasSetProxy = true; } } async function download() { initData(); switchToMediaPage(); await sleep(500); await scrollToEnd(); await collectMedia(); setCurrentStatus(`Download success!`); } function init() { const parentElStyle = { width: '300px', minHeight: 'auto', padding: '10px', background: '#FFF', position: 'fixed', top: 'inherit', left: '30px', bottom: '30px', borderRadius: '6px', zIndex: 1000 }; const parentEl = createElement('div', parentId, 'l-wrapper__sidebar b-profile-list b-bundles-group', document.body, parentElStyle); const list = createElement('ul', '','b-profile__content__list', parentEl); createStatusBlock(list); createCheckBlock(list); setInfoBlock(list); createDownloadBlock(list); } function run(){ init(); } run(); })()