mo (LDH) 下载器

在mo的内容页增加图片和视频下载的按钮, 解锁右键功能

目前為 2023-01-30 提交的版本,檢視 最新版本

// ==UserScript==
// @name                mo (LDH) 下载器
// @namespace           https://1mether.me/
// @version             0.23
// @description         在mo的内容页增加图片和视频下载的按钮, 解锁右键功能
// @author              乙醚(@locoda)
// @match               http*://m.tribe-m.jp/*
// @match               http*://m.ex-m.jp/*
// @match               http*://m.ldh-m.jp/*
// @match               http*://m.ldhgirls-m.jp/*
// @icon                https://www.google.com/s2/favicons?sz=64&domain=ldh.co.jp
// @source              https://github.com/locoda/mo-downloader
// @license             MIT
// ==/UserScript==

(function () {
    "use strict";
    // 删除图片保护
    removeProtectImg();
    // 在详情页注入按钮
    if (window.location.href.includes("detail")) {
        injectDownloadAllButtons();
    }
    // 根据视频注入按钮
    if (window.location.href.includes("movie")) {
        if (document.querySelector("div.limelight-player")) {
            injectPerVideoDownloadButton(document);
        }
    }
    // 在时间轴界面设置Listener
    if (window.location.href.includes("timeline")) {
        // 等待加载scroll
        (function init() {
            var counter = document.querySelector("ldh-infinite-scroll");
            if (counter) {
                customizedTimelinePage();
            } else {
                setTimeout(init, 300);
            }
        })();
    }
})();

function removeProtectImg() {
    // 移除右键限制
    document.oncontextmenu = function () {
        return true;
    };
    // 移除protectimg限制
    document
        .querySelectorAll(".protectimg")
        .forEach((node) => node.classList.remove("protectimg"));
}

// ================================
// =    Button Injection Utils    =
// ================================

function injectDownloadAllButtons() {
    var article = document.querySelector("article");
    if (article.classList.contains("article--news")) {
        // 新闻页面特殊处理
        article = article.querySelector(".article__body");
    }
    attachButtonToArticle(article);
}

function findEligibleImgs(article) {
    const keywords = ["uplcmn", "upload"];
    return Array.from(article.querySelectorAll("img"))
        .map((img) => img.src)
        .filter((img) => keywords.some((k) => img.includes(k)));
}

function attachButtonToArticle(article) {
    var imgs = findEligibleImgs(article);
    // 注入按钮 div
    var buttonsDiv = document.createElement("div");
    buttonsDiv.className = "ldh-mo-dl";
    buttonsDiv.style = "margin-top: 0.4em; margin-bottom: 0.4em;";
    article.insertBefore(buttonsDiv, article.firstChild);
    // 图片链接生成按钮
    const isMobile = () =>
        /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
            navigator.userAgent
        );
    if (isMobile()) {
        injectOneButton(buttonsDiv, "生成图片链接", function () {
            generateOnClickHandler(buttonsDiv, article);
        });
    }
    // 图片下载按钮
    injectOneButton(
        buttonsDiv,
        "下载所有图片 (" + imgs.length + ")",
        function () {
            downloadOnClickHandler(article);
        }
    );
    // 视频下载按钮
    if (article.querySelector("div.limelight-player")) {
        injectOneButton(buttonsDiv, "下载所有视频", function () {
            downloadVideoOnClickHandler(article);
        });
        injectPerVideoDownloadButton(article);
    }
}

function injectPerVideoDownloadButton(div) {
    div.querySelectorAll("div.limelight-player").forEach((videoDiv) => {
        var mediaId = videoDiv.id.substring(videoDiv.id.lastIndexOf("_") + 1);
        injectOneButton(videoDiv.parentElement, "下载这个视频", function () {
            downloadVideo(mediaId);
        });
    });
}

function injectOneButton(element, textOnButton, clickListener) {
    var btn = document.createElement("BUTTON");
    var btnText = document.createTextNode(textOnButton);
    btn.appendChild(btnText);
    btn.addEventListener("click", clickListener);
    btn.style =
        "background-color: transparent; border: solid #808080 2px; border-radius: 20px; color: #545454; margin: 0.2em";
    element.appendChild(btn);
}

function downloadOnClickHandler(article) {
    downloadImages(findEligibleImgs(article), getPrefixFromArticle(article));
}

