// ==UserScript==
// @name Comic Fuz Downloader
// @namespace http://circleliu.cn
// @version 0.2.2
// @description Userscript for download comics on Comic Fuz
// @author Circle
// @match https://comic-fuz.com/viewer.html*
// @run-at document-start
// @grant none
// @require https://unpkg.com/[email protected]/dist/ajaxhook.min.js
// @require https://unpkg.com/[email protected]/dist/jszip.min.js
// @require https://unpkg.com/[email protected]/dist/jszip-utils.min.js
// @require https://unpkg.com/[email protected]/vendor/FileSaver.js
// @require https://unpkg.com/[email protected]/dist/jquery.min.js
// @homepageURL https://circleliu.github.io/Comic-Fuz-Downloader/
// @supportURL https://github.com/CircleLiu/Comic-Fuz-Downloader
// ==/UserScript==
(function () {
'use strict'
const jq3 = $.noConflict()
jq3(document).ready(($) => {
const divDownload = $(`
<div class="menuField" id="showDownloadMenuField" title="Download">
<button id="showDownload">
<div id="downloadProgress"></div>
</button>
</div>
`)
const downloadIcon = 'url("https://circleliu.github.io/Comic-Fuz-Downloader/icons/download.png")'
const loadingIcon = 'url("https://circleliu.github.io/Comic-Fuz-Downloader/icons/loading.gif")'
$('#menu .submenu:first').append(divDownload)
setIconReady()
$('#showDownload').click(async function() {
setIconDownloading()
console.time('download')
await downloadAsZip()
console.timeEnd('download')
setIconReady()
})
function setIconReady() {
$('#showDownload').css({
'background-image': downloadIcon,
'background-repeat': 'no-repeat',
'background-size': 'contain',
'color': 'white',
'font-size': '8px',
'text-align': 'left',
})
$('#downloadProgress').text('')
}
function setIconDownloading() {
$('#showDownload').css({
'background-image': loadingIcon,
'background-size': '50%',
})
$('#downloadProgress').css({
'margin-top': '20px',
})
}
// $(window).load(() => {
// contentTitle = $('#pagetitle .titleText:first').text()
// })
})
let progressDownloaded = 0
let progressAll = 0
function updateDownloadProgress(text) {
if (text) {
jq3('#downloadProgress').text(text)
} else {
jq3('#downloadProgress').text(`Loading: ${progressDownloaded}/${progressAll}`)
}
}
let cid
let comicTitle
let contentTitle
let authInfo
let baseUrl
let config
ah.proxy({
onResponse: (response, handler) => {
if (response.config.url.indexOf('license') > -1) {
handleLicense(response)
}
if (response.config.url.indexOf('configuration_pack.json') > -1) {
handleConfiguration(response)
}
handler.next(response)
},
})
function handleLicense(response) {
const url = new URL(response.config.url)
const data = JSON.parse(response.response)
cid = url.searchParams.get('cid')
authInfo = data.auth_info
baseUrl = data.url
}
function handleConfiguration(response) {
const data = JSON.parse(response.response)
config = data
comicTitle = _findVal(data, 'Title')
}
function _findVal(object, key) {
var value;
Object.keys(object).some(function(k) {
if (k === key) {
value = object[k];
return true;
}
if (object[k] && typeof object[k] === 'object') {
value = _findVal(object[k], key);
return value !== undefined;
}
});
return value;
}
async function getAllImagesToZip(zip) {
const promises = []
progressDownloaded = 0
config.configuration.contents.forEach(async ({file}) => {
for (let i = 0; i < config[file].FileLinkInfo.PageCount; i++) {
promises.push(getImageToZip(file, i, zip))
}
})
progressAll = promises.length
return Promise.all(promises)
}
async function getImageToZip(filePath, index, zip) {
const { name, data } = await getImageAndReorgnize(filePath, index)
progressDownloaded += 1
zip.file(`${name}.jpg`, data)
updateDownloadProgress()
}
async function getImageAndReorgnize(filePath, index) {
const pagePath = `${filePath}/${index}`
const srcImage = constructImageUrl(pagePath)
const canvas = await reorgnizeImage(srcImage, filePath, index)
const image = await getCanvasBlob(canvas)
return {
name: `${filePath.slice(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'))}-${index}`,
data: image
}
}
function getCanvasBlob(canvas) {
return new Promise(function(resolve, reject) {
canvas.toBlob(function(blob) {
resolve(blob)
}, 'image/jpeg', 1)
})
}
function constructImageUrl(pagePath) {
const imageUrl = `${baseUrl}${pagePath}.jpeg`
const params = $.param(authInfo)
return `${imageUrl}?${params}&_`
}
function reorgnizeImage(image, filePath, index) {
return new Promise((resolve, reject) => {
const srcCanvas = document.createElement('canvas')
const srcContext = srcCanvas.getContext('2d')
const srcImage = new Image()
srcImage.src = image
srcImage.crossOrigin = 'Anonymous'
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
srcImage.onload = () => {
const width = srcImage.width
const height = srcImage.height
srcCanvas.width = width
srcCanvas.height = height
srcContext.drawImage(srcImage, 0, 0)
canvas.width = config[filePath].FileLinkInfo.PageLinkInfoList[index].Page.Size.Width
canvas.height = config[filePath].FileLinkInfo.PageLinkInfoList[index].Page.Size.Height
const pagePath = `${filePath}/${index}`
const pattern = Decoder.calcPattern(pagePath)
const mapData = Decoder.decode(width, height, 64, 64, pattern)
mapData.forEach(({ srcX, srcY, destX, destY, width, height }) => {
const srcData = srcContext.getImageData(destX, destY, width, height)
context.putImageData(srcData, srcX, srcY)
})
resolve(canvas)
}
srcImage.onerror = reject
})
}
function getMetadata() {
contentTitle = jq3('#pagetitle .titleText:first').text()
}
async function downloadAsZip() {
getMetadata()
const zip = new JSZip()
zip.file('ComicInfo.txt', `${cid}\n${comicTitle}\n${contentTitle}`)
await getAllImagesToZip(zip)
const content = await zip.generateAsync({ type: 'blob' }, ({ percent }) => {
updateDownloadProgress(`Packaging: ${percent.toFixed(2)}%`)
})
saveAs(content, `${contentTitle}.zip`)
}
const Decoder = {
calcPattern: function(str) {
let n = 0
str.split('').forEach((char) => {
n += char.charCodeAt(0)
})
return (n % 4) + 1
},
decode: function (e, t, r, i, n) {
var s,a,o,u,c,p,l,m,d,h,y = Math.floor(e / r),g = Math.floor(t / i),f = e % r,b = t % i,S = []
if (
((s = y - ((43 * n) % y)),
(s = s % y == 0 ? (y - 4) % y : s),
(s = 0 == s ? y - 1 : s),
(a = g - ((47 * n) % g)),
(a = a % g == 0 ? (g - 4) % g : a),
(a = 0 == a ? g - 1 : a),
f > 0 &&
b > 0 &&
((o = s * r),
(u = a * i),
S.push({
srcX: o,
srcY: u,
destX: o,
destY: u,
width: f,
height: b,
})),
b > 0)
)
for (l = 0; l < y; l++)
(d = this.calcXCoordinateXRest_(l, y, n)),
(h = this.calcYCoordinateXRest_(d, s, a, g, n)),
(c = this.calcPositionWithRest_(d, s, f, r)),
(p = h * i),
(o = this.calcPositionWithRest_(l, s, f, r)),
(u = a * i),
S.push({
srcX: o,
srcY: u,
destX: c,
destY: p,
width: r,
height: b,
})
if (f > 0)
for (m = 0; m < g; m++)
(h = this.calcYCoordinateYRest_(m, g, n)),
(c = (d = this.calcXCoordinateYRest_(h, s, a, y, n)) * r),
(p = this.calcPositionWithRest_(h, a, b, i)),
(o = s * r),
(u = this.calcPositionWithRest_(m, a, b, i)),
S.push({
srcX: o,
srcY: u,
destX: c,
destY: p,
width: f,
height: i,
})
for (l = 0; l < y; l++)
for (m = 0; m < g; m++)
(h = (m + 37 * n + 41 * (d = (l + 29 * n + 31 * m) % y)) % g),
(c =
d * r + (d >= this.calcXCoordinateYRest_(h, s, a, y, n) ? f : 0)),
(p =
h * i + (h >= this.calcYCoordinateXRest_(d, s, a, g, n) ? b : 0)),
(o = l * r + (l >= s ? f : 0)),
(u = m * i + (m >= a ? b : 0)),
S.push({
srcX: o,
srcY: u,
destX: c,
destY: p,
width: r,
height: i,
})
return S
},
calcPositionWithRest_: function (e, t, r, i) {
return e * i + (e >= t ? r : 0)
},
calcXCoordinateXRest_: function (e, t, r) {
return (e + 61 * r) % t
},
calcYCoordinateXRest_: function (e, t, r, i, n) {
var s,
a,
o = n % 2 == 1
return (
(e < t ? o : !o) ? ((a = r), (s = 0)) : ((a = i - r), (s = r)),
((e + 53 * n + 59 * r) % a) + s
)
},
calcXCoordinateYRest_: function (e, t, r, i, n) {
var s,
a,
o = n % 2 == 1
return (
(e < r ? o : !o) ? ((a = i - t), (s = t)) : ((a = t), (s = 0)),
((e + 67 * n + t + 71) % a) + s
)
},
calcYCoordinateYRest_: function (e, t, r) {
return (e + 73 * r) % t
},
}
})()