写真的保存

不知道说什么...

// ==UserScript==
// @name         写真的保存
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  不知道说什么...
// @license MIT
// @author       kOda
// @match        https://everia.club/*
// @match        https://everiaeveria.b-cdn.net/*
// @match        https://www.everiaclub.com/*
// @require       https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js
// @icon         https://www.google.com/s2/favicons?sz=64&domain=everia.club
// @grant GM_xmlhttpRequest
// @grant GM_addElement
// @grant GM_addStyle
// ==/UserScript==

// 将请求到的子页面转化成DOM对象,在DOM对象上进行操作。

'use strict';

// 时间单位 毫秒
// 延时尽量别整太低,限制访问就不好了
const CONFIG = {
    // 单图片保存后延时
    photoWait: 300,
    // 单写真页保存后延时
    pageWait: 3000,
    // 写真页的文件夹名是否简化
    cleanDirName: true,
    // 简单图片文件名
    simplePhotoName: false,
};

$(unsafeWindow.document).ready(function() {
    CONFIG.iseveriaclub = unsafeWindow.location.host == "www.everiaclub.com";

    var downBtn = document.createElement("button");
    var multiSaver = new MultiPhotoSaver(unsafeWindow.document);

    downBtn.style = `   display: block;
                        position: fixed;
                        left: 10%;
                        bottom: 15%;
                        text-align: center;
                        width: 10%;
                        height: 4%;      `;
    multiSaver.document.body.appendChild(downBtn);

    // 检测当前页面为写真页或主页。
    if (CONFIG.isSenntakuPeeji) {
        downBtn.innerText = "下载本页全部写真";
        downBtn.addEventListener("click", () => {
            multiSaver.downStart().then(() => {
                console.log("本页已下载完毕!(●ˇ∀ˇ●)");
                downBtn.innerText = "已全部下载完成"
            })
            downBtn.disabled = true;
        })
    } else {
        downBtn.innerText = "下载至本地"
        downBtn.addEventListener("click", (e) => {
            unsafeWindow.showDirectoryPicker({mode: "readwrite"}).then((value) => {
                // 油猴处于沙盒时(无// @grant none)使用unsafeWindow访问网页
                new PhotoSaver(unsafeWindow.document, value).pageSave().then(() => {
                    console.log(unsafeWindow.document.title + " 下载成功。");
                    downBtn.innerText = "已全部下载完成";
                    downBtn.disabled = true;
                })
            })
        })
    }
})

// 用于保存单页写真
class PhotoSaver {
    constructor(newDoc, relativeDir) {
        this.window = unsafeWindow;
        this.document = newDoc;
        this.topDir = relativeDir;
        this.index = 0;
        this.srcAttr = "";
        this.title = "";
        this.retryList = [];
        this.imgs = [];

        this.init();
    }

    init() {
        // https://everia.club/*
        // https://everiaeveria.b-cdn.net/*
        // 这两个网站写真页的有多个不同的网页结构,大概是更新了却没有把旧的去除。
        let selectors = ["body > div:nth-child(5) > div.mainleft > img", "#content > article > div.entry-content.clr > figure > figure > img", "#content > article > div.entry-content.clr > div > a > img"]

        // 标题,用于文件夹名.
        this.title = this.document.title;
        this.srcAttr = CONFIG.iseveriaclub ? "data-original" : "data-src";

        if (CONFIG.cleanDirName) {
            this.title = CONFIG.iseveriaclub ? this.title.split(", ").pop().split("-Everia club")[0] :
                                                this.title.split(", ").pop().split(" \u2013 EVERIA.CLUB")[0]
        }

        for (let i = 0; i < selectors.length; i++) {
            this.imgs = this.document.querySelectorAll(selectors[i]);
            if (this.imgs.length) {
                if (!CONFIG.iseveriaclub) {
                    // 2023 10月左右,部分页面除src外没有其它属性存储url。
                    this.imgs[0].getAttribute(this.srcAttr) ? 0 :
                        this.srcAttr = "src";
                }
                break;
            }
        }
    }

    isExist() {
        return new Promise(function(exist, noexist) {
            this.topDir.getDirectoryHandle(this.title).then(() => noexist(this.title)).catch(() => exist(this));
        }.bind(this))
    }

    getFileName(response, base) {
        // 这里用png只是随便选的,没什么深意。
        var temp = `${base}.png`;

        if (!CONFIG.simplePhotoName) {
            // 这个网站的文件名妹法从url上获取,但在返回头有着原文件名。
            if (response.finalUrl.includes("blogger.googleusercontent.com")) temp = response.responseHeaders.split("filename=\"").pop().split("\"")[0];
            else temp = decodeURIComponent(decodeURIComponent(response.finalUrl.split("/").pop()));
        }

        return temp;
    }

    pageSave() {
        return new Promise(function(saveSuccess, saveFail) {
            this.topDir.getDirectoryHandle(this.title, {create: true})
                .then(function(saveDir) {
                    var timer = 0;
                    var currentList = [];
                    this.retryList.push(saveDir);

                    var temp2 = function() {
                        if (this.index == this.imgs.length) {
                            clearTimeout(timer);

                            this.retryList.push(currentList);
                            this.saveRetry(saveSuccess, saveFail);

                            return;
                        }

                        var url = this.imgs[this.index].getAttribute(this.srcAttr)
                        this.photoSave(url, this.index, saveDir).then(() => {
                            timer = setTimeout(temp2, CONFIG.photoWait)
                        }).catch(function(e) {
                            console.error(e)

                            currentList.push([url, this.index])
                            timer = setTimeout(temp2, 1)
                        }.bind(this)).finally(() => this.index++)
                    }.bind(this)

                    temp2();
                }.bind(this), (e) => saveFail(e));
        }.bind(this))
    }

