/* jshint esversion: 9 */ // ==UserScript== // @name Fantia downloader // @name:en Fantia downloader // @name:ja Fantia downloader // @namespace http://tampermonkey.net/ // @version 3.1.9 // @description Download your Fantia rewards more easily! // @description:en Download your Fantia rewards more easily! // @description:ja Download your Fantia rewards more easily! // @author suzumiyahifumi // @include https://fantia.jp/posts/* // @include https://fantia.jp/fanclubs/*/backnumbers* // @icon https://www.google.com/s2/favicons?domain=fantia.jp // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.2.0/jszip.min.js // @grant none // @downloadURL https://update.greasyfork.icu/scripts/423306/Fantia%20downloader.user.js // @updateURL https://update.greasyfork.icu/scripts/423306/Fantia%20downloader.meta.js // ==/UserScript== //log: 3.1.5 remove jQuery inject (function () { 'use strict'; Date.prototype.Format = function (fmt) { let o = { "M+": this.getMonth() + 1, "d+": this.getDate(), "h+": this.getHours(), "m+": this.getMinutes(), "s+": this.getSeconds(), "q+": Math.floor((this.getMonth() + 3) / 3), "S": this.getMilliseconds() }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (let k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt; }; $('head').append(``); $('nav.scroll-tabs>div').append(`擷取下載`); let init = setInterval(() => { let pageType = (window.location.href.match(/https:\/\/fantia\.jp\/posts\/*/g) != null) ? `post` : `backnumber`; let post = (pageType == "backnumber") ? 1 : $(`.the-post`).length; if (window.setting) { var postContent = (window.setting.metaData.content == undefined || window.setting.metaData.content.length == 0) ? true : false; } else { window.setting = new Setting(); } if (($(`div[id^='post-content-id-']`).length != 0 || postContent) && post != 0) { // for nonImgbox $(`div.image-thumbnails`).each((i, div) => { let b = $(div).closest('div.content-block').find(`div.text-center > div.btn-group-tabs`); if (b.length == 0) $(div).before(`
`); }); // for single image $(`a.fantiaImage`).each((i, div) => { $(div).before(`
`); }); // for post $(`.the-post .post-thumbnail .img-default`).closest(`div.post-thumbnail`).before(`
`); // make sure the button has been inserted before stopping interval. if ($(`div[role="group"]`).length) { window.getDownLoadButton(); clearInterval(init); } } }, 500); class Setting { constructor() { this.pageType = (window.location.href.match(/https:\/\/fantia\.jp\/posts\/*/g) != null) ? `post` : `backnumber`; let qs = new URLSearchParams((this.pageType == `post`) ? `` : (window.location.search == ``) ? $(`div.text-center>a.active`).attr("href").split("?")[1] : window.location.search); this.jsonUrl = `https://fantia.jp/api/v1/${(this.pageType == `post`) ? `posts/${window.location.href.split("/").pop()}` : `fanclub/backnumbers/monthly_contents/plan/${qs.get("plan")}/month/${qs.get("month")}`}`; let authorId = $("h1.fanclub-name>a").attr(`href`).split("/").pop(); const keyArr = [`cookieSave`, `lang`, `authorId_${authorId}`, `generalSaveZIP`, `generalSaveFile`, `authorSaveZIP_${authorId}`, `authorSaveFile_${authorId}`, `dateFormat`]; this.cookieOri = document.cookie; this.cookie = this.cookieParser(keyArr); this.cookieSave = this.cookie.cookieSave || this.defaultCookie(`cookieSave`); this.setCookie({ cookieSave: this.cookieSave, lang: (this.cookieSave == `On`) ? this.cookie.lang : this.getOriLang(), generalSave: this.defaultCookie(`generalSave`), authorSave: this.defaultCookie(`authorSave`), authorSaveCheck: this.cookie[`authorId_${authorId}`] || this.defaultCookie(`authorSaveCheck`), authorId: authorId, dateFormat: (this.cookieSave == `On`) ? this.cookie.dateFormat : this.defaultCookie(`dateFormat`) }); if (this.cookieSave == `On`) { let g = this.cookieParser([`generalSaveZIP`, `generalSaveFile`]); this.setCookie({ generalSave: { zipName: g.generalSaveZIP, fileName: g.generalSaveFile } }); if (this.cookie[`authorId_${authorId}`] || this.cookie[`authorId_${authorId}`] == `On`) { let a = this.cookieParser([`authorSaveZIP_${authorId}`, `authorSaveFile_${authorId}`]); this[`authorId_${authorId}`] = `On`; this.setCookie({ authorSaveCheck: `On`, authorSave: { zipName: a[`authorSaveZIP_${authorId}`], fileName: a[`authorSaveFile_${authorId}`] } }); } } this.saveCookie(false); this.metaJson = {}; this.metaData = {}; if (window.csrfToken) { $.ajaxSetup({ headers: { "x-csrf-token": window.csrfToken } }); } let self = this; $.get(this.jsonUrl, (json) => { self.metaJson = json; let data = json[self.pageType]; self.metaData = { user: data.fanclub.creator_name, uid: data.fanclub.id, content: data[`${self.pageType}_contents`] }; }); return this; } saveCookie(check) { if (this.cookie.cookieSave == 'On') { let date = new Date(); date.setDate(date.getDate() + 75); let cookie = { cookieSave: this.cookie.cookieSave, lang: this.cookie.lang, dateFormat: this.cookie.dateFormat, Expires: date.toUTCString() }; cookie.generalSaveZIP = this.cookie.generalSave.zipName; cookie.generalSaveFile = this.cookie.generalSave.fileName; if (this[`authorId_${this.authorId}`] == 'On') { cookie[`authorId_${this.authorId}`] = 'On'; cookie[`authorSaveZIP_${this.authorId}`] = this.cookie.authorSave.zipName; cookie[`authorSaveFile_${this.authorId}`] = this.cookie.authorSave.fileName; } this.updateCookie(cookie); } else { let date = new Date(); date.setDate(date.getTime() - 1); let cookie = { cookieSave: 'Off', Expires: date.toUTCString() }; cookie[`authorId_${this.authorId}`] = 'Off'; this.updateCookie(cookie); } return (check != false) ? alert(this.getDefault(`saveMessage`, this.lang)) : true; } updateCookie(cookie = undefined) { let Expires = cookie.Expires; delete cookie.Expires; for (let [key, value] of Object.entries(cookie)) { document.cookie = `${key}=${value}; Expires=${Expires}; Path=/`; } this.cookieOri = document.cookie; return this.cookieOri; } cookieParser(keyArr) { return (typeof keyArr == `string`) ? document.cookie.split('; ').map(row => row.split('=')).filter(row => keyArr == row[0])[1] || undefined : Object.fromEntries(document.cookie.split('; ').map(row => row.split('=')).filter(row => keyArr.includes(row[0]))); } setCookie(settingObj) { for (let [key, value] of Object.entries(settingObj)) { this.cookie[key] = value; this[key] = value; } return this.cookie; } setLang(Lang) { let lang = Lang || this.getOriLang().toLowerCase(); if (/^ja\b/.test(lang)) this.lang = `ja`; if (/^zh\b/.test(lang)) this.lang = `zh`; return this.lang; } getOriLang() { let res = `en`; let lang = (navigator.language || navigator.browserLanguage).toLowerCase(); if (/^ja\b/.test(lang)) res = `ja`; if (/^zh\b/.test(lang)) res = `zh`; return res; } defaultCookie(key) { let defaultSetting = { cookieSave: "Off", lang: this.getOriLang(), generalSave: { zipName: `{postTitle}_{boxTitle}`, fileName: `{imgIndex:0}` }, authorSave: { zipName: `[{user}]{postTitle}_{boxTitle}`, fileName: `{boxTitle}_{imgIndex:0}` }, authorSaveCheck: `Off`, dateFormat: `yyyyMMdd` }; return (key) ? defaultSetting[key] : defaultSetting; } getDefault(key, lang = undefined) { let defaultSetting = { en: { downloadImg: `Download Images`, downloadImgZip: `Download ZIP`, retrieving: `Retrieving Link`, zipping: `Zipping`, processing: `Processing`, done: `Done`, // 按紐 settingCenter: `Setting`, cookieButton: `Cookie`, languageButton: `言語設定/Language/界面語言`, generalSave: `Global Save`, authorSave: `Author Save`, zipName: `ZIP file name`, fileName: `image file name`, dateFormat: `Date Format`, saveSetting: `Apply Setting`, closeSetting: `Close`, tableParams: `Parameter`, tableMean: `Mean`, saveMessage: `Setting has save!`, // 參數 user: `Creator Name`, uid: `Creator ID`, postTitle: `Post Title`, postId: `Post ID`, boxTitle: `Image Box Title`, "imgIndex:0": `Index for image, Number is first index number.`, plan: `Name of Plan`, fee: `fee of Plan`, postDate: `Post Date`, taskDate: `Download Date`, ext: `Filename Extension` }, ja: { downloadImg: `ダウンロード`, downloadImgZip: `ZIPダウンロード`, retrieving: `取得中`, zipping: `zip圧縮`, processing: `圧縮`, done: `ダウンロード完了`, // 按紐 settingCenter: `設定`, cookieButton: `Cookie`, languageButton: `Language/言語設定/界面語言`, generalSave: `保存の設定`, authorSave: `クリエイターに保存`, zipName: `ZIPフォルダ`, fileName: `ファイル`, dateFormat: `日時の設定`, saveSetting: `設定を保存する`, closeSetting: `閉じる`, tableParams: `パラメータ`, tableMean: `効果`, saveMessage: `設定を保存しました!`, // 參數 user: `クリエイターの名前`, uid: `クリエイターのID`, postTitle: `投稿のタイトル`, postId: `投稿のID`, boxTitle: `イラストボックスのタイトル`, "imgIndex:0": `画像のインデックス、番号は最初のインデックス番号です`, plan: `プラン名`, fee: `プランの月額料金`, postDate: `公開日時`, taskDate: `ダウンロード日時`, ext: `拡張子` }, zh: { downloadImg: `全圖片下載`, downloadImgZip: `ZIP 下載`, retrieving: `擷取連結中`, zipping: `壓縮檔案中`, processing: `壓縮`, done: `下載已完成`, // 按紐 settingCenter: `設定`, cookieButton: `Cookie`, languageButton: `Language/界面語言/言語設定`, generalSave: `全域設定`, authorSave: `作者特訂`, zipName: `壓縮檔檔名`, fileName: `圖片檔檔名`, dateFormat: `日期時間格式`, saveSetting: `儲存設定`, closeSetting: `關閉`, tableParams: `參數`, tableMean: `效果`, saveMessage: `設定已儲存!`, // 參數 user: `創作者名稱`, uid: `創作者 ID`, postTitle: `投稿標題`, postId: `投稿 ID`, boxTitle: `圖片區標題`, "imgIndex:0": `檔案排序號,數字決定起始數字`, plan: `訂閱方案名`, fee: `瀏覽月費`, postDate: `投稿日期`, taskDate: `下載日期`, ext: `檔案副檔名` } }; return (key) ? defaultSetting[lang || this.lang][key] : defaultSetting[lang || this.lang]; } renderSettingParams() { $(`#cookie${this.cookieSave}`).attr("checked", true); $(`#${(this.authorSaveCheck == `On`) ? `authorSave` : `generalSave`}`).attr("checked", true); $(`#selectSetting option[value='${this.lang}']`).attr("selected", true); $(`#zipNameInput`).val(this[(this.authorSaveCheck == `On`) ? `authorSave` : `generalSave`].zipName); $(`#fileNameInput`).val(this[(this.authorSaveCheck == `On`) ? `authorSave` : `generalSave`].fileName); $(`#dateFormatInput`).val(this.dateFormat); $(`#paramsTable`).html(this.paramsTemplate(this.lang)); return this; } changeStyle(mode) { let blur = CSS.supports(`backdrop-filter`, `blur(15px)`); switch (mode) { case `Blur`: return; case `unBlur`: return; default: return; } } changeLang(Lang) { let lang = Lang || $("#selectSetting option:selected").val(); this.setCookie({ lang: lang }); $("#titleSetting").text(this.getDefault(`settingCenter`, lang)); $("#cookieSetting").text(this.getDefault(`cookieButton`, lang)); $("#langSetting").text(this.getDefault(`languageButton`, lang)); $("#generalSaveSetting").text(this.getDefault(`generalSave`, lang)); $("#authorSaveSetting").text(this.getDefault(`authorSave`, lang)); $("#zipNameSetting").text(this.getDefault(`zipName`, lang)); $("#fileNameSetting").text(this.getDefault(`fileName`, lang)); $("#dateFormatSetting").text(this.getDefault(`dateFormat`, lang)); $("#saveSetting").text(this.getDefault(`saveSetting`, lang)); $("#closeSetting").text(this.getDefault(`closeSetting`, lang)); $(".downloadSpan").text(this.getDefault('downloadImg', lang)); $(".downloadSpanZip").text(this.getDefault('downloadImgZip', lang)); $("#paramsTable").html(this.paramsTemplate(this.lang)); return; } changeName(type) { let name = $(`#${type}NameInput`).val(); let setting = $(`input[name='saveCheck']:checked`).attr("id"); if (name == ``) { this.changeSaveSetting(setting); } else { this[setting][`${type}Name`] = name; } return; } changeSaveSetting(id) { let setting = this[id]; $(`#zipNameInput`).val(setting.zipName); $(`#zipNameInput`).attr("placeholder", setting.zipName); $(`#fileNameInput`).val(setting.fileName); $(`#fileNameInput`).attr("placeholder", setting.fileName); return; } paramsTemplate(lang = this.lang) { let table = ``; let a = [`user`, `uid`, `postTitle`, `postId`, `boxTitle`, `imgIndex:0`, `plan`, `fee`, `postDate`, `taskDate`, `ext`].forEach((p, i) => { table += ``; }); table += `
${setting.getDefault(`tableParams`, lang)} ${setting.getDefault(`tableMean`, lang)}
{${p}} ${setting.getDefault(p, lang)}
`; return table; } settingCenterTemplate(lang) { return `

