- // ==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();
- });
- }
- })();