function generateOnClickHandler(buttonsDiv, article) {
    var imgs = findEligibleImgs(article);
    var textarea = buttonsDiv.querySelector("textarea.ldh-mo-dl");
    if (!textarea) {
        textarea = document.createElement("textarea");
        textarea.className = "ldh-mo-dl";
        textarea.style = "height: 100px; width: 80%;";
        var br = document.createElement("br");
        buttonsDiv.insertBefore(br, buttonsDiv.firstChild);
        buttonsDiv.insertBefore(textarea, buttonsDiv.firstChild);
    }
    textarea.value = imgs.join("\n");
    textarea.select();
}

function downloadVideoOnClickHandler(article) {
    var elems = article.querySelectorAll("script");
    var videos = Array.from(elems)
        .filter(
            (v) =>
                v.textContent.includes("mediaId") &&
                !v.textContent.includes("blogTalkData")
        )
        .map(
            (v) =>
                JSON.parse(
                    v.textContent.substring(
                        v.textContent.indexOf("(") + 1,
                        v.textContent.lastIndexOf(")")
                    )
                ).mediaId
        );

    videos.map((video) => downloadVideo(video, getPrefixFromArticle(article)));
}

function customizedTimelinePage() {
    // 初始化
    document
        .querySelectorAll("ldh-infinite-scroll article")
        .forEach((article) => attachButtonToArticle(article));
    //
    const infiniteScrollContainer = document.querySelector("ldh-infinite-scroll");
    const config = { childList: true };
    const observer = new MutationObserver(function (mutations, observer) {
        var nodes = mutations.find((r) =>
            Array.from(r.addedNodes).filter((n) => (n.className = "article"))
        ).addedNodes;
        nodes.forEach((node) =>
            attachButtonToArticle(node.querySelector("article"))
        );
        removeProtectImg();
    });
    observer.observe(infiniteScrollContainer, config);
}

// ========================
// =    Download Utils    =
// ========================

function downloadImages(imgs, prefix = "") {
    // Thanks to https://github.com/y252328/Instagram_Download_Button
    imgs.map((img) =>
        fetch(img, {
            headers: new Headers({
                Origin: window.location.origin,
            }),
            mode: "cors",
            cache: "no-cache",
        })
            .then((response) => response.blob())
            .then((blob) =>
                dowloadBlob(
                    window.URL.createObjectURL(blob),
                    prefix + img.substring(img.lastIndexOf("/") + 1)
                )
            )
            .catch((e) => console.error(e))
    );
}

function downloadVideo(video, prefix = "") {
    const videoRequestURL =
        "https://production-ps.lvp.llnw.net/r/PlaylistService/media/<mediaId>/getMobilePlaylistByMediaId";
    fetch(videoRequestURL.replace("<mediaId>", video), {
        headers: new Headers({
            Origin: window.location.origin,
            Referer: window.location.origin,
        }),
        mode: "cors",
        cache: "no-cache",
    })
        .then((response) => response.json())
        .then(
            (response) =>
                response.mediaList[0].mobileUrls.filter(
                    (v) => v.targetMediaPlatform == "MobileH264"
                )[0].mobileUrl
        )
        .then((mobileUrl) =>
            fetch(mobileUrl.replace("http://", "https://"))
                .then((response) => response.blob())
                .then((blob) => {
                    let tempName = mobileUrl.replace("/root-message-cxf-apache", "");
                    dowloadBlob(
                        window.URL.createObjectURL(blob),
                        prefix + tempName.substring(tempName.lastIndexOf("/") + 1)
                    );
                })
                .catch((e) => console.error(e))
        );
}

function dowloadBlob(blob, filename) {
    var a = document.createElement("a");
    a.download = filename;
    a.href = blob;
    document.body.appendChild(a);
    a.click();
    a.remove();
}

// ======================
// =    Naming Utils    =
// ======================

function getPrefixFromArticle(article) {
    var candidate =
        article.querySelector(".article__head") ||
        article.querySelector(".article__header");
    if (candidate) {
        return (
            candidate.textContent
                .split(/\s/g)
                .filter((s) => s)
                .join("_") + "_"
        );
    }
    return "";
}

function sanitizeFileName(input, replacement = "_") {
    const illegalRe = /[\/\?<>\\:\*\|"]/g;
    const controlRe = /[\x00-\x1f\x80-\x9f]/g;
    const reservedRe = /^\.+$/;
    const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
    const windowsTrailingRe = /[\. ]+$/;
    return input
        .replace(illegalRe, replacement)
        .replace(controlRe, replacement)
        .replace(reservedRe, replacement)
        .replace(windowsReservedRe, replacement)
        .replace(windowsTrailingRe, replacement);
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址