${this.getDefault(`settingCenter`, lang)}

${this.getDefault(`cookieButton`, lang)}

${this.getDefault(`languageButton`, lang)}

${this.getDefault(`zipName`, lang)}

.zip

${this.getDefault(`fileName`, lang)}

.{ext}

${this.getDefault(`dateFormat`, lang)}

${this.getDefault(`saveSetting`, lang)}

${this.getDefault(`closeSetting`, lang)}

`; } } class downloader { constructor(event) { this.pageType = setting.pageType; this.metaData = setting.metaData; this.button = ($(event.target).is("button")) ? $(event.target) : $(event.target).closest("button"); this.boxType = this.button.attr("box-type"); this.postContent = $(event.target).closest(".boxIndex"); this.type = (this.button.hasClass(`zip`)) ? `zip` : `file`; this.boxIndex = this.postContent.attr("boxIndex"); if (this.boxType == "box") { let content = this.metaData.content[this.boxIndex]; let p = (content.plan == null) ? `一般公開` : undefined; this.metaData.category = content.category; this.metaData.indextype = (content.category == "blog") ? this.button.closest(".blogBox").attr("blog-img-index") : "photo_gallery"; this.metaData.srcArr = (content.category == "blog") ? [JSON.parse(content.comment).ops.filter(i => { return (i.insert.fantiaImage && i.insert.fantiaImage.id == this.metaData.indextype) ? true : false })[0].insert.fantiaImage.original_url] : content.post_content_photos.map(img => img.url.original); this.metaData.fee = p || content.plan.price; this.metaData.plan = p || content.plan.name; this.metaData.postDate = content.parent_post.date; this.metaData.postId = content.parent_post.url.split("/").pop(); this.metaData.postTitle = content.parent_post.title; this.metaData.title = content.title; this.metaData.d = (content.category == "photo_gallery") ? downloader.getDigits(Number(content.post_content_photos.length)) : 1; } else if (this.boxType == "post") { let p = `一般公開`; this.metaData.category = "post"; this.metaData.indextype = "post"; this.metaData.srcArr = [setting.metaJson.post.thumb.original]; this.metaData.fee = p; this.metaData.plan = p; this.metaData.postDate = setting.metaJson.post.posted_at; this.metaData.postId = setting.metaJson.post.uri.show.split("/").pop(); this.metaData.postTitle = setting.metaJson.post.title; this.metaData.title = setting.metaJson.post.title; this.metaData.d = 1; } this.zipName = `${setting[`${(setting.authorSaveCheck == 'On') ? `author` : `general`}Save`].zipName}.zip`; this.fileName = `${setting[`${(setting.authorSaveCheck == 'On') ? `author` : `general`}Save`].fileName}.{ext}`; this.dateFormat = setting.dateFormat; this.zipfmt = ``; this.zipImgIndex0 = 0; this.filefmt = ``; this.fileImgIndex0 = 0; this.d = this.metaData.d; this.zip = (this.type == `zip`) ? new JSZip() : undefined; return this.downloadImg(); } changeButton(mode, input = false) { let button = this.button; switch (mode) { case `start`: button.addClass(['active', 'hdr']); if (this.type == `file`) { button.find('i').removeClass('fa-download'); button.find('i').addClass('fa-spinner'); button.find('i').addClass('fa-pulse'); } else { button.find('i').removeClass('fa-file-archive-o'); button.find('i').addClass('fa-spinner'); button.find('i').addClass('fa-pulse'); } break; case `catchLink`: button.find('span').text(setting.getDefault(`retrieving`)); break; case `countTask`: button.find('span').text(`${this.finCount} / ${this.imgSrc.length}`); break; case `pickUp`: button.find('span').text(setting.getDefault(`zipping`)); break; case `end`: if (this.type == `file`) { button.find('i').removeClass('fa-pulse').removeClass('fa-spinner').addClass('fa-download'); } else { button.find('i').removeClass('fa-pulse').removeClass('fa-spinner').addClass('fa-file-archive-o'); } button.removeClass(['active', 'hdr']); button.find('span').text(setting.getDefault(`done`)); break; case `log`: button.find('span').text(input); break; default: return this; } return this; } paramsParser(type, fmt) { let o = { user: () => { return this.metaData.user || $("h1.fanclub-name>a").text(); }, uid: () => { return this.metaData.uid || this.authorId; }, postTitle: () => { return this.metaData.postTitle || $("h1.post-title").text(); }, postId: () => { return this.metaData.postId || window.location.href.split("/").pop(); }, boxTitle: () => { return this.metaData.boxTitle || this.button.closest("div.post-content-inner").find('h2').text(); }, plan: () => { let feeStr = this.metaData.plan || this.button.closest("div.post-content-inner").find(`div.post-content-for strong.ng-binding`).text(); let match = this.metaData.plan || feeStr.match(new RegExp(/(\d+円)以上限定$/g)); if (match != null) return this.metaData.plan || feeStr.replace(match[0], ``); return this.metaData.plan || `一般公開`; }, fee: () => { let feeStr = this.metaData.fee || this.button.closest("div.post-content-inner").find(`div.post-content-for strong.ng-binding`).text(); let match = this.metaData.fee || feeStr.match(new RegExp(/((\d+)円)以上限定$/g)); if (match != null) return this.metaData.fee || RegExp.$1; return this.metaData.fee || `一般公開`; }, postDate: () => { return new Date(this.metaData.postDate || $(`small.post-date>span`).text()).Format(this.dateFormat); }, taskDate: () => { return new Date().Format(this.dateFormat); } }; for (let k in o) { if (new RegExp('(\\{' + k + '\\})', 'g').test(fmt)) fmt = fmt.replace(RegExp.$1, o[k]()); } let s = (/\{imgIndex(\:(\d+))?\}/g.test(fmt)) ? RegExp.$2 : 0; if (/\{imgIndex(\:(\d+))?\}/g.test(fmt)) fmt = fmt.replace(RegExp.$1, ``); this[`${type}fmt`] = fmt; this[`${type}ImgIndex0`] = s; return this; } nextName(type, index, mimeType) { if (this.metaData.indextype != "photo_gallery") return this[`${type}fmt`].replace(`{imgIndex}`, (this.metaData.indextype).toString().padStart(this.d, 0)).replace(`{ext}`, mimeType.toString().split(`/`)[1]); return this[`${type}fmt`].replace(`{imgIndex}`, (Number(index) + Number(this[`${type}ImgIndex0`])).toString().padStart(this.d, 0)).replace(`{ext}`, mimeType.toString().split(`/`)[1]); } downloadImg() { let dataCont = 0; this.paramsParser(`zip`, this.zipName); this.paramsParser(`file`, this.fileName); this.changeButton(`start`); this.changeButton(`catchLink`); this.changeButton(`log`, `${dataCont} / ${this.metaData.srcArr.length}`); let self = this; this.metaData.srcArr.forEach((url, i) => { downloader.loadAsArrayBuffer(url, function (imgData, mimeType, lastModified) { dataCont += 1; self.changeButton(`log`, `${dataCont} / ${self.metaData.srcArr.length}`); self.mimeType = mimeType; if (self.zip == undefined) { if (dataCont == self.metaData.srcArr.length) self.changeButton('end'); let content = new Blob([imgData], { type: mimeType }); downloader.download(content, self.nextName('file', i, mimeType)); return; } else { const sDate = lastModified && lastModified !== '' ? new Date(lastModified) : null const date = sDate ? new Date(sDate.getTime() - sDate.getTimezoneOffset() * 60000) : new Date() self.zip.file(self.nextName('file', i, mimeType), imgData, { date }); if (dataCont == self.metaData.srcArr.length) { self.changeButton(`pickUp`); self.zip.generateAsync({ type: "blob" }, function updateCallback(metadata) { self.changeButton(`log`, `${setting.getDefault(`processing`)}:${metadata.percent.toFixed(2)} %`); }).then(function (content) { self.changeButton('end'); downloader.download(content, self.nextName('zip', 0, mimeType)); return; }); } } }); }); } static download(content, name) { let tag = document.createElement('a'); tag.href = (URL || webkitURL).createObjectURL(content); tag.download = name; document.body.appendChild(tag); tag.click(); document.body.removeChild(tag); return; } static getDigits(i) { let L = 0; while (i >= 1) { i = i / 10; L += 1; } return `${L}`; } static loadAsArrayBuffer(url, callback) { let xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = "arraybuffer"; xhr.onerror = function (error) { return new Error(`ERROR`); }; xhr.onload = function () { if (xhr.status === 200) { callback(xhr.response, xhr.getResponseHeader("Content-Type"), xhr.getResponseHeader("Last-Modified")); } else { return new Error(`ERROR`); } }; xhr.send(); } } window.getDownLoadButton = () => { $("div.post-content-inner").each((i, div) => { $(div).addClass("boxIndex").attr("boxIndex", i); $(div).find("div.btn-group-tabs").append(``); }); $(`.the-post`).find("div.btn-group-tabs").append(``); $('.set-FD').remove(); $("div#page").append(`
`); $("div#page").append(setting.settingCenterTemplate()); setting.renderSettingParams().paramsTemplate(); return; }; window.openSettingCenter = () => { if ($(`#settingCenterDiv`).hasClass(`close`)) { $(`#settingCenterDiv`).removeClass(`close`); } return; }; window.getImg = (event) => { return checkBrowser(event, (event) => { return new downloader(event); }); }; window.checkBrowser = (event, callBack) => { try { let excludes = []; let ex = excludes.map(b => navigator.userAgent.indexOf(b)).filter(b => (b != -1) ? true : false); if (ex.length >= 1) { alert(`請使用 Firefox 下載圖片!\nPlease run this script on Firefox!\n Also you can check the new script version!`); return; } else { return callBack(event); } } catch (err) { console.log(err); alert(`出了些問題!你可以嘗試使用 Firefox 下載圖片!\nThere are some ERROR, You can try this script on Firefox!\n Also you can check the new script version!`); return; } }; })();