// ==UserScript==
// @name MyFigureCollection: 图片下载器
// @name:en MyFigureCollection: Image Downloader
// @name:zh-CN MyFigureCollection: 图片下载器
// @name:zh-TW MyFigureCollection: 图片下载器
// @description 下载原始图片
// @description:en The original fullsize images downloader
// @description:zh-CN 下载原始图片
// @description:zh-TW 下载原始图片
// @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
// @require https://gf.qytechs.cn/scripts/444466-mini-mvvm/code/mini%20mvvm.js?version=1047057
// @license GPL-3.0
// @compatible Chrome
// @version 1.4.9
// @author ayan0312
// ==/UserScript==
const TIME_OUT = 30 * 1000
const FILTER_URLS = [
'https://static.myfigurecollection.net/ressources/nsfw.png',
'https://static.myfigurecollection.net/ressources/spoiler.png'
]
const globalState = observe({
group: GM_getValue('groupCount', 1),
count: GM_getValue('picCount', 1),
componentDownloadStatus: {},
downloadStates: {
total: 0,
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, {
total: 0,
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
states.total += 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>Group: </span>
<button v-on:click="decreaseGroup">-</button>
<span style="margin:0 10px">{{global.group}}</span>
<button v-on:click="increaseGroup">+</button>
<span style="margin-left:10px">Item: </span>
<button v-on:click="decreaseCount">-</button>
<span style="margin:0 10px">{{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: 'Download',
loading: 'Downloading...',
error: 'Failed',
timeout: 'Timeout',
downloaded: 'Redownload'
}
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
refreshGroup()
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;flex-wrap:wrap">
<div style="margin-right:15px;color:black">
<span style="">Total:</span>
<span>{{states.total}}</span>
</div>
<div style="margin-right:15px;color:green">
<span style="">Downloaded:</span>
<span>{{states.downloaded}}</span>
</div>
<div style="margin-right:15px;color:grey">
<span style="">Downloading:</span>
<span>{{states.loading}}</span>
</div>
<div style="margin-right:15px;color:brown">
<span style="">Timeout:</span>
<span>{{states.timeout}}</span>
</div>
<div style="color:red">
<span>Failed:</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>{{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 `Group: ${this.group} Item: ${this.count}`
}
},
mounted() {
const value = GM_getValue(window.location.href.split('&')[0])
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.split('&')[0], { 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>Group:</span>
<span >{{group}}</span>
<span>Item:</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 ? 'Close Preview' : 'Preview'
},
containerStyle() {
return Object.assign({}, commonStyle, this.originalImage ? {
width: '100%'
} : {})
},
refreshBtnStyle() {
return REQEUST_BUTTON_STYLES[this.refreshStatus]
},
refreshMsg() {
const messages = {
normal: 'Show Spoiler/NSFW',
loading: 'Showing...',
error: 'Failed',
timeout: 'Timeout',
downloaded: 'Reshow'
}
return messages[this.refreshStatus]
}
},
mounted() {
const value = GM_getValue(picture.split('&')[0])
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) {
let url = 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 refreshGroup() {
const itemId = (new URL(location.href)).searchParams.get('itemId')
if (itemId && !GM_getValue(itemId)) {
GM_setValue(itemId, true)
if (globalState.count > 0)
globalState.group += 1
}
}
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()