    saveRetry(retrySuccess, retryFail) {
        // 目前只处理因网络波动而造成下载失败的图片
        if (this.retryList[1].length) {
            var tryCount = 0;
            var index = 0;
            var timer = 0;

            console.log(`开始重新下载:`)
            console.log(this.retryList);

            var temp3 = function () {
                if (index == this.retryList[1].length) {
                    console.log(`重下完成,大成功!`)
                    clearTimeout(timer);
                    retrySuccess();
                    return
                }

                var args = this.retryList[1][index];
                this.photoSave(args[0], args[1], this.retryList[0]).then(() => {
                    tryCount = 0;
                    console.log(`${args[0]} 重下成功!`)
                    timer = setTimeout(temp3, CONFIG.photoWait)
                }).catch((error) => {
                    if (tryCount++ > 2) {
                        tryCount = 0;
                        console.log(`${args[0]} \n 编号为:${args[1]} \n重下失败.... 建议自行下载。`)
                        timer = setTimeout(temp3, 1);
                    }

                    index--;
                    timer = setTimeout(temp3, CONFIG.photoWait);
                }).finally(() => index++)
            }.bind(this)

            temp3();
        } else {
            retrySuccess();
        }
    }

    photoSave(url, index, saveDir) {
        return new Promise(function(resolve, reject) {
            GM_xmlhttpRequest({
                method: "GET",
                url: url,
                responseType: "blob",
                timeout: 5000,
                ontimeout: (e) => reject(new Error("Timeout!")),
                onload: function(response) {
                    var name = this.getFileName(response, String(index))

                    saveDir.getFileHandle(name, {create: true})
                    .then((value) => {
                        return value.createWritable()})
                    .then((writer) => {
                        console.log(`保存图片:${name}`)
                        writer.write({
                            type: "write",
                            data: response.response
                        })
                        writer.close().then(function() {
                            resolve()
                        });
                    }).catch((e) => reject(e))
                }.bind(this)
            });
        }.bind(this))
    }
}

class MultiPhotoSaver {
    constructor(newDoc) {
        this.document = newDoc;
        this.existedDirs = [];
        this.photoSelector = "";
        this.photoLinks = [];

        this.init();
    }

    // initialization
    init() {
        // 指向写真页的a标签的选择器
        // https://www.everiaclub.com: .leftp  a(search、tags)
        // https://everia.club/          .search-entry-inner .thumbnail-link(search) .blog-entry-inner div > a(tags)
        // https://everiaeveria.b-cdn.net

        // 就两三个选择器,懒得再去做什么判断了
        if (!CONFIG.iseveriaclub) {
            let t1 = this.document.querySelectorAll(".search-entry-inner .thumbnail-link");
            let t2 = this.document.querySelectorAll(".blog-entry-inner div > a");

            t1.length ? this.photoLinks = t1 : this.photoLinks = t2;
        } else {
            this.photoLinks = this.document.querySelectorAll(".leftp > a");
        }

        this.photoLinks.length ? CONFIG.isSenntakuPeeji = true : 0;
    }

    downStart() {
        return new Promise(function(resolve, reject) {
            unsafeWindow.showDirectoryPicker({mode: "readwrite"}).then(function(picker) {
                var index = 0;
                var retryCount = 0;
                var timer = 0;

                var saverHand = function (photoDoc) {
                    new PhotoSaver(photoDoc, picker).isExist().then((saver) => {saver.pageSave()
                        .then(() => {
                            console.log(photoDoc.title + " 下载成功。");
                            timer = setTimeout(temp1, CONFIG.pageWait);
                        }, (error) => {
                            console.log(photoDoc.title + " 下载失败了");
                            console.error(error)
                        })
                    }, function(fileName) {
                        console.log(`${fileName} 已存在`)
                        this.existedDirs.push(fileName);
                        timer = setTimeout(temp1, 1);
                    }.bind(this));
                }.bind(this)

                // 为避免ip封禁
                // 使用该函数进行请求延时处理。
                var temp1 = function () {
                    if (index == this.photoLinks.length) {
                        if (this.existedDirs.length) {
                            console.log("以下写真已存在,如需下载,请先删除同名文件夹...")
                            console.log(this.existedDirs)
                        }

                        resolve();
                        clearTimeout(timer);
                        return;
                    }

                    GM_xmlhttpRequest({
                        method: "GET",
                        url: this.photoLinks[index++].href,
                        timeout: 5000,
                        ontimeout: function(error) {
                            if (retryCount++ > 2) {
                                console.log("重试失败...")
                                console.log("IP可能被限制")
                                console.log("请您在一段时间重试。")
                            }
                            console.log("请求文件超时,自动重试...");
                            temp1();
                        },
                        onload: function(response) {
                            retryCount = 0;
                            saverHand(new DOMParser().parseFromString(response.response, "text/html"));
                        }
                    });
                }.bind(this)

                temp1();
            }.bind(this));
        }.bind(this))
    }
}