Greasy Fork

Greasy Fork is available in English.

微博媒体下载

读取微博媒体的链接,发送到下载下载,并保存

目前为 2024-01-28 提交的版本,查看 最新版本

// ==UserScript==
// @name         微博媒体下载
// @namespace    http://your.namespace.com
// @version      0.2
// @description  读取微博媒体的链接,发送到下载下载,并保存
// @author       Your name
// @run-at       document-start
// @match        https://*.weibo.com/*
// @icon         https://weibo.com/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// ==/UserScript==


//页面元素监测,判断小红书笔记列表是否出现
(function () {
    // 创建一个 MutationObserver 实例
    var observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            // 检查每个变化的类型
            if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
                // 循环遍历添加的节点
                mutation.addedNodes.forEach(function (addedNode) {
                    // 检查添加的节点是否为目标元素
                    if (addedNode.classList) {
                        let like = addedNode.querySelector('[class="woo-like-main toolbar_btn_Cg9tz"]')
                        if (like) {
                            //console.log(addedNode)
                            let parentElement = like.parentElement?.parentElement?.parentElement?.parentElement?.parentElement
                            if (parentElement?.classList?.contains('woo-box-flex')) {
                                if (!parentElement.querySelector('.go')) {
                                    添加按钮(parentElement, addedNode)
                                    let share = addedNode.querySelector('[class="toolbar_share_39C6P toolbar_cursor_34j5V"]')
                                    if (share) {
                                        share.querySelector('span').textContent = '';
                                        share.style.paddingRight = "30px";
                                        share.style.paddingLeft = "30px";
                                        share.style.marginRight = "15px";

                                    }
                                }
                            }
                        } else {
                            let copy = addedNode.querySelector('[class="copy-slide-fade-enter copy-slide-fade-enter-active"]')
                            if (copy) {
                                copy.textContent = ''
                            } else {
                                let share = addedNode.querySelector('[class="share-slide-fade-enter share-slide-fade-enter-active"]')
                                if (share) {
                                    share.textContent = ''
                                } else {
                                    //console.log(1, addedNode)
                                }
                            }
                        }
                    }
                });
            }
        });
    });
    // 开始观察父节点下的变化
    observer.observe(document.body, { childList: true, subtree: true });
})();

