// ==UserScript==
// @name MyFigureCollection: 快速下载图片
// @namespace Violentmonkey Scripts
// @match https://myfigurecollection.net/picture/*
// @match https://myfigurecollection.net/pictures.php
// @grant GM_download
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @license MIT
// @version 1.4.2
// @author -
// @description 快速下载MyFigureCollection的相册图片
// ==/UserScript==
const TIME_OUT = 60 * 1000
const FILTER_URLS = [
'https://static.myfigurecollection.net/ressources/nsfw.png',
'https://static.myfigurecollection.net/ressources/spoiler.png'
]
function* getSequence() {
let i = 0;
while (true) {
yield (i = i + 1);
}
}
let sequence = getSequence();
function getId(name) {
return `${name ? name : 'none'}.${new Date().getTime()}.${Math.floor(Math.random() * 10000)}.${sequence.next().value}`;
}
class Dep {
constructor(name) {
this.subs = [];
this.id = getId(name);
}
delete() {
if (this.subs.length < 1)
return;
this.notify();
this.subs.forEach((sub) => {
sub.removeDep(this);
});
this.subs.length = 0;
}
addSub(sub) {
this.subs.push(sub);
}
depend() {
Dep.target.addDep(this);
}
notify() {
this.subs.forEach((sub) => {
sub.update();
});
}
}
Dep.target = null;
const extend = (a, b) => {
for (const key in b) {
a[key] = b[key];
}
return a;
};
const isFunction = (val) => typeof val === 'function';
const NOOP = () => { };
const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
const isPlainObject = (val) => toTypeString(val) === '[object Object]';
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
function toArray(nodes) {
return [].slice.call(nodes);
}
const unique = (arr) => Array.from(new Set(arr));
function observe(value, vm) {
if (!value || typeof value !== 'object')
return value;
if(value.__ob__)
return value
return new Observer(value, vm).proxy;
}
class Observer {
constructor(data, vm) {
Object.keys(data).forEach((key) => {
data[key] = observe(data[key], vm);
});
this.dep = new Dep('data');
Object.defineProperty(data, '__ob__', {
configurable: false,
enumerable: false,
value:this
})
this.proxy = new Proxy(data, {
get: (target, key, receiver) => {
const result = Reflect.get(target, key, receiver)
if (Dep.target){
if(isPlainObject(result) && result.__ob__)
result.__ob__.dep.depend()
else
this.dep.depend()
}
return result;
},
set: (target, key, newValue, receiver) => {
const result = Reflect.set(target, key, observe(newValue), receiver);
this.dep.notify();
return result;
},
deleteProperty: (target, key) => {
const childObj = target[key];
let result = false;
if (isPlainObject(childObj) && hasOwn(childObj, '__ob__')) {
let ob = childObj['__ob__'];
ob.dep.delete();
ob = null;
result = Reflect.deleteProperty(target, key);
this.dep.notify();
}
return result;
},
});
}
}
class EventEmitter {
constructor(scope) {
this._events = new Map();
if (scope)
this._scope = scope;
}
has(eventName){
return this._events.has(eventName)
}
on(eventName, callback) {
if (!this._events.has(eventName))
this._events.set(eventName, []);
this._events.get(eventName).push(callback);
}
emit(eventName, value) {
if (!this._events.has(eventName))
return;
this._events.get(eventName).forEach((callback) => {
if (isFunction(callback))
callback(value);
});
}
off(eventName, callback) {
if (callback) {
this._events.set(eventName, this._events.get(eventName).filter((cb) => {
if (cb === callback || cb.originFunction === callback)
return false;
}));
}
else {
this._events.delete(eventName);
}
}
once(eventName, callback) {
const self = this;
const onceCallback = function () {
self.off(eventName, onceCallback);
callback.apply(self, arguments);
};
onceCallback.originFunction = callback;
this.on(eventName, onceCallback);
}
}
var EventLoop;
(function (EventLoop) {
const callbacks = [];
const p = Promise.resolve();
let pending = false;
let useMacroTask = false;
function flushCallbacks() {
pending = false;
const copies = callbacks.slice(0);
callbacks.length = 0;
copies.forEach((fn) => fn());
}
EventLoop.flushCallbacks = flushCallbacks;
const macroTimerFunction = () => {
setTimeout(flushCallbacks, 0);
};
const microTimerFunction = () => {
p.then(flushCallbacks);
};
function withMacroTask(fn) {
return (fn._withTask ||
(fn._withTask = function () {
useMacroTask = true;
const res = fn.apply(null, arguments);
useMacroTask = false;
return res;
}));
}
EventLoop.withMacroTask = withMacroTask;
function nextTick(context, callback) {
let _resolve;
callbacks.push(() => {
if (callback)
callback.call(context);
else if (_resolve)
_resolve(context);
});
if (!pending) {
pending = true;
if (useMacroTask)
macroTimerFunction();
else
microTimerFunction();
}
if (!callback)
return new Promise((resolve) => {
_resolve = resolve;
});
}
EventLoop.nextTick = nextTick;
})(EventLoop || (EventLoop = {}));
function getApplyFunction(fn, scope) {
const func = function () {
fn.apply(scope, arguments);
};
return func;
}
const createVM = (options = {}) => new MVVM(extend(options, {
element: options.element ? options.element : document.body
}));
class MVVM {
constructor(options = {}) {
this.$event = new EventEmitter(this);
this.$children = {};
this.$refs = {};
this.$on = getApplyFunction(this.$event.on, this.$event);
this.$emit = getApplyFunction(this.$event.emit, this.$event);
this.$off = getApplyFunction(this.$event.off, this.$event);
this.$once = getApplyFunction(this.$event.once, this.$event);
this.$options = options;
this.components = options.components;
MVVM.cid += 1;
this.cid = MVVM.cid;
this._init();
if (this.$options.element)
this.compile(this.$options.element);
}
$watch(key, cb) {
new Watcher(this, key, cb);
}
$nextTick(callback) {
if (callback)
return EventLoop.nextTick(this, callback);
return EventLoop.nextTick(this);
}
use(fn) {
fn.call(this, this);
return this;
}
compile(element) {
this.$compile = new Compile(element, this);
this.$emit('mounted');
}
_init() {
this._initMethods();
this._initLifecycle();
this.$emit('beforeCreate');
this._initData();
this._initComputed();
this._initWatch();
this.$emit('created');
}
_initMethods() {
let methods = this.$options.methods;
if (typeof methods !== 'object')
return;
Object.keys(methods).forEach((key) => {
let object = methods[key];
if (!isFunction(object))
return;
if (this[key])
return;
this[key] = object;
});
}
_initLifecycle() {
this.$options.beforeCreate &&
this.$on('beforeCreate', this.$options.beforeCreate.bind(this));
this.$options.created && this.$on('created', this.$options.created.bind(this));
this.$options.beforeMount &&
this.$on('beforeMount', this.$options.beforeMount);
this.$options.mounted && this.$on('mounted', this.$options.mounted.bind(this));
this.$options.beforeUpdate &&
this.$on('beforeUpdate', this.$options.beforeUpdate);
this.$options.updated && this.$on('updated', this.$options.updated.bind(this));
}
_initData() {
const data = this.$options.data;
this.$data = isFunction(data) ? data.call(this) : data;
Object.keys(this.$data).forEach((key) => Object.defineProperty(this, key, {
configurable: false,
enumerable: true,
get: () => {
return this.$data[key];
},
set: (newVal) => {
this.$data[key] = newVal;
},
}));
this.$data = observe(this.$data, this);
}
_initComputed() {
let computed = this.$options.computed;
if (!isPlainObject(computed))
return;
Object.keys(computed).forEach((key) => {
let object = computed[key];
Object.defineProperty(this, key, {
get: isFunction(object)
? object
: 'get' in object
? object.get
: NOOP,
set: isFunction(object)
? object
: 'set' in object
? object.set
: NOOP,
});
});
}
_initWatch() {
let watch = this.$options.watch;
if (typeof watch !== 'object')
return;
Object.keys(watch).forEach((key) => {
let object = watch[key];
if (!isFunction(object))
return;
this.$watch(key, object);
});
}
}
MVVM.cid = 0;
function getVMVal(vm, exp) {
let temp;
exp.split('.').forEach((k, i) => {
if (i === 0)
temp = vm[k];
else
temp = temp[k];
});
return temp;
}
function setVMVal(vm, exp, value) {
let temp;
let exps = exp.split('.');
if (exps.length === 1)
vm[exps[0]] = value;
else
exps.forEach((k, i, exps) => {
if (i === 0)
temp = vm[k];
else if (i < exps.length - 1)
temp = temp[k];
else if (i === exps.length - 1)
temp[k] = value;
});
}
function parseGetter(exp) {
return (vm) => getVMVal(vm, exp);
}
class Watcher {
constructor(vm, expOrFn, callback,deep = false) {
this.callback = callback;
this.vm = vm;
this.deep = deep
this._depIds = {};
if (isFunction(expOrFn))
this._getter = expOrFn;
else
this._getter = parseGetter(expOrFn.trim());
this.value = this.get();
}
update() {
let newVal = this.get();
let oldVal = this.value;
if (newVal === oldVal && !(this.deep && isPlainObject(newVal) && isPlainObject(oldVal))) return
this.value = newVal;
this.callback.call(this.vm, newVal, oldVal);
}
removeDep(dep) {
delete this._depIds[dep.id];
}
addDep(dep) {
if (!hasOwn(this._depIds, dep.id)) {
dep.addSub(this);
this._depIds[dep.id] = dep;
}
}
get() {
Dep.target = this;
let value = this._getter.call(this.vm, this.vm);
Dep.target = null;
return value;
}
}
class ElementUtility {
static fragment(el) {
let fragment = document.createDocumentFragment(), child;
while ((child = el.firstChild))
fragment.appendChild(child);
return fragment;
}
static parseHTML(html) {
const domParser = new DOMParser();
let temp = domParser.parseFromString(html, 'text/html');
return temp.body.children;
}
static isElementNode(node) {
if (node instanceof Element)
return node.nodeType == 1;
return false;
}
static isTextNode(node) {
if (node instanceof Text)
return node.nodeType == 3;
return false;
}
static text(node, value) {
if (typeof value === 'number')
value = String(value);
node.textContent = value ? value : '';
}
static html(node, value) {
if (typeof value === 'number')
value = String(value);
node.innerHTML = value ? value : '';
}
static class(node, value, oldValue) {
let className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
let space = className && String(value) ? ' ' : '';
node.className = className + space + value;
}
static model(node, newValue) {
if (typeof newValue === 'number')
newValue = String(newValue);
node.value = newValue ? newValue : '';
}
static style(node, newValue, oldValue) {
if (!oldValue)
oldValue = {};
if (!newValue)
newValue = {};
const keys = Object.keys(oldValue).concat(Object.keys(newValue));
unique(keys).forEach((key) => {
if (hasOwn(oldValue, key) && hasOwn(newValue, key)) {
if (oldValue[key] != newValue[key])
node.style.setProperty(key, newValue[key]);
}
else if (hasOwn(newValue, key))
node.style.setProperty(key, newValue[key]);
else
node.style.removeProperty(key);
});
}
static display(node, newValue, oldValue) {
let func = (val) => {
return {
display: val ? 'block' : 'none',
};
};
ElementUtility.style(node, func(newValue), null);
}
}
class MVVMComponent extends MVVM {
constructor(options) {
super(options);
this.$template = options.template || '';
if (options.parent)
this.$parent = options.parent;
}
$mount(element) {
this.compile(element);
}
}
const parseAnyDirectiveFunction = (parseString) => {
return (dir) => dir.indexOf(parseString) == 0;
};
const isDirective = parseAnyDirectiveFunction('v-');
const isEventDirective = parseAnyDirectiveFunction('on');
const isTextDirective = parseAnyDirectiveFunction('text');
const isHtmlDirective = parseAnyDirectiveFunction('html');
const isModelDirective = parseAnyDirectiveFunction('model');
const isClassDirective = parseAnyDirectiveFunction('class');
const isStyleDirective = parseAnyDirectiveFunction('style');
const isShowDirective = parseAnyDirectiveFunction('show');
const isRefDirective = parseAnyDirectiveFunction('ref');
const isForDirective = parseAnyDirectiveFunction('for');
function bindWatcher(node, vm, exp, updater) {
let __for__ = node['__for__'];
let val;
if (__for__ && __for__[exp])
val = __for__[exp]
else
val = getVMVal(vm, exp);
updater && updater(node, val);
new Watcher(vm, exp, (newValue, oldValue) => {
if (newValue === oldValue)
return;
updater && updater(node, newValue, oldValue);
});
}
function eventHandler(node, vm, exp, eventType) {
let fn = vm.$options.methods && vm.$options.methods[exp];
if (eventType && fn) {
if(node instanceof MVVMComponent)
node.$on(eventType,fn.bind(vm))
else
node.addEventListener(eventType, fn.bind(vm), false);
}
}
function vFor(node, vm, exp, c) {
let reg = /\((.*)\)/;
let item, index, list;
if (reg.test(exp)) {
const arr = RegExp.$1.trim().split(',');
item = arr[0];
index = arr[1];
let rightString = RegExp.rightContext.trim();
let rarr = rightString.split(' ');
list = rarr[1];
if (rarr[0] !== 'in')
return;
let val = getVMVal(vm, list);
let children = [];
toArray(node.children).forEach((element) => {
children.push(element.cloneNode(true));
node.removeChild(element);
});
for (let i = 0; i < val.length; i++) {
children.forEach((element) => {
let newNode = element.cloneNode(true);
newNode.__for__ = {
[item]: val[i],
[index]: i
};
node.appendChild(newNode);
c.compileElement(node);
});
}
}
}
function forHandler(node, vm, exp, c) {
vFor(node, vm, exp, c);
new Watcher(vm, exp, (newValue, oldValue) => {
if (newValue === oldValue)
return;
vFor(node, vm, exp, c);
});
}
class Compile {
constructor(el, vm) {
this.slotCallback = [];
this.$vm = vm;
this.$el = ElementUtility.isElementNode(el)
? el
: document.querySelector(el);
this._init();
}
_init() {
if (this.$vm instanceof MVVMComponent) {
this.$slot = ElementUtility.fragment(this.$el);
this.$fragment = this.parseComponentTemplate(this.$vm.$template);
this.$vm.$el = this.$el;
this.$vm.$emit('beforeMount');
this.compileElement(this.$fragment);
this.$el.parentNode.replaceChild(this.$fragment, this.$el);
}
else {
this.$fragment = ElementUtility.fragment(this.$el);
this.$vm.$el = this.$el;
this.$vm.$emit('beforeMount');
this.compileElement(this.$fragment);
this.$el.appendChild(this.$fragment);
}
Object.entries(this.$vm.$children).forEach(([key, child]) => {
const slotCallback = child.$compile.slotCallback;
if (slotCallback.length < 1)
return;
slotCallback.forEach((fn) => {
fn(this);
});
});
}
isSlot(node) {
if (node.tagName === 'SLOT')
return true;
return false;
}
compileSlotElement(slot) {
if (!(this.$vm instanceof MVVMComponent))
return;
if (this.$slot.children.length === 0) {
slot.parentNode.removeChild(slot);
return;
}
this.slotCallback.push(c => {
c.compileElement(this.$slot);
slot.parentNode.replaceChild(this.$slot, slot);
});
}
parseComponentTemplate(templateHTML) {
let element = ElementUtility.parseHTML(templateHTML);
const template = document.createElement('template');
if (element.length) {
if (element.length === 1) {
if (element[0].tagName.toLowerCase() !== 'template')
template.appendChild(element[0]);
}
else
toArray(element).forEach((child) => {
template.appendChild(child);
});
}
return ElementUtility.fragment(template);
}
parseTemplate(leftString, rightString) {
return (node, newValue, oldValue) => {
const str = leftString + newValue + rightString;
ElementUtility.text(node, str);
};
}
compileElement(el) {
let childNodes = [];
// slice
el.childNodes.forEach(node=>{
childNodes.push(node)
})
childNodes.forEach((node) => {
if (el['__for__'])
node['__for__'] = el['__for__'];
let reg = /\{\{(.*)\}\}/;
if (ElementUtility.isElementNode(node)) {
if (this.isComponent(node))
this.compileComponent(node.tagName.toLowerCase(), node);
else if (this.isSlot(node))
this.compileSlotElement(node);
else
this.compile(node);
}
else if (ElementUtility.isTextNode(node) &&
reg.test(node.textContent))
bindWatcher(node, this.$vm, RegExp.$1.trim(), this.parseTemplate(RegExp.leftContext, RegExp.rightContext));
if (node.childNodes && node.childNodes.length)
this.compileElement(node);
});
}
compile(node) {
let nodeAttrs = node.attributes;
toArray(nodeAttrs).forEach((attr) => {
let attrName = attr.name;
if (!isDirective(attrName)){
if (attrName.startsWith('[') && attrName.endsWith(']')) {
node.removeAttribute(attrName)
let realAttrName = attrName.replace('[','')
realAttrName = realAttrName.replace(']','')
bindWatcher(node,this.$vm,attr.value,(node,newVal,oldVal)=>{
node.setAttribute(realAttrName,newVal)
})
}
return;
}
let dir = attrName.substring(2);
let suffix = dir.split(':')[1];
let exp = attr.value || suffix;
if (isEventDirective(dir))
eventHandler(node, this.$vm, exp, suffix);
else if (isTextDirective(dir))
bindWatcher(node, this.$vm, exp, ElementUtility.text);
else if (isHtmlDirective(dir))
bindWatcher(node, this.$vm, exp, ElementUtility.html);
else if (isClassDirective(dir))
bindWatcher(node, this.$vm, exp, ElementUtility.class);
else if (isModelDirective(dir)) {
bindWatcher(node, this.$vm, exp, ElementUtility.model);
let val = getVMVal(this.$vm, exp);
node.addEventListener('input', (e) => {
let target = e.target;
let newValue = target.value;
if (val === newValue)
return;
setVMVal(this.$vm, exp, newValue);
val = newValue;
});
}
else if (isStyleDirective(dir))
bindWatcher(node, this.$vm, exp, ElementUtility.style);
else if (isShowDirective(dir))
bindWatcher(node, this.$vm, exp, ElementUtility.display);
else if (isRefDirective(dir))
this.$vm.$refs[exp] = node;
else if (isForDirective(dir))
forHandler(node, this.$vm, exp, this);
node.removeAttribute(attrName);
});
}
isComponent(node) {
const tagName = node.tagName.toLowerCase();
if (!/^[(a-zA-Z)-]*$/.test(tagName))
return false;
if (this.$vm.components && hasOwn(this.$vm.components, tagName))
return true;
return false;
}
compileComponent(componentName, node) {
const attributes = []
toArray(node.attributes).forEach((attr) => {
attributes.push(attr)
})
const componentOptions = this.$vm.components[componentName];
const component = new MVVMComponent(extend(componentOptions, {
parent: this.$vm
}));
component.$mount(node);
this.$vm.$children[componentName] = component;
attributes.forEach(attr=>{
let attrName = attr.name;
if (!isDirective(attrName))
return;
let dir = attrName.substring(2);
let suffix = dir.split(':')[1];
let exp = attr.value || suffix;
if (isEventDirective(dir))
eventHandler(component, this.$vm, exp, suffix);
else if (isRefDirective(dir))
this.$vm.$refs[exp] = component;
})
}
}
const globalState =observe({
group:GM_getValue('groupCount',1),
count:GM_getValue('picCount',1),
componentDownloadStatus:{},
downloadStates:{
normal:0,
loading:0,
error:0,
timeout:0,
downloaded:0,
}
})
new Watcher(null,()=>{
return globalState.count
},(newVal)=>{
GM_setValue('picCount',newVal)
})
new Watcher(null,()=>{
return globalState.group
},(newVal)=>{
GM_setValue('groupCount',newVal)
globalState.count = 0
})
new Watcher(null,()=>{
return globalState.componentDownloadStatus
},(newVal)=>{
Object.assign(globalState.downloadStates,{
normal:0,
loading:0,
error:0,
timeout:0,
downloaded:0,
})
const states = globalState.downloadStates
Object.keys(newVal).forEach(key=>{
const status = newVal[key]
if(states[status] != null)
states[status]+=1
})
},true)
function beforeDownload(){
if(GM_getValue('groupCount',1) != globalState.group){
const curCount = GM_getValue('picCount',1) + 1
globalState.group = GM_getValue('groupCount',1)
globalState.count = curCount
}else{
globalState.group = GM_getValue('groupCount',1)
globalState.count = GM_getValue('picCount',1) + 1
}
return {group:globalState.group,count:globalState.count}
}
const DownloadSequence = {
template: `
<span>当前组:</span>
<button v-on:click="decreaseGroup">-</button>
<span>{{global.group}}</span>
<button v-on:click="increaseGroup">+</button>
<span>当前图片:</span>
<button v-on:click="decreaseCount">-</button>
<span>{{global.count}}</span>
<button v-on:click="increaseCount">+</button>
`,
data(){
return {
global:globalState,
}
},
methods:{
increaseCount(){
this.global.count+=1
},
decreaseCount(){
this.global.count-=1
},
increaseGroup(){
this.global.group+=1
},
decreaseGroup(){
this.global.group-=1
}
}
}
const REQEUST_BUTTON_STYLES = {
normal:{},
loading: { background: 'white',color:'black',cursor:'wait'},
error:{background:'red',color:'white'},
timeout:{background:'yellow',color:'black'},
downloaded: {background:'green',color:'white'}
}
const DownloadButton = {
template: `
<button v-on:click="download" v-style="downloadBtnStyle">
{{downloadedMsg}}
</button>
`,
data(){
return {
oldStatus:'normal',
downloadStatus:'normal' // 'normal' 'loading' 'error' 'timeout' 'downloaded'
}
},
computed:{
downloadBtnStyle(){
return REQEUST_BUTTON_STYLES[this.downloadStatus]
},
downloadedMsg(){
const messages = {
normal:'下载原图' ,
loading: '正在下载...',
error:'下载失败',
timeout:'下载超时',
downloaded: '重新下载'
}
return messages[this.downloadStatus]
}
},
watch:{
downloadStatus(newStatus,oldStatus){
this.oldStatus = oldStatus
globalState.componentDownloadStatus[this.cid] = newStatus
}
},
created(){
globalState.componentDownloadStatus[this.cid] = this.downloadStatus
},
destoryed(){
delete globalState.componentDownloadStatus[this.cid]
},
methods:{
download(){
if(this.downloadStatus === 'loading') return
this.downloadStatus = 'loading'
if(this.oldStatus !== 'error' && this.oldStatus !== 'timeout')
this.value = beforeDownload()
this.$emit('download',this.value)
},
}
}
const DownloadState = {
template:`
<div style="display:flex;flex-direction:row;padding:5px">
<div style="margin-right:5px;color:grey">
<span style="">未下载:</span>
<span>{{states.normal}}</span>
</div>
<div style="margin-right:5px;color:green">
<span style="">已下载:</span>
<span>{{states.downloaded}}</span>
</div>
<div style="margin-right:5px;color:black">
<span style="">正在下载:</span>
<span>{{states.loading}}</span>
</div>
<div style="margin-right:5px;color:brown">
<span style="">下载超时:</span>
<span>{{states.timeout}}</span>
</div>
<div style="color:red">
<span>下载报错:</span>
<span>{{states.error}}</span>
</div>
</div>
`,
data(){
return {
states:globalState.downloadStates
}
},
methods:{
download(value){
this.$emit('download',value)
}
}
}
const PictureDownload = {
components:{
'download-button':DownloadButton,
'download-sequence':DownloadSequence,
},
template:`
<div>
<download-sequence></download-sequence>
<span v-show:downloaded style="margin:0 10px">{{msg}}</span>
<download-button v-ref="downloadButton" v-on:download="download"></download-button>
</div>
`,
data(){
return {
group:0,
count:0,
downloaded:false
}
},
computed:{
msg(){
return `${this.group}.${this.count}`
}
},
mounted(){
const value = GM_getValue(window.location.href)
if(!value) return
this.$refs.downloadButton.downloadStatus = value.downloadStatus
this.$refs.downloadButton.value = {
group: value.group,
count: value.count
}
this.downloaded = true
this.group = value.group
this.count = value.count
},
methods:{
download(value){
this.downloaded = true
this.group = value.group
this.count = value.count
this.$emit('download',value)
}
}
}
function insertAfter(targetNode, afterNode){
const parentNode = afterNode.parentNode
const beforeNode = afterNode.nextElementSibling
if(beforeNode == null)
parentNode.appendChild(targetNode)
else
parentNode.insertBefore(targetNode, beforeNode)
}
function mountVM(node,component){
const vm = new MVVMComponent(component)
vm.$mount(node)
return vm
}
function download({vm,picture,group,count,origin}){
const values = origin.split('/')
const fileType = values[values.length - 1].split('.')[1]
const name = `${group}.${count}.${fileType}`
const end = (status)=>{
return ()=>{
vm.downloadStatus = status
GM_setValue(picture,{origin,group,count,downloadStatus:status})
}
}
GM_download({
url:origin,
name,
timeout:TIME_OUT,
onload:end('downloaded'),
onerror:end('error'),
ontimeout:end('timeout'),
})
}
function renderPictureExtension(objectMeta,thePicture){
const origin = thePicture.href
const div = document.createElement('div')
objectMeta.appendChild(div)
const vm = mountVM(div,PictureDownload)
vm.$on('download',(value)=>{
download({
...value,
origin,
picture:window.location.href,
vm:vm.$refs.downloadButton,
})
})
}
const createPictruePreview = ({thumb,origin,picture}) => {
const commonStyle={
'margin':'10px 10px',
'border':'1px solid #000',
'padding':'10px',
'border-radius':'5px',
'background':'#fff',
'transition':'all 0.5s',
'box-sizing': 'border-box'
}
return {
components:{
'download-button':DownloadButton
},
template: `
<div v-style="containerStyle">
<div style="margin-bottom:10px;display:flex;flex-direction:row;justify-content:center;align-items:center;width:100%;">
<div style="margin:0 10px;flex-shrink:0"><img style="cursor:pointer" v-on:click="openPicturePage" [src]="thumb" /></div>
<div style="flex-shrink:1" v-show="originalImage"><img style="width:100%" [src]="src" /></div>
</div>
<div style="display:flex;justify-content:center;align-items:center;flex-direction:column">
<download-button v-ref="downloadButton" v-on:download="download"></download-button>
<br v-show:downloaded />
<div v-show:downloaded>
<span>组:</span>
<span >{{group}}</span>
<span>图片:</span>
<span >{{count}}</span>
</div>
<br />
<button v-on:click="toggle">{{msg}}</button>
<br />
<button v-show:refresh v-on:click="refreshOrigin" v-style="refreshBtnStyle">
{{refreshMsg}}
</button>
</div>
</div>
`,
data(){
return {
thumb,
origin,
picture,
refresh:FILTER_URLS.includes(origin),
originalImage:false,
group:0,
count:0,
downloaded:false,
refreshStatus:'normal' // 'normal' 'loading' 'error' 'timeout' 'downloaded'
}
},
computed:{
src(){
return this.originalImage ? this.origin : this.thumb
},
msg(){
return this.originalImage ? '关闭预览' : '预览原图'
},
containerStyle(){
return Object.assign({},commonStyle,this.originalImage ? {
width:'100%'
}:{} )
},
refreshBtnStyle(){
return REQEUST_BUTTON_STYLES[this.refreshStatus]
},
refreshMsg(){
const messages = {
normal:'获取隐藏图片' ,
loading: '正在获取...',
error:'获取失败',
timeout:'获取超时',
downloaded: '重新获取'
}
return messages[this.refreshStatus]
}
},
mounted(){
const value = GM_getValue(picture)
if(!value) return
this.$refs.downloadButton.downloadStatus = value.downloadStatus
this.$refs.downloadButton.value = {
group: value.group,
count: value.count
}
this.downloaded = true
this.group = value.group
this.count = value.count
},
methods:{
download(value){
this.downloaded = true
this.group = value.group
this.count = value.count
if(this.refresh && this.refreshStatus !== 'downloaded')
this.refreshOrigin()
.then(()=>{
this.$emit('download',{...value,origin:this.origin})
})
else
this.$emit('download',{...value,origin:this.origin})
},
toggle(){
if(this.refresh && !this.originalImage && this.refreshStatus !== 'downloaded')
this.refreshOrigin()
this.originalImage = !this.originalImage
},
refreshOrigin(){
if(this.refreshStatus === 'loading') return
this.refreshStatus = 'loading'
return new Promise((resolve,reject)=>{
GM_xmlhttpRequest({
url:this.picture,
responseType:'document',
timeout:TIME_OUT,
onload:(data)=>{
const doc = data.response
const a = doc.querySelector('.the-picture>a')
if(a){
this.origin = a.href
const thumb = a.href.split('/')
thumb.splice(thumb.length - 1,0,'thumbnails')
this.thumb = thumb.join('/')
this.refreshStatus = 'downloaded'
resolve()
return
}
this.refreshStatus = 'error'
reject()
},
onerror:()=>{
this.refreshStatus = 'error'
reject()
},
ontimeout:()=>{
this.refreshStatus = 'timeout'
reject()
}
})
})
},
openPicturePage(){
window.open(picture)
}
}
}
}
function parseOriginalImageURL(thumb_url){
if(thumb_url.indexOf('thumbnails/') > -1){
url = thumb_url.split('thumbnails/').join('')
} else {
const paths = thumb_url.split('pictures/')[1].split('/')
if(paths.length > 2){
paths.splice(3,1)
url = [thumb_url.split('pictures/')[0],paths.join('/')].join('pictures/')
}
}
return url
}
function getImageURLs(node){
const picture = node.querySelector('a').href
const viewport = node.querySelector('.viewport')
const thumb = viewport.style.background.split('"')[1]
let origin = thumb
if(FILTER_URLS.includes(origin))
origin = thumb
else
origin = parseOriginalImageURL(origin)
return {thumb,origin,picture}
}
function renderPicturesExtension(thumbs){
function _initParentNode(parentNode){
parentNode.innerHTML = ''
parentNode.style.setProperty('display','flex')
parentNode.style.setProperty('flex-direction','row')
parentNode.style.setProperty('flex-wrap','wrap')
parentNode.style.setProperty('justify-content','center')
parentNode.style.setProperty('align-items','center')
const div1 = document.createElement('div')
parentNode.parentNode.insertBefore(div1,parentNode)
mountVM(div1,DownloadSequence)
const div2 = document.createElement('div')
const pageCount = document.querySelector('.listing-count-pages') || parentNode
pageCount.parentNode.insertBefore(div2,pageCount)
mountVM(div2,DownloadState)
}
const parentNode = thumbs[0].parentNode
_initParentNode(parentNode)
thumbs.forEach(thumb_node=>{
const imageURLs = getImageURLs(thumb_node)
const preview = createPictruePreview(imageURLs)
const div3 = document.createElement('div')
parentNode.appendChild(div3)
const previewVM = mountVM(div3,preview)
previewVM.$on('download',(value)=>{
download({
...value,
picture:imageURLs.picture,
vm:previewVM.$refs.downloadButton,
})
})
})
}
function render(){
const objectMeta = document.querySelector('.object-meta')
const thePicture = document.querySelector('.the-picture>a')
if(objectMeta && thePicture)
renderPictureExtension(objectMeta,thePicture)
const thumbs = []
document.querySelectorAll('.picture-icon.tbx-tooltip').forEach(thumb=>{
thumbs.push(thumb)
})
if(thumbs.length > 0)
renderPicturesExtension(thumbs.reverse())
}
render()