您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
下载 iwara 视频。 注意:可能需要给 tampermonkey 等插件设置下载权限。
// ==UserScript== // @name iwara download // @name:zh-CN iwara 下载 // @description Download videos from iwara.tv. NOTE: may need grant download privilege to browser extension like tampermonkey. // @description:zh-CN 下载 iwara 视频。 注意:可能需要给 tampermonkey 等插件设置下载权限。 // @namespace https://sleazyfork.org/zh-CN/scripts/425903-iwara-download // @version 1.0.0 // @author oajsdfk // @match https://*.iwara.tv/* // // @grant GM_download // ==/UserScript== (function () { 'use strict'; if (!jQuery) { return; } const PARALLEL = 2; let downloaded_str = localStorage.getItem('downloaded'); let downloaded = []; if (downloaded_str) { downloaded = downloaded_str.split(' '); } const is_downloaded = (id) => downloaded.indexOf(id) != -1; const add_downloaded = (id) => { if (is_downloaded(id)) return; let s = localStorage.getItem('downloaded'); if (s != downloaded_str) { let n = s.indexOf(downloaded_str); if (n == 0) { let add = s.substr(downloaded_str.length + 1).split(' '); console.log("downloaded in other window:", add); downloaded.push(...add); downloaded_str = s; } else { s.split(' ').forEach((id) => { if (is_downloaded(id)) return; console.log("downloaded in other window:", id); downloaded.push(id); downloaded_str += ' ' + id; }); } } if (is_downloaded(id)) return; downloaded.push(id); downloaded_str += ' ' + id; localStorage.setItem('downloaded', downloaded_str); }; let downloading_str = ''; let downloading = []; const remove_downloading = (vid) => { let n = downloading.indexOf(vid); if (n != -1) { downloading.splice(n, 1); downloading_str = downloading.join(' '); localStorage.setItem('downloading', downloading_str); } } const add_downloading = (vid) => { downloading.push(vid); downloading_str = downloading.join(' '); localStorage.setItem('downloading', downloading_str); } window.onstorage = (e) => { if (e.key == 'downloading') { downloading_str = e.newValue; downloading = downloading_str.split(' '); return; } if (e.key == 'downloaded') { let s = e.newValue; if (s != downloaded_str) { let n = s.indexOf(downloaded_str); if (n == 0) { let add = s.substr(downloaded_str.length + 1).split(' '); console.log("downloaded in other window:", add); downloaded.push(...add); downloaded_str = s; } else { s.split(' ').forEach((id) => { if (is_downloaded(id)) return; console.log("downloaded in other window:", id); downloaded.push(id); downloaded_str += ' ' + id; }); localStorage.setItem('downloaded', downloaded_str); } } return; } }; var $ = jQuery.noConflict(); var view_page = window.location.pathname.startsWith('/videos/') function fname(str) { return str.replace(/\\/g, '¥') .replace(/\//g, '/') .replace(/:/g, ':') .replace(/\*/g, '*') .replace(/\?/g, '?') .replace(/"/g, '”') .replace(/</g, '<') .replace(/>/g, '>') .replace(/\|/g, '|') .replace(/\t/g, ' ') .replace(/~/g, '~'); }; $('.node-video').append('<input type="checkbox" class="dl_chk" checked/>'); $('body').append(`<style> #dlboxs input { background-color: transparent; padding: 0px; margin: 0px; border: 0px; } #dlboxs input:hover { background-color: limegreen; border: 1px; } </style><div id="dlboxs" style="position: fixed; left: 0px; bottom: 0px; z-index: 500; background-color:transparent;"> <input id="set_downloaded" type="button" value="⚐" title="标记所选已被下载"> <input id="sel_dl_all" type="button" value="◼" title="全选"><input id="sel_dl_invert" type="button" value="⬗" title="反选"><input id="sel_dl_none" type="button" value="◻" title="空选"> <input id="download" type="button" value=" ⭳ " title="下载所选"> </div>`) $('#sel_dl_all').on('click', function (e) { $('.dl_chk:enabled').each(function () { this.checked = true; }); }); $('#sel_dl_invert').on('click', function (e) { $('.dl_chk:enabled').each(function () { this.checked = !this.checked; }); }); $('#sel_dl_none').on('click', function (e) { $('.dl_chk:enabled').each(function () { this.checked = false; }); }); $('#set_downloaded').on('click', function (e) { let vs = $('.node-video').has('.dl_chk:checked:enabled'); //console.log("set_downloaded:", vs) if (vs.length === 0) { return; } vs.toArray().forEach(v => { let video = $(v) let b = parse_video(video) if (!b) return null let [like, view, vid, title, user] = b console.log('add_downloaded:', '♥' + like + ' 👁' + view + ' ' + user + '/' + title + ' [' + vid + ']'); add_downloaded(vid) set_downloaded(video) }) }); function set_downloaded(video) { if (!view_page) { let i = video.find('.dl_chk'); i.attr('disabled', true); i.removeClass('dl_chk'); } else { $('#download').attr('disabled', true); } }; function check_downloaded() { let vs = $('.node-video')//.has('.dl_chk:checked:enabled'); if (vs.length === 0) { return; } $(this).val('checking'); vs.toArray().forEach(v => { let video = $(v) let b = parse_video(video) if (!b) return null let [like, view, vid, title, user] = b if (is_downloaded(vid)) { set_downloaded(video) return; } if (downloading.indexOf(vid) != -1) { set_downloaded(video) return; } //console.log('check_downloaded:', '♥' + like + ' 👁' + view + ' ' + user + '/' + title + ' [' + vid + ']'); }) } check_downloaded() $('#download').on('click', function () { if (view_page) { download(); return; } let checked = $('.node-video').has('.dl_chk:checked:enabled'); if (checked.length === 0) return; async_pool(checked.toArray().map(v => $(v)), download, PARALLEL); }); function parse_video(video) { let like; let view; let vid; let title; let user; if (!view_page) { let t = video.find('.field-item > a'); if (t) { let href = t.attr('href') if (!href) return null vid = href.replace('/videos/', ''); t = t.find('img') title = t.attr('title') || t.attr('title'); } if (!title || !vid) { t = video.find('.title > a'); vid = t.attr('href').replace('/videos/', ''); title = t.text(); } like = video.find('.likes-icon').has('.glyphicon-heart').text().trim(); view = video.find('.likes-icon').has('.glyphicon-eye-open').text().trim(); user = video.find('.username').text(); } else { vid = window.location.pathname.replace('/videos/', ''); title = $('.node-info').find('.title').text(); let t = $('.node-views').has('.glyphicon-heart').has('.glyphicon-eye-open').text().trim().split(/\s+/); like = t[0]; view = t[1]; user = $('.node-info').find('.username').text(); } if (user.endsWith('.')) { let n = user.replace(/\.$/, '_'); console.log("rename user:", user, n) user = n } //console.log('download:', '♥' + like + ' 👁' + view + ' ' + user + '/' + title + ' [' + vid + ']'); return [like, view, vid, title, user] } async function download(video) { let b = parse_video(video) if (!b) return null let [like, view, vid, title, user] = b console.log('download:', '♥' + like + ' 👁' + view + ' ' + user + '/' + title + ' [' + vid + ']'); let filename = fname(user) + '/' + fname(title) + ' [' + vid + ']'; if (is_downloaded(vid)) { console.log('already downloaded suc:', filename); set_downloaded(video); return; } if (downloading.indexOf(vid) != -1) { console.log(vid, 'is downloading'); return; } add_downloading(vid); return new Promise((resolve, reject) => { $.get('/api/video/' + vid, function (res) { if (res[0]) { console.log(vid, "urls: ", res[0]); let t = res[0].mime.split('/'); let f = filename + '.' + t[t.length - 1]; let url = 'https:' + res[0].uri; console.log('downloading file:', f, 'url:', url); GM_download({ url: url, name: 'iwara/' + f, onload: () => { console.log('download suc:', f); set_downloaded(video); add_downloaded(vid); resolve(); }, onprogress: (e) => { let v = e.loaded / e.total * 100; console.log('downloading:', f, e, v + '%'); }, onerror: (e) => { console.error('download failed:', f, e); remove_downloading(vid); reject(e); }, ontimeout: (e) => { console.error('download timeout:', f, e); remove_downloading(vid); reject(e); }, saveAs: false }); } else { console.error("no video:", vid); remove_downloading(vid); reject("no video"); } }, 'JSON').fail(function (err) { console.error("get_url_failed", vid, err); remove_downloading(vid); reject(err); }); }); } async function async_pool(args, fn, limit) { return new Promise((resolve) => { const argQueue = [...args].reverse(); let count = 0; const outs = []; const pollNext = () => { if (argQueue.length === 0 && count === 0) { resolve(outs); } else { while (count < limit && argQueue.length) { const index = args.length - argQueue.length; const arg = argQueue.pop(); count += 1; const out = fn(arg); const processOut = (out, index) => { outs[index] = out; count -= 1; pollNext(); }; if (typeof out === 'object' && out.then && out.then) { out.then(out => processOut({ status: 'fulfilled', value: out }, index)) .catch(err => processOut({ status: 'rejected', reason: err }, index)); } else { processOut(out, index); } } } }; pollNext(); }); } })();