function 添加按钮(parentElement, addedNode) {
    //return
    //广告元素
    if(addedNode.querySelector('[class="wbpro-tag head-info_tag_3iMJw"]')){
        addedNode.style.filter=' blur(4px)';
        return;
    }

    let svgparentElement = document.createElement('div');
    if (parentElement.parentElement.parentElement.classList.contains('Feed_retweetBar_3IHPj')) {
        svgparentElement.className = "personallike2";
        addedNode = addedNode.querySelector('.retweet')
        console.log(addedNode)
        //转发微博
    } else {
        svgparentElement.className = "personallike";
    }

    parentElement.appendChild(svgparentElement);
    svgparentElement.addEventListener('mouseover', function (event) {
        event.stopPropagation();
        svgparentElement.classList.add('mouseover')
    });
    svgparentElement.addEventListener('mouseout', function (event) {
        event.stopPropagation();
        svgparentElement.classList.remove('mouseover')
    });
    svgparentElement.addEventListener("click", function (event) {
        event.stopPropagation();

        获取信息发送(svgparentElement, addedNode);

    });
    svgparentElement.addEventListener('contextmenu', function (event) {
        event.preventDefault(); // 阻止默认的右键菜单弹出
        // 在这里编写处理右键点击事件的代码
        保存目录设置();
    });
    svgparentElement.innerHTML += `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" >
    <g fill="none" stroke="rgb(0, 120, 201)" class="go">
    <path stroke-width="1.5" d="M23.324 5.709.329 11.747M15.27 18.599l8.145-13.088M.431 11.774l7.563 1.749"/>
    <path stroke-width=".8" d="M21.96 6.67 7.955 13.52"/>
    <path stroke-width="2.5" d="m15.421 18.294-4.983-3.153"/>
    <path stroke-width=".8" d="m10.45 15.066 13.176-9.699M7.186 20.564l.576-6.944"/>
    <path stroke-width="1.5" d="m10.46 15.033-3.194 5.789"/></g></svg>`
}
if (!document.querySelector('.svgstyle')) {

    let css = `
        .toolbar_share_39C6P.toolbar_cursor_34j5V {
            display: none;
        }
        .personallike {
            width: 30px;
            height: 30px;
            cursor: pointer;
            margin: 8px 30px 0 0;
            user-select: none
        }
        .personallike2{
            width:30px;
            height:30px;
            cursor: pointer;
            user-select: none;
            margin: 0px 30px 0 30px;
        }
        .mouseover .go{

            stroke: #ff6f00;
        }

        [state="eorro"] .go {
            stroke: #464646 !important;
        }
        [state="wait"] .go {
            stroke: #e5b800 !important;
        }
        [state="fail"] .go {
            stroke: #2C3227 !important;
        }
        [state="complete"] .go {
            stroke: #5CE500 !important;
        }
        .savedDirectoryPath{
            background: #bcbdc2cf;
            backdrop-filter: blur(5px);
            z-index: 99;
            position: fixed;
            border-radius: 10px;
            top: calc(30vw - 45px);
            left: calc(50vw - 150px);
            width: 350px;
            height: 90px;
            height: 120px;
            user-select: none;
        }
        .pathinput {
            outline: aquamarine;
            border: none;
            width: 310px;
            height: 40px;
            margin: 10px 15px 5px;
            border-radius: 10px;
            background: #e4e4e4;
        }
        .pathinput1 {
            outline: aquamarine;
            border: none;
            width: 310px;
            height: 20px;
            margin: 10px 15px 0px;
            border-radius: 10px;
            background: #e4e4e4;
        }
        .pathbutton {
            position: relative;
            right: -250px;
            cursor: pointer;
            border-radius: 5px;
            border: none;
            font-size: 14px;
        }
        .pathbutton1 {
            position: relative;
            right: -15px;
            cursor: pointer;
            border-radius: 5px;
            border: none;
            font-size: 14px;
        }
        .pathbutton:hover {
            background: #56c1c5;
        }
        .pathbutton:active {
            background: #ff9500;
        }
        
    `
    let style = document.createElement('style')
    style.className = "svgstyle"
    style.textContent = css;
    document.body.appendChild(style)
}
function 获取信息发送(svgparentElement, msgElement) {
    try {
        let href = msgElement.querySelector('[class="head-info_time_6sFQg"]')?.href
        if (href) {
            console.dir(href)
            let 匹配, js2, js3, js4, 发帖时间, 用户名, id, ip, 微博文案, filename, url, savedDirectoryPath, 混合媒体, 单视频, 图片, 视频id,清晰度,码率;
            id = 取文件名(href);
            匹配 = false;
            js3 = [];
            js4 = {};
            for (let index = 0; index < js.length; index++) {
                js2 = js[index];
                if (js2.mblogid === id) {
                    匹配 = true;
                    发帖时间 = 转换时间(js2.created_at);
                    用户名 = js2.user?.screen_name?.trim();
                    id = js2.idstr.trim();
                    ip = js2.region_name?.trim().split(' ')[1];
                    微博文案 = js2.text_raw?.trim();
                    savedDirectoryPath = GM_getValue('directoryPath');
                    混合媒体 = js2.mix_media_info?.items;
                    单视频 = js2.page_info?.media_info.playback_list;
                    图片 = js2.pic_infos;
                    if (混合媒体?.length > 0) {
                        for (let i2 = 0; i2 < 混合媒体?.length; i2++) {
                            let 图片id = 混合媒体[i2].data?.pic_id;
                            if(混合媒体[i2].type==='video'){
                                视频id = 混合媒体[i2].data?. media_info?.media_id;
                                url = 混合媒体[i2].data?. media_info?.h265_mp4_hd;
                                filename = `${用户名}----${微博文案}----${id}----${视频id}----${ip}${发帖时间}----(${i2+1}).mp4`;
                            }else{
                                url = 混合媒体[i2].data?.largest?.url;
                                filename = `${用户名}----${微博文案}----${id}----${图片id}----${ip}${发帖时间}----(${i2+1}).JPG`;
                            }
                            filename = filename.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, '');
                            js3.push({ url: url, 文件名: filename });
                        }
                    } else {
                        if (单视频?.length > 0) {
                            url = 单视频[0].play_info?.url;
                            清晰度 = 单视频[0].play_info?.quality_desc;
                            码率 = 单视频[0].play_info?.quality_label;
                            视频id = 单视频[0].media_id;
                            filename = `${用户名}----${微博文案}----${id}----${视频id} ${清晰度}${码率}----${ip}${发帖时间}.mp4`;
                            filename = filename.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, '');
                            js3.push({ url: url, 文件名: filename });
                        } else {
                            if (图片 && js2.hasOwnProperty('pic_infos')) {
                                let 图片ID集 = Object.keys(js2.pic_infos);
                                for (let i2 = 0; i2 < 图片ID集.length; i2++) {
                                    let 图片id = 图片ID集[i2];
                                    url = js2.pic_infos[图片ID集[i2]].largest?.url;
                                    filename = `${用户名}----${微博文案}----${id}----${图片id}----${ip}${发帖时间}----(${i2+1}).JPG`;
                                    filename = filename.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, '');
                                    js3.push({ url: url, 文件名: filename });
                                }
                            }
                        }
                    }
                    js4.media = js3;
                    js4.微博文 = `《${用户名}》 \n ${微博文案}`;
                    js4.目录 = savedDirectoryPath + 用户名.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, '');
                     savedDirectoryPath = GM_getValue("directoryPath");
                    if (savedDirectoryPath) {
                        svgparentElement.setAttribute("state", "wait");
                        发送信息(JSON.stringify(js4), svgparentElement);
                    } else {
                        svgparentElement.setAttribute("state", "error");
                        保存目录设置();
                    }
                    console.log(js4, JSON.stringify(js4))
                    // 去除特殊符 (发帖用户 + “----” + 类型 + “----” + 微博文案 + “----”, )
                    // 去除特殊符 (文件名 + 微博ID + “----” + 发帖时间 + “(” + 到文本 (计次3) + “).JPG”, )
                    // let filename = `${用户名}----${微博文案}----${id}----${发帖时间}----${js.ip}${js.postingtime}----${urlname}`
                    // filename = filename.replace(/[<>:"/\\|?*\x00-\x1F\ud800-\udfff]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u2600-\u27FF]/g, '');
                    return;
                }
            }
            // if (匹配) {
            //     svgparentElement.setAttribute("state", "wait");
            //     发送信息(extractedText, "推文ID", svgparentElement);
            // } else {
            //     showToast("未找到匹配的内容。", true);
            //     svgparentElement.setAttribute("state", "error");
            // }
        }
    } catch (error) {
        console.log(error)
    }


};
function 判断服务器(服务器地址) {
    if (/^(https?:\/\/)/.test(服务器地址) === false) {
        showToast('如需要使用外部网页文件下载器,请在源码里填写服务器地址。')
        return false;
    }
    return true;
}

function 保存目录设置() {
    let div = document.createElement('div')
    div.className = 'savedDirectoryPath';
    document.body.appendChild(div);
    var ipinput = document.createElement('input');
    ipinput.className = 'pathinput1';
    ipinput.type = 'text';
    ipinput.placeholder = '服务器地址:http://150.111.42.12:5001/weibodown';
    div.appendChild(ipinput);

    ipinput.value = GM_getValue('severip', '');
    // 创建编辑框
    var input = document.createElement('input');
    input.className = 'pathinput';
    input.type = 'text';
    input.placeholder = '输入目录路径:I:\\微博影像\\';
    div.appendChild(input);
    input.value = GM_getValue('directoryPath', '');
    var closebutton = document.createElement('button');
    closebutton.className = 'pathbutton1';
    closebutton.innerHTML = '关闭';
    div.appendChild(closebutton);
    // 创建按钮
    var button = document.createElement('button');
    button.className = 'pathbutton';
    button.innerHTML = '保存';
    div.appendChild(button);
    closebutton.addEventListener('click', function (event) {
        event.stopPropagation();
        div.remove();
    })
    // 监听按钮点击事件
    button.addEventListener('click', function (event) {
        event.stopPropagation();
        var directoryPath = input.value;
        if (directoryPath) { // 检查输入框不为空
            let path = isValidDirectoryPath(directoryPath)
            if (path) { // 判断路径是否合法
                severip = ipinput.value;
                GM_setValue("directoryPath", path);
                input.value = GM_getValue('directoryPath');
                if (判断服务器(severip)) {

                    GM_setValue("severip", severip);

                    showToast('服务器地址、目录路径已保存,当前目录' + path + '\n,右键下载点击按钮重新设置路径', true);
                    div.remove();
                } else {
                    alert('请填写服务器地址');
                }
            } else {
                alert(`输入的路径不合法,请重新输入,\\需要转换成\\\\`);
            }
        } else {
            alert('请输入目录路径');
        }
    });
}
function isValidDirectoryPath(path) {
    let path2 = path.replace(path.split("\\").pop(), "")
    if (path2 === '') {
        path2 = path.replace(path.split("/").pop(), "")
    }
    path2 = path2.replace(/\\/g, '/').replace(/\/\//g, '/');
    // 去掉前后的引号和双引号
    path2 = path2.replace(/^['"]|['"]$/g, '');
    return path2;
}



function 取文件名(name) {
    const parts = name.split('/');
    return parts[parts.length - 1];
}
function 发送信息(信息, 按钮) {
    // 发送跨域 POST 请求
    console.log(GM_getValue("severip"))
    if (GM_getValue("severip") === '' || !GM_getValue("severip")) {
        showToast('请填写并保存你服务器地址。', false);
        保存目录设置()
        return;
    }
    GM_xmlhttpRequest({
        method: 'POST',
        url: GM_getValue("severip"),
        headers: {
            'Content-Type': 'application/json',
        },
        data: 信息, // 请求体数据
        onload: function (response) {
            console.log("请求完成", response.responseText);
            按钮.setAttribute("state", "complete");
            if (!response.responseText) {
                response.responseText = 信息.微博文 + '\n请求失败';
            }
            showToast(JSON.parse(response.responseText).msg['下载']['下载状态'], true);


        },
        onerror: function (error) {
            console.error("请求失败", error);
            showToast(信息 + '网络请求失败' + error, false);
            按钮.setAttribute("state", "fail");
        }
    });
}
function showToast(message, isError) {
    if (!message) {
        message = '传入信息为空。'
    }
    // 创建新的提示框
    const toastContainer = document.createElement('div');
    // 设置样式属性
    toastContainer.style.position = 'fixed';
    toastContainer.style.justifyContent = 'center';
    toastContainer.style.top = '30%';
    toastContainer.style.left = '50%';
    toastContainer.style.width = '65vw';
    toastContainer.style.transform = 'translate(-50%, -50%)';
    toastContainer.style.display = 'flex';
    toastContainer.style.padding = '5px';
    toastContainer.style.fontSize = '20px';
    toastContainer.style.background = '#e7f4ff';
    toastContainer.style.zIndex = '999';
    toastContainer.style.borderRadius = '15px';
    toastContainer.classList.add('PopupMessage'); // 设置 class 名称为 PopupMessage
    // 根据是否为错误提示框添加不同的样式
    if (isError) {
        toastContainer.classList.add('success');
        toastContainer.style.color = '#3fc91d';
    } else {
        toastContainer.classList.add('error');
        toastContainer.style.color = '#CC5500';
    }
    // 将提示框添加到页面中
    document.body.appendChild(toastContainer);
    // 获取页面高度的 20vh
    const windowHeight = window.innerHeight;
    //设置最低的高度。
    const height = windowHeight * 0.2;
    // 设置当前提示框的位置
    toastContainer.style.top = `${height}px`;
    // 在页面中插入新的信息
    const toast = document.createElement('div');
    // 使用 <br> 实现换行
    toast.innerHTML = message.replace(/\n/g, '<br>');
    toastContainer.appendChild(toast);
    // 获取所有的弹出信息元素,包括新添加的元素
    const popupMessages = document.querySelectorAll('.PopupMessage');
    // 调整所有提示框的位置
    let offset = 0;
    popupMessages.forEach(popup => {
        if (popup !== toastContainer) {
            popup.style.top = `${parseInt(popup.style.top) - toast.offsetHeight - 5}px`;
        }
        offset += popup.offsetHeight;
    });
    // 在 3 秒后隐藏提示框
    setTimeout(() => {
        toastContainer.classList.add('hide');
        // 过渡动画结束后移除提示框
        setTimeout(() => {
            toastContainer.parentNode.removeChild(toastContainer);
        }, 300);
    }, 3000);
};


监测页面请求()
function 监测页面请求() {


    // 保存原始的 XMLHttpRequest 对象
    var originalXhrOpen = XMLHttpRequest.prototype.open;
    var originalXhrSend = XMLHttpRequest.prototype.send;
    // 重写 XMLHttpRequest 的 open 方法
    XMLHttpRequest.prototype.open = function (method, url) {
        //console.log('发起网络请求:', method, url);

        // 保存请求URL
        this.__url = url;

        // 调用原始的 open 方法
        originalXhrOpen.apply(this, arguments);
    };
    // 重写 XMLHttpRequest 的 send 方法
    XMLHttpRequest.prototype.send = function (data) {
        var xhr = this;

        // 监听请求完成事件
        xhr.addEventListener('load', function () {
            // console.log('请求URL:', xhr.__url);
            // console.log('请求头:', xhr.getAllResponseHeaders());
            // console.log('响应内容:', xhr.responseText);
            数据判断(xhr.__url, xhr.responseText)
        });

        // 调用原始的 send 方法
        originalXhrSend.apply(this, arguments);
    };
    // 监听 fetch 请求
    if (window.fetch) {
        var originalFetch = window.fetch;

        window.fetch = function (url, options) {
            console.log('发起网络请求:', url, options);

            // 调用原始的 fetch 方法
            return originalFetch.apply(this, arguments)
                .then(function (response) {
                    //console.log('响应URL:', response.url);
                    //console.log('响应头:', response.headers);
                    return response.text().then(function (text) {
                        //console.log('响应内容:', text);
                        数据判断(response.url, text);
                        return new Response(text, response);
                    });
                });
        };
    }
}

var js = [];
function 数据判断(url, data) {
    try {
        // // 判断是否包含有 "comment/page" 和 "cursor=" 的 URL 请求
        // console.log('响应链接', currentUrl);
        // // 判断是否包含有 "comment/page" 和 "cursor=" 的 URL 请求
        // console.log('请求链接:', currentUrl);
        // console.log('请求协议头:', xhr.getAllResponseHeaders());
        // console.log('Cookie:', document.cookie);
        // console.log('提交数据:', data);
        // console.log('响应数据:', xhr.responseText);
        console.dir(url)
        ///
        if (url.includes('/ajax/feed/groupstimeline?list_id=') || url.includes('ajax/feed/unreadfriendstimeline?list_id=') ) {
            let statuses = JSON.parse(data).statuses
            if (statuses.length > 0) {
                for (let index = 0; index < statuses.length; index++) {
                    if (statuses[index].retweeted_status) {
                        js.push(statuses[index].retweeted_status)
                    } else {
                        js.push(statuses[index])
                    }
                }
                console.log(js)
            } else {
                console.log(2, js)
            }
        }
        if (url.includes('ajax/statuses/mymblog?uid=')) {
            let statuses = JSON.parse(data).data?.list
            if (statuses.length > 0) {
                for (let index = 0; index < statuses.length; index++) {
                    if (statuses[index].retweeted_status) {
                        js.push(statuses[index].retweeted_status)
                    } else {
                        js.push(statuses[index])
                    }
                }
                console.log(js)
            } else {
                console.log(2, js)
            }
        }
        //收藏视频
        if (url.includes('ajax/favorites/all_fav?uid=')) {
            let statuses = JSON.parse(data).data?.status
            if (statuses.length > 0) {
                for (let index = 0; index < statuses.length; index++) {
                    if (statuses[index].retweeted_status) {
                        js.push(statuses[index].retweeted_status)
                    } else {
                        js.push(statuses[index])
                    }
                }
                console.log(js)
            } else {
                console.log(2, js)
            }
        }
        //喜欢的视频
        if (url.includes('ajax/favorites/all_fav?uid=')) {
            let statuses = JSON.parse(data).data?.list
            if (statuses.length > 0) {
                for (let index = 0; index < statuses.length; index++) {
                    if (statuses[index].retweeted_status) {
                        js.push(statuses[index].retweeted_status)
                    } else {
                        js.push(statuses[index])
                    }
                }
                console.log(js)
            } else {
                console.log(2, js)
            }
        }
        if (url.includes('ajax/statuses/show?id=')) {
            let statuses = JSON.parse(data).mblogid
            if (statuses) {
                js.push(JSON.parse(data))
                console.log(js)
            } else {
                console.log(2, js)
            }
        }
    } catch (error) {
        console.log("错误信息", error)
    }

}

function 转换时间(inputDateString) {
    // 解析日期字符串
    const date = new Date(inputDateString);
    // 获取年、月、日、时、分、秒
    const year = date.getFullYear();
    const month = date.getMonth() + 1; // 月份是从0开始的,需要加1
    const day = date.getDate();
    const hours = date.getHours();
    const minutes = date.getMinutes();
    const seconds = date.getSeconds();
    // 格式化输出
    const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} ${hours.toString().padStart(2, '0')}${minutes.toString().padStart(2, '0')}${seconds.toString().padStart(2, '0')}`;
    return formattedDate;
}
(function () {
    'use strict';
})();