下载微博图片和视频(仅支持新版界面)

从新版微博界面下载图片和视频。

// ==UserScript==
// @name         Download Weibo Images & Videos (Only support new version weibo UI)
// @name:zh-CN   下载微博图片和视频(仅支持新版界面)
// @version      1.3.4
// @description  Download images and videos from new version weibo UI webpage.
// @description:zh-CN 从新版微博界面下载图片和视频。
// @author       OWENDSWANG
// @match        https://weibo.com/*
// @match        https://www.weibo.com/*
// @match        https://s.weibo.com/weibo*
// @match        https://s.weibo.com/realtime*
// @match        https://s.weibo.com/video*
// @exclude      https://weibo.com/tv/*
// @exclude      https://www.weibo.com/tv/*
// @exclude      https://weibo.com/p/*
// @exclude      https://www.weibo.com/p/*
// @icon         https://weibo.com/favicon.ico
// @license      MIT
// @homepage     https://greasyfork.org/scripts/430877
// @supportURL   https://github.com/owendswang/Download-Weibo-Images-Videos
// @grant        GM_xmlhttpRequest
// @grant        GM_notification
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @connect      weibo.com
// @connect      www.weibo.com
// @connect      sinaimg.cn
// @connect      weibocdn.com
// @connect      localhost
// @namespace    http://tampermonkey.net/
// @run-at       document-end
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
// ==/UserScript==

(function() {
    'use strict';

    const settingVersion = 1;

    let text = [];
    let text_zh = [
/*0*/   '添加下载按钮',
/*1*/   '欢迎使用“下载微博图片”脚本',
/*2*/   '请选择添加下载按钮的方式:',
/*3*/   '点击“添加下载按钮”来添加下载按钮。',
/*4*/   '当鼠标位于浏览器页面时添加下载按钮,但这种方式会占用很多CPU资源。',
/*5*/   '确定',
/*6*/   '下载设置',
/*7*/   '下载文件名称',
/*8*/   '{original} - 原文件名\n{username} - 原博主名称\n{userid} - 原博主ID\n{mblogid} - 原博mblogid\n{uid} - 原博uid\n{ext} - 文件后缀\n{index} - 图片序号\n{YYYY} {MM} {DD} {HH} {mm} {ss} - 原博发布时\n间的年份、月份、日期、小时、分钟、秒,可\n分开独立使用\n{content} - 博文内容(最多前25个字符)',
/*9*/   '下载队列',
/*10*/  '重试',
/*11*/  '关闭',
/*12*/  '取消',
/*13*/  '打包下载',
/*14*/  '打包文件名',
/*15*/  '与“下载文件名称”规则相同,但{original}、{ext}、{index}除外',
/*16*/  '单独设置转发微博下载文件名称',
/*17*/  '转发微博下载文件名称',
/*18*/  '除“下载文件名”规则外,额外标签如下:\n{re.mblogid} - 转博mblogid\n{re.username} - 转发博主名称\n{re.userid} - 转发博主ID\n{re.uid} - 转博uid\n{re.content} - 转发博文内容(最多前25个字符)\n{re.YYYY} {re.MM} {re.DD} {re.HH} {re.mm} {re.ss}\n - 原博发布时间的年份、月份、日期、小时、\n分钟、秒,可分开独立使用',
/*19*/  '转发微博打包文件名',
/*20*/  '与“转发微博下载文件名称”规则相同,但{original}、{ext}、{index}除外',
/*21*/  '使用Aria2c远程下载',
/*22*/  'RPC接口地址',
/*23*/  '使用此方式下载,无法使用打包功能,无法在页面右下角显示下载进度和结果。',
/*24*/  '如果接口地址不是localhost,需手动将地址添加到XHR白名单。',
/*25*/  '设置',
/*26*/  '<b>注意</b>:',
/*27*/  '启用“打包下载”时,需区分多文件名称,\n避免重复而导致打包后只有一个文件,文件命\n名时,必须包含{original}、{index}中至少一个\n标签。',
/*28*/  '下载视频封面',
/*29*/  '下载无水印图片',
/*30*/  '图片质量会下降',
/*31*/  '隐藏页面上的设置按钮',
/*32*/  '可在浏览器扩展Tampermonkey下拉菜单中打开\n设置窗口。',
    ];
    let text_en = [
/*0*/   'Add Download Buttons',
/*1*/   'Welcome Using \'Download Weibo Images\' Script',
/*2*/   'Which way do you like to add download buttons to each weibo post?',
/*3*/   'Click \'Add Download Buttons\' button to add download buttons.',
/*4*/   'When mouse over browser page, add download buttons automatically. But it takes a lot of CPU usage.',
/*5*/   'OK',
/*6*/   'Download Setting',
/*7*/   'Download File Name',
/*8*/   '{original} - Original file name\n{username} - Original user name\n{userid} - Original user ID\n{mblogid} - Original mblogid\n{uid} - Original uid\n{ext} - File extention\n{index} - Image index\n{YYYY} {MM} {DD} {HH} {mm} {ss} - "Year", \n"Month", "Date", "Hour", "Minute", "Second" \nof the created time of the original post\n{content} - Original post content (limited to \nfirst 25 characters)',
/*9*/   'Download Queue',
/*10*/  'Retry',
/*11*/  'Close',
/*12*/  'Cancel',
/*13*/  'Pack download files as a ZIP file',
/*14*/  'ZIP File Name',
/*15*/  'The same rules as "Download File Name" except {original}, {ext} and {index}',
/*16*/  'Different File Name for Retweets',
/*17*/  'Retweet Download File Name',
/*18*/  'Except the rules for "Download File Name", there are additional tags as below.\n{re.mblogid} - Retweet mblogid\n{re.username} - Retweet user name\n{re.userid} - Retweet user ID\n{re.uid} - Retweet uid\n{re.content} - Retweet post content (limited to first 25 characters)\n{re.YYYY} {re.MM} {re.DD} {re.HH} {re.mm} {re.ss} - "Year", "Month", "Date", "Hour", "Minute", "Second" of the created time of the retweet post',
/*19*/  'Retweet Zip File Name',
/*20*/  'The same rules as "Retweet Download File Name" except {original}, {ext} and {index}',
/*21*/  'Use Aria2c remote download API',
/*22*/  'RPC Url',
/*23*/  'In this mode, You would not be able to download in ZIP mode and to observe download progress and result.',
/*24*/  'If it\'s not \'localhost\', you would have to add it to \'XHR white list\'.',
/*25*/  'Settings',
/*26*/  '<b>Attention</b>: ',
/*27*/  'When \'ZIP mode\' enabled, you have \nto include one of the tags {original} or {index} \nto avoid duplicated ones being overwritten.',
/*28*/  'Download video cover image',
/*29*/  'Download Images without watermarks',
/*30*/  'Image quality is lower than those \nwith watermarks',
/*31*/  'Hide \'Settings\' button on the web page',
/*32*/  '\'Settings\' modal could be called \nout by click on the dropdown menu of \nTampermoneky extension.',
    ];
    if(navigator.language.substr(0, 2) == 'zh') {
        text = text_zh;
    } else {
        text = text_en;
    }

    let downloadQueueCard = document.createElement('div');
    downloadQueueCard.style.position = 'fixed';
    downloadQueueCard.style.bottom = '0.5rem';
    downloadQueueCard.style.left = '0.5rem';
    downloadQueueCard.style.maxHeight = '50vh';
    downloadQueueCard.style.overflowY = 'auto';
    downloadQueueCard.style.overflowX = 'hidden';
    let downloadQueueTitle = document.createElement('div');
    downloadQueueTitle.textContent = text[9];
    downloadQueueTitle.style.fontSize = '0.8rem';
    downloadQueueTitle.style.color = 'gray';
    downloadQueueTitle.style.display = 'none';
    downloadQueueCard.appendChild(downloadQueueTitle);
    document.body.appendChild(downloadQueueCard);
    let progressBar = document.createElement('div');
    progressBar.style.height = '1.4rem';
    progressBar.style.width = '23rem';
    // progressBar.style.background = 'linear-gradient(to right, red 100%, transparent 100%)';
    progressBar.style.borderStyle = 'solid';
    progressBar.style.borderWidth = '0.1rem';
    progressBar.style.borderColor = 'grey';
    progressBar.style.borderRadius = '0.5rem';
    progressBar.style.boxSizing = 'content-box';
    progressBar.style.marginTop = '0.5rem';
    progressBar.style.marginRight = '1rem';
    progressBar.style.position = 'relative';
    let progressText = document.createElement('div');
    // progressText.textContent = 'test.test';
    progressText.style.mixBlendMode = 'screen';
    progressText.style.width = '100%';
    progressText.style.textAlign = 'center';
    progressText.style.color = 'orange';
    progressText.style.fontSize = '0.7rem';
    progressText.style.lineHeight = '1.4rem';
    progressText.style.overflow = 'hidden';
    progressBar.appendChild(progressText);
    let progressCloseBtn = document.createElement('button');
    progressCloseBtn.style.border = 'unset';
    progressCloseBtn.style.background = 'unset';
    progressCloseBtn.style.color = 'orange';
    progressCloseBtn.style.position = 'absolute';
    progressCloseBtn.style.right = '0';
    progressCloseBtn.style.top = '0.1rem';
    progressCloseBtn.style.fontSize = '1rem';
    progressCloseBtn.style.lineHeight = '1rem';
    progressCloseBtn.style.cursor = 'pointer';
    progressCloseBtn.textContent = '×';
    progressCloseBtn.title = text[12];
    progressCloseBtn.onmouseover = function(e){
        this.style.color = 'red';
    }
    progressCloseBtn.onmouseout = function(e){
        this.style.color = 'orange';
    }
    progressBar.appendChild(progressCloseBtn);
    // downloadQueueCard.appendChild(progressBar);

    function saveAs(blob, name) {
        const link = document.createElement("a");
        link.style.display = "none";
        link.href = URL.createObjectURL(blob);
        link.download = name;
        link.target = '_blank';
        document.body.appendChild(link);
        link.click();
        const timeout = setTimeout(() => {
            URL.revokeObjectURL(link.href);
            link.parentNode.removeChild(link);
        }, 1000);
    }

    function send2Aria2c(url, fileName, headerFlag) {
        // console.log(downloadUrl);
        return new Promise(function(resolve, reject) {
            let header = [ 'User-Agent: ' + window.navigator.userAgent ];
            if (headerFlag) {
                header.push('Referer: https://' + location.host);
                header.push('Origin: https://' + location.host);
            }
            downloadQueueTitle.style.display = 'block';
            let progress = downloadQueueCard.appendChild(progressBar.cloneNode(true));
            progress.firstChild.textContent = fileName;
            GM_xmlhttpRequest({
                method: 'POST',
                url: GM_getValue('ariaRpcUrl','http://localhost:6800/jsonrpc'),
                data: JSON.stringify({
                    jsonrpc: '2.0',
                    id: 'weibo',
                    method: 'aria2.addUri',
                    params: [ [ url ], { header, out: fileName } ],
                }),
                headers: {"Content-Type": "application/json"},
                onload: function(response) {
                    // console.log(response.responseText);
                    progress.style.background = 'green';
                    const timeout = setTimeout(() => {
                        progress.remove();
                        if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    }, 1000);
                    progress.lastChild.onclick = function(e) {
                        clearTimeout(timeout);
                        this.parentNode.remove();
                        if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    }
                    resolve(response);
                },
                onabort: function(e) { resolve(null); },
                onerror: function(e) { downloadError(e, url, fileName, headerFlag, progress); resolve(null); },
                ontimeout: function(e) { downloadError(e, url, fileName, headerFlag, progress); resolve(null); },
            });
            // 下面这种原生的方法,因为安全原因,不被浏览器允许,属于跨域,且在https页面上请求http。
            /*let oReq = new XMLHttpRequest();
            oReq.open("POST", GM_getValue('ariaRpcUrl','http://localhost:6800/jsonrpc'));
            oReq.setRequestHeader('Content-Type', 'application/json')
            oReq.onload = (e) => {
                // console.log(response.responseText);
                progress.style.background = 'green';
                const timeout = setTimeout(() => {
                    progress.remove();
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                }, 1000);
                progress.lastChild.onclick = function(e) {
                    clearTimeout(timeout);
                    this.parentNode.remove();
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                }
                resolve(oReq.response);
            };
            oReq.onerror = (e) => { downloadError(e, url, fileName, headerFlag, progress); resolve(null); };
            oReq.onabort = (e) => { resolve(null); };
            oReq.ontimeout = (e) => { downloadError(e, url, fileName, headerFlag, progress); resolve(null); };
            oReq.send(JSON.stringify({
                jsonrpc: '2.0',
                id: 'weibo',
                method: 'aria2.addUri',
                params: [ [ url ], { header, out: fileName } ],
            }));
            progress.lastChild.onclick = function(e) {
                oReq.abort();
                this.parentNode.remove();
                if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
            };*/
        });
    }

    function httpRequest(url, method = 'GET', data = null) {
        return new Promise(function(resolve, reject) {
            let oReq = new XMLHttpRequest();
            oReq.open(method, url);
            oReq.responseType = 'json';
            oReq.onload = (e) => { resolve(oReq.response); };
            oReq.onerror = (e) => { resolve(null); };
            oReq.onabort = (e) => { resolve(null); };
            oReq.ontimeout = (e) => { resolve(null); };
            oReq.setRequestHeader('X-XSRF-TOKEN', getCookie('XSRF-TOKEN'));
            if(typeof(data) === 'string') {
                oReq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                oReq.send(data);
            } else if(typeof(data) === 'object') {
                oReq.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
                oReq.send(JSON.stringify(data));
            } else {
                oReq.send();
            }
        });
    }

    function gmHttpRequest(url, method = 'GET', data = null, ua = null) {
        return new Promise(function(resolve, reject) {
            // console.log(url);
            let headers = {
                'Referer': 'https://' + location.host,
                'Origin': 'https://' + location.host,
                'X-XSRF-TOKEN': getCookie('XSRF-TOKEN'),
            };
            if(typeof(data) === 'string') {
                headers['Content-Type'] = 'application/x-www-form-urlencoded';
            } else if(typeof(data) === 'object') {
                headers['Content-Type'] = 'application/json;charset=UTF-8';
            }
            if(ua) {
                headers['User-Agent'] = ua;
            }
            // console.log(headers)
            GM_xmlhttpRequest({
                method,
                url,
                data,
                responseType: 'json',
                headers,
                onload: ({ status, response }) => {
                    // console.log(response);
                    resolve(response)
                },
                onabort: function(e) { resolve(null); },
                onerror: function(e) { resolve(null); },
                ontimeout: function(e) { resolve(null); },
            });
        });
    }

    function downloadError(e, url, name, headerFlag, progress, zipMode = false, ariaMode = false) {
        // console.log(e, url);
        /*GM_notification({
            title: 'Download error',
            text: 'Error: ' + e.error + '\nUrl: ' + url,
            silent: true,
            timeout: 3,
        });*/
        progress.style.background = 'red';
        progress.firstChild.textContent = name + ' [' + (e.error || 'Unknown') + ']';
        progress.firstChild.style.color = 'yellow';
        progress.firstChild.style.mixBlendMode = 'unset';
        if (!zipMode) {
            let progressRetryBtn = document.createElement('button');
            progressRetryBtn.style.border = 'unset';
            progressRetryBtn.style.background = 'unset';
            progressRetryBtn.style.color = 'yellow';
            progressRetryBtn.style.position = 'absolute';
            progressRetryBtn.style.right = '1.2rem';
            progressRetryBtn.style.top = '0.05rem';
            progressRetryBtn.style.fontSize = '1rem';
            progressRetryBtn.style.lineHeight = '1rem';
            progressRetryBtn.style.cursor = 'pointer';
            progressRetryBtn.style.letterSpacing = '-0.2rem';
            progressRetryBtn.textContent = '⤤⤦';
            progressRetryBtn.title = text[10];
            progressRetryBtn.onmouseover = function(e){
                this.style.color = 'white';
            }
            progressRetryBtn.onmouseout = function(e){
                this.style.color = 'yellow';
            }
            progressRetryBtn.onclick = function(e) {
                this.parentNode.remove();
                if (ariaMode) {
                    send2Aria2c(url, name, headerFlag);
                } else {
                    downloadWrapper(url, name, headerFlag);
                }
            }
            progress.insertBefore(progressRetryBtn, progress.lastChild);
        }
        progress.lastChild.title = text[11];
        progress.lastChild.style.color = 'yellow';
        progress.lastChild.onmouseover = function(e){
            this.style.color = 'white';
        };
        progress.lastChild.onmouseout = function(e){
            this.style.color = 'yellow';
        };
        progress.lastChild.onclick = function(e) {
            this.parentNode.remove();
            if(progress.parent.childElementCount == 1) progress.parent.firstChild.style.display = 'none';
        };
        // setTimeout(() => { progress.remove(); if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none'; }, 1000);
    }

    function getCookie(key = null) {
        let cookiesArr = document.cookie.split('; ');
        let cookiesObj = {};
        for (const cookie of cookiesArr) {
            let [ name, value ] = cookie.split('=');
            cookiesObj[name] = value;
        }
        if (key) {
            return cookiesObj[key];
        } else {
            return cookiesObj;
        }
    }

    function downloadWrapper(url, name, headerFlag = false, zipMode = false) {
        // console.log(url);
        downloadQueueTitle.style.display = 'block';
        let progress = downloadQueueCard.appendChild(progressBar.cloneNode(true));
        progress.firstChild.textContent = name + ' [0%]';
        if (zipMode) {
            return new Promise(function(resolve, reject) {
                const download = GM_xmlhttpRequest({
                    method: 'GET',
                    url,
                    responseType: 'blob',
                    headers: headerFlag ? {
                        'Referer': 'https://' + location.host,
                        'Origin': 'https://' + location.host
                    } : null,
                    onprogress: (e) => {
                        // e = { int done, finalUrl, bool lengthComputable, int loaded, int position, int readyState, response, str responseHeaders, responseText, responseXML, int status, statusText, int total, int totalSize }
                        const percent = e.done / e.total * 100;
                        progress.style.background = 'linear-gradient(to right, green ' + percent + '%, transparent ' + percent + '%)';
                        progress.firstChild.textContent = name + ' [' + percent.toFixed(0) + '%]';
                    },
                    onload: ({ status, response }) => {
                        const timeout = setTimeout(() => {
                            progress.remove();
                            if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                        }, 1000);
                        progress.lastChild.onclick = function(e) {
                            clearTimeout(timeout);
                            this.parentNode.remove();
                            if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                        };
                        resolve(response);
                    },
                    onabort: function(e) { resolve(null); },
                    onerror: function(e) { downloadError(e, url, name, headerFlag, progress); resolve(null); },
                    ontimeout: function(e) { downloadError(e, url, name, headerFlag, progress); resolve(null); },
                });
                progress.lastChild.onclick = function(e) {
                    download.abort();
                    this.parentNode.remove();
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                };
                // 下面这种原生的方法,可以正常下载非V+的资源,遇到V+的资源会报错。
                /*let oReq = new XMLHttpRequest();
                oReq.open("GET", url);
                oReq.responseType = 'blob';
                oReq.onprogress = (e) => {
                    // console.log(e);
                    const percent = e.loaded / e.total * 100;
                    progress.style.background = 'linear-gradient(to right, green ' + percent + '%, transparent ' + percent + '%)';
                    progress.firstChild.textContent = name + ' [' + percent.toFixed(0) + '%]';
                };
                oReq.onload = (e) => {
                    const timeout = setTimeout(() => {
                        progress.remove();
                        if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    }, 1000);
                    progress.lastChild.onclick = function(e) {
                        clearTimeout(timeout);
                        this.parentNode.remove();
                        oReq.abort();
                        if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    };
                    resolve(oReq.response);
                };
                oReq.onerror = (e) => { downloadError(e, url, name, headerFlag, progress); resolve(null); };
                oReq.onabort = (e) => { resolve(null); };
                oReq.ontimeout = (e) => { downloadError(e, url, name, headerFlag, progress); resolve(null); };
                oReq.send();
                progress.lastChild.onclick = function(e) {
                    this.parentNode.remove();
                    oReq.abort();
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                };*/
            });
        } else {
            /*const download = GM_download({
                url,
                name,
                headers: headerFlag ? {
                    'Referer': 'https://' + location.host,
                    'Origin': 'https://' + location.host
                } : null,
                onprogress: (e) => {
                    // e = { int done, finalUrl, bool lengthComputable, int loaded, int position, int readyState, response, str responseHeaders, responseText, responseXML, int status, statusText, int total, int totalSize }
                    const percent = e.done / e.total * 100;
                    progress.style.background = 'linear-gradient(to right, green ' + percent + '%, transparent ' + percent + '%)';
                    progress.firstChild.textContent = name + ' [' + percent.toFixed(0) + '%]';
                },
                onload: ({ status, response }) => {
                    const timeout = setTimeout(() => {
                        progress.remove();
                        if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    }, 1000);
                    progress.lastChild.onclick = function(e) {
                        clearTimeout(timeout);
                        this.parentNode.remove();
                        if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    }
                },
                onerror: (e) => { downloadError(e, url, name, headerFlag, progress); },
                ontimeout: (e) => { downloadError(e, url, name, headerFlag, progress); },
            });
            progress.lastChild.onclick = function(e) {
                download.abort();
                this.parentNode.remove();
                if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
            };*/
            // 这个方法也可以,而且不是走GM_download,不会被油猴设置里的下载选项影响。
            const download = GM_xmlhttpRequest({
                method: 'GET',
                url,
                responseType: 'blob',
                headers: headerFlag ? {
                    'Referer': 'https://' + location.host,
                    'Origin': 'https://' + location.host
                } : null,
                onprogress: (e) => {
                    // e = { int done, finalUrl, bool lengthComputable, int loaded, int position, int readyState, response, str responseHeaders, responseText, responseXML, int status, statusText, int total, int totalSize }
                    const percent = e.done / e.total * 100;
                    progress.style.background = 'linear-gradient(to right, green ' + percent + '%, transparent ' + percent + '%)';
                    progress.firstChild.textContent = name + ' [' + percent.toFixed(0) + '%]';
                },
                onload: ({ status, response }) => {
                    const timeout = setTimeout(() => {
                        progress.remove();
                        if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    }, 1000);
                    progress.lastChild.onclick = function(e) {
                        clearTimeout(timeout);
                        this.parentNode.remove();
                        if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    };
                    saveAs(response, name);
                },
            });
            progress.lastChild.onclick = function(e) {
                download.abort();
                this.parentNode.remove();
                if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
            };
            // 下面这种原生的方法,可以正常下载非V+的资源,遇到V+的资源会报错。
            /*let oReq = new XMLHttpRequest();
            oReq.open("GET", url);
            oReq.responseType = 'blob';
            oReq.onprogress = (e) => {
                // console.log(e);
                const percent = e.loaded / e.total * 100;
                progress.style.background = 'linear-gradient(to right, green ' + percent + '%, transparent ' + percent + '%)';
                progress.firstChild.textContent = name + ' [' + percent.toFixed(0) + '%]';
            };
            oReq.onload = (e) => {
                const timeout = setTimeout(() => {
                    progress.remove();
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                }, 1000);
                progress.lastChild.onclick = function(e) {
                    clearTimeout(timeout);
                    this.parentNode.remove();
                    oReq.abort();
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                };
                saveAs(oReq.response, name);
            };
            oReq.onerror = (e) => { downloadError(e, url, name, headerFlag, progress); };
            oReq.ontimeout = (e) => { downloadError(e, url, name, headerFlag, progress); };
            oReq.send();
            progress.lastChild.onclick = function(e) {
                this.parentNode.remove();
                oReq.abort();
                if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
            };*/
            // 下面fetch的方法,感觉不是很好写,所以就不用下面的方法。
            /*(async function () {
                let controller = new AbortController();
                const response = await fetch(url, { signal: controller.signal });
                progress.lastChild.onclick = function(e) {
                    controller.abort();
                    this.parentNode.remove();
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                }
                const contentLength = response.headers.get('content-length');
                const total = parseInt(contentLength, 10);
                let loaded = 0;
                const reader = response.body.getReader();
                const res = new Response(new ReadableStream({
                    async start(controller) {
                        while(true) {
                            const { done, value } = await reader.read();
                            if (value) loaded += value.length;
                            const percent = loaded / total * 100;
                            progress.style.background = 'linear-gradient(to right, green ' + percent + '%, transparent ' + percent + '%)';
                            progress.firstChild.textContent = name + ' [' + percent.toFixed(0) + '%]';
                            if (done) {
                                break;
                                controller.close();
                            }
                        }
                    }
                }));
                const blob = await res.blob();
                const link = document.createElement("a");
                link.style.display = "none";
                link.href = URL.createObjectURL(blob);
                link.download = name;
                link.target = '_blank';
                document.body.appendChild(link);
                link.click();
                progress.style.background = 'green';
                const timeout = setTimeout(() => {
                    progress.remove();
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                    URL.revokeObjectURL(link.href);
                    link.parentNode.removeChild(link);
                }, 1000);
                progress.lastChild.onclick = function(e) {
                    clearTimeout(timeout);
                    this.parentNode.remove();
                    URL.revokeObjectURL(link.href);
                    link.parentNode.removeChild(link);
                    if(downloadQueueCard.childElementCount == 1) downloadQueueTitle.style.display = 'none';
                }
            })();*/
        }
    }

    function getName(nameSetting, originalName, ext, userName, userId, postId, postUid, index, postTime, content, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetContent) {
        let setName = nameSetting;
        setName = setName.replace('{ext}', ext);
        setName = setName.replace('{original}', originalName);
        setName = setName.replace('{username}', userName);
        setName = setName.replace('{userid}', userId);
        setName = setName.replace('{mblogid}', postId);
        setName = setName.replace('{uid}', postUid);
        setName = setName.replace('{index}', index);
        setName = setName.replace('{content}', content.substring(0, 25));
        let YYYY, MM, DD, HH, mm, ss;
        const postAt = new Date(postTime);
        if (postTime) {
            YYYY = postAt.getFullYear().toString();
            MM = (postAt.getMonth() + 1).toString().padStart(2, '0');
            DD = postAt.getDate().toString().padStart(2, '0');
            HH = postAt.getHours().toString().padStart(2, '0');
            mm = postAt.getMinutes().toString().padStart(2, '0');
            ss = postAt.getSeconds().toString().padStart(2, '0');
        }
        setName = setName.replace('{YYYY}', YYYY);
        setName = setName.replace('{MM}', MM);
        setName = setName.replace('{DD}', DD);
        setName = setName.replace('{HH}', HH);
        setName = setName.replace('{mm}', mm);
        setName = setName.replace('{ss}', ss);
        if (retweetPostId && GM_getValue('retweetMode', false)) {
            setName = setName.replace('{re.mblogid}', retweetPostId);
            setName = setName.replace('{re.username}', retweetUserName);
            setName = setName.replace('{re.userid}', retweetUserId);
            setName = setName.replace('{re.uid}', retweetPostUid);
            setName = setName.replace('{re.content}', retweetContent.substring(0, 25));
            let reYYYY, reMM, reDD, reHH, remm, ress;
            const retweetPostAt = new Date(retweetPostTime);
            if (retweetPostTime) {
                reYYYY = retweetPostAt.getFullYear().toString();
                reMM = (retweetPostAt.getMonth() + 1).toString().padStart(2, '0');
                reDD = retweetPostAt.getDate().toString().padStart(2, '0');
                reHH = retweetPostAt.getHours().toString().padStart(2, '0');
                remm = retweetPostAt.getMinutes().toString().padStart(2, '0');
                ress = retweetPostAt.getSeconds().toString().padStart(2, '0');
            }
            setName = setName.replace('{re.YYYY}', reYYYY);
            setName = setName.replace('{re.MM}', reMM);
            setName = setName.replace('{re.DD}', reDD);
            setName = setName.replace('{re.HH}', reHH);
            setName = setName.replace('{re.mm}', remm);
            setName = setName.replace('{re.ss}', ress);
        }
        return setName.replace(/[<|>|*|"|\/|\|:|?|\n]/g, '_');
    }

    function handleDownloadList(downloadList, packName) {
        if (GM_getValue('ariaMode', false)) {
            for (const item of downloadList) {
                send2Aria2c(item.url, item.name, item.headerFlag);
            }
        } else if (GM_getValue('zipMode', false)) {
            let zip = new JSZip();
            // console.log('zip', zip);
            let promises = downloadList.map(async function(ele, idx) {
                return await downloadWrapper(ele.url, ele.name, ele.headerFlag, true).then(function(data) {
                    // console.log(ele, idx, 'data', data);
                    const currDate = new Date();
                    const dateWithOffset = new Date(currDate.getTime() - currDate.getTimezoneOffset() * 60000);
                    if (data) zip.file(downloadList[idx].name, data, { date: dateWithOffset });
                });
            });
            // console.log('promises', promises);
            Promise.all(promises).then(async function(responseList) {
                // console.log('responseList', responseList);
                // console.log('zip', zip);
                // console.log('generateAsync', zip.generateAsync());
                const content = await zip.generateAsync({ type: 'blob', streamFiles: true }/*, function({ percent, currentFile }) { console.log(percent); }*/);
                // console.log('content', content);
                if (zip.files && Object.keys(zip.files).length > 0) saveAs(content, packName);
            });
        } else {
            for (const item of downloadList) {
                downloadWrapper(item.url, item.name, item.headerFlag);
            }
        }
    }

    async function handleVideo(mediaInfo, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText) {
        const newList = [];
        let largeVidUrl = mediaInfo.playback_list ? mediaInfo.playback_list[0].play_info.url : ( mediaInfo.mp4_hd_url || mediaInfo.stream_url_hd || mediaInfo.stream_url );
        if(mediaInfo.hasOwnProperty('h5_url') && mediaInfo.h5_url) {
            const urlObj = new URL(mediaInfo.h5_url); // e.g. 'https://video.weibo.com/show?fid=1034:4924511439749139'
            const fid = urlObj.searchParams.get('fid');
            let url = 'https://' + location.host + '/tv/api/component?page=/tv/show/' + fid; // e.g. 'https://weibo.com/tv/api/component?page=/tv/show/1034:4924511439749139'
            // let url = 'https://h5.video.weibo.com/api/component?page=/show/' + fid; // e.g. 'https://h5.video.weibo.com/api/component?page=/show/1034:5070572795658319'
            let data = 'data={"Component_Play_Playinfo":{"oid":"' + fid + '"}}'; // e.g. 'data={"Component_Play_Playinfo":{"oid":"1034:4924511439749139"}}'
            // console.log(url, data);
            let tvRes = await gmHttpRequest(url, 'POST', data);
            // console.log(tvRes);
            if(tvRes && tvRes.data && tvRes.data.Component_Play_Playinfo && tvRes.data.Component_Play_Playinfo.urls && Object.keys(tvRes.data.Component_Play_Playinfo.urls).length > 0) {
                largeVidUrl = tvRes.data.Component_Play_Playinfo.urls[Object.keys(tvRes.data.Component_Play_Playinfo.urls)[0]];
                if(largeVidUrl.startsWith('//')) {
                    largeVidUrl = 'http:' + largeVidUrl;
                }
            }
        }
        // console.log(largeVidUrl);
        let vidName = largeVidUrl.split('?')[0];
        vidName = vidName.split('/')[vidName.split('/').length - 1].split('?')[0];
        let originalName = vidName.split('.')[0];
        let ext = vidName.split('.')[1];
        const setName = getName((GM_getValue('retweetMode', false) && retweetPostId) ? GM_getValue('retweetFileName', '{original}.{ext}') : GM_getValue('dlFileName', '{original}.{ext}'), originalName, ext, userName, userId, postId, postUid, index.toString().padStart(padLength, '0'), postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText);
        newList.push({ url: largeVidUrl, name: setName, headerFlag: true });
        if(GM_getValue('dlVidCov', true) && mediaInfo.hasOwnProperty('big_pic_info')) {
            let picUrl = mediaInfo.big_pic_info.pic_big.url;
            let largePicUrl = picUrl.replace('/orj480/', GM_getValue('rmWtrMrk', false) ? '/oslarge/' : '/large/');
            let picName = largePicUrl.split('/')[largePicUrl.split('/').length - 1].split('?')[0];
            let originalName = picName.split('.')[0];
            let ext = picName.split('.')[1];
            const setName = getName((GM_getValue('retweetMode', false) && retweetPostId) ? GM_getValue('retweetFileName', '{original}.{ext}') : GM_getValue('dlFileName', '{original}.{ext}'), originalName, ext, userName, userId, postId, postUid, index.toString().padStart(padLength, '0'), postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText);
            newList.push({url: largePicUrl, name: setName, headerFlag: true });
        }
        return newList;
    }

    function handlePic(pic, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText) {
        let newList = [];
        let picId = pic.pic_id;
        let picUrl = pic.largest?.url || pic.pic_big?.url;
        let picSize = picUrl.split('/')[3];
        let largePicUrl = picUrl.replace('/' + picSize + '/', GM_getValue('rmWtrMrk', false) ? '/oslarge/' : '/large/');
        let downloadUrl = GM_getValue('rmWtrMrk', false) ? largePicUrl : ('https://weibo.com/ajax/common/download?pid=' + picId);
        let picName = largePicUrl.split('/')[largePicUrl.split('/').length - 1].split('?')[0];
        let originalName = picName.split('.')[0];
        let ext = picName.split('.')[1];
        const setName = getName((GM_getValue('retweetMode', false) && retweetPostId) ? GM_getValue('retweetFileName', '{original}.{ext}') : GM_getValue('dlFileName', '{original}.{ext}'), originalName, ext, userName, userId, postId, postUid, index.toString().padStart(padLength, '0'), postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText);
        newList.push({ url: downloadUrl, name: setName, headerFlag: true });
        if(pic.hasOwnProperty('video')) {
            let videoUrl = pic.video;
            let videoName = videoUrl.split('%2F')[videoUrl.split('%2F').length - 1].split('?')[0];
            videoName = videoName.split('/')[videoName.split('/').length - 1].split('?')[0];
            if (!videoName.includes('.')) videoName = videoUrl.split('/')[videoUrl.split('/').length - 1].split('?')[0];
            // console.log(videoUrl, videoName);
            let originalName = videoName.split('.')[0];
            let ext = videoName.split('.')[1];
            const setName = getName((GM_getValue('retweetMode', false) && retweetPostId) ? GM_getValue('retweetFileName', '{original}.{ext}') : GM_getValue('dlFileName', '{original}.{ext}'), originalName, ext, userName, userId, postId, postUid, index.toString().padStart(padLength, '0'), postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText);
            newList.push({ url: videoUrl, name: setName, headerFlag: true });
        }
        return newList;
    }

    function addDlBtn(footer) {
        // console.log('add download button');
        let dlBtnDiv = document.createElement('div');
        dlBtnDiv.className = 'woo-box-item-flex toolbar_item_1ky_D toolbar_cursor_34j5V';
        let divInDiv = document.createElement('div');
        divInDiv.className = 'woo-box-flex woo-box-alignCenter woo-box-justifyCenter toolbar_like_20yPI toolbar_likebox_1rLfZ toolbar_wrap_np6Ug';
        let dlBtn = document.createElement('button');
        dlBtn.className = 'woo-like-main toolbar_btn_Cg9tz download-button';
        dlBtn.setAttribute('tabindex', '0');
        dlBtn.setAttribute('title', '下载');
        // dlBtn.innerHTML = '<span class="woo-like-iconWrap"><svg class="woo-like-icon"><use xlink:href="#woo_svg_download"></use></svg></span><span class="woo-like-count">下载</span>';
        dlBtn.innerHTML = '<span class="woo-like-iconWrap"><i class="woo-font woo-font--imgSave woo-like-icon"></i></span><span class="woo-like-count">下载</span>';
        dlBtn.addEventListener('click', async function(event) {
            event.preventDefault();
            const article = this.closest('article.woo-panel-main');
            if(article) {
                // let contentRow = article.getElementsByClassName('content_row_-r5Tk')[0];
                const header = article.getElementsByTagName('header')[0];
                const postLink = header.getElementsByClassName('head-info_time_6sFQg')[0];
                let postId = postLink.href.split('/')[postLink.href.split('/').length - 1];
                const resJson = await httpRequest('https://' + location.host + '/ajax/statuses/show?id=' + postId);
                // console.log(resJson);
                let status = resJson;
                let retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText;
                if(resJson.hasOwnProperty('retweeted_status')) {
                    status = resJson.retweeted_status;
                    retweetPostId = resJson.mblogid;
                    retweetUserName = resJson.user.screen_name;
                    retweetUserId = resJson.user.idstr;
                    retweetPostUid = resJson.idstr;
                    retweetPostTime = resJson.created_at;
                    retweetText = resJson.text_raw;
                }
                postId = status.mblogid;
                const picInfos = status.pic_infos;
                const picIds = status.pic_ids;
                const mixMediaInfo = status.mix_media_info;
                const userName = status.user.screen_name;
                const userId = status.user.idstr;
                const postUid = status.idstr;
                const postTime = status.created_at;
                const text = status.text_raw;
                let downloadList = [];
                if(footer.parentElement.getElementsByTagName('video').length > 0) {
                    // console.log('download video');
                    if(resJson.page_info?.media_info) {
                        downloadList = downloadList.concat(await handleVideo(resJson.page_info.media_info, 1, userName, userId, postId, postUid, 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }
                    /*if(resJson.page_info?.pic_info && GM_getValue('dlVidCov', true)) {
                        downloadList = downloadList.concat(handlePic(resJson.page_info.pic_info, 1, userName, userId, postId, postUid, 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }*/
                }
                if (picInfos) {
                    // console.log('download images');
                    let index = 0;
                    let padLength = Object.entries(picInfos).length.toString().length;
                    for (const [id, pic] of Object.entries(picInfos)) {
                        index += 1;
                        downloadList = downloadList.concat(handlePic(pic, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }
                }
                /*if (picIds) {
                    // console.log('download images');
                    let index = 0;
                    let padLength = picIds.length.toString().length;
                    for (const picId of Object.entries(picIds)) {
                        index += 1;
                        downloadList = downloadList.concat(handlePic(picId, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }
                }*/
                if (mixMediaInfo && mixMediaInfo.items) {
                    // console.log('mix media');
                    let index = 0;
                    let padLength = Object.entries(mixMediaInfo.items).length.toString().length;
                    for (const [id, media] of Object.entries(mixMediaInfo.items)) {
                        index += 1;
                        if(media.type === 'video') {
                            downloadList = downloadList.concat(await handleVideo(media.data.media_info, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                            if (GM_getValue('dlVidCov', true)) {
                                downloadList = downloadList.concat(handlePic(media.data.pic_info, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                            }
                        } else if (media.type === 'pic') {
                            downloadList = downloadList.concat(handlePic(media.data, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                        }
                    }
                }
                const packName = getName((GM_getValue('retweetMode', false) && retweetPostId) ? GM_getValue('retweetPackFileName', '{mblogid}.zip') : GM_getValue('packFileName', '{mblogid}.zip'), '{original}', '{ext}', userName, userId, postId, postUid, '{index}', postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText);
                handleDownloadList(downloadList, packName);
            }
        });
        divInDiv.appendChild(dlBtn);
        dlBtnDiv.appendChild(divInDiv);
        footer.firstChild.firstChild.firstChild.appendChild(dlBtnDiv);
        // console.log('added download button');
    }

    function addSingleDlBtn(img, idx = 0) {
        // console.log(img);
        const imgCtn = img.parentElement;
        const dlBtn = document.createElement('div');
        dlBtn.style.color = 'dimgray';
        dlBtn.style.position = 'absolute';
        dlBtn.style.bottom = '0';
        dlBtn.style.left = '0';
        dlBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.4)';
        dlBtn.style.padding = '0.3rem';
        dlBtn.style.borderRadius = '0 8px';
        dlBtn.style.width = '1rem';
        dlBtn.style.height = '1rem';
        dlBtn.style.cursor = 'pointer';
        dlBtn.style.zIndex = '11';
        dlBtn.innerHTML = '<i class="woo-font woo-font--imgSave"></i>';
        dlBtn.addEventListener('mouseenter', (event) => { dlBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.8)'; dlBtn.style.color = 'black'; });
        dlBtn.addEventListener('mouseleave', (event) => { dlBtn.style.backgroundColor = 'rgba(255, 255, 255, 0.4)'; dlBtn.style.color = 'dimgray'; });
        dlBtn.addEventListener('click', async function(event) {
            event.stopPropagation();
            const article = this.closest('article.woo-panel-main');
            if(article) {
                // let contentRow = article.getElementsByClassName('content_row_-r5Tk')[0];
                const header = article.getElementsByTagName('header')[0];
                const postLink = header.getElementsByClassName('head-info_time_6sFQg')[0];
                let postId = postLink.href.split('/')[postLink.href.split('/').length - 1];
                const resJson = await httpRequest('https://' + location.host + '/ajax/statuses/show?id=' + postId);
                // console.log(resJson);
                let status = resJson;
                let retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText;
                if(resJson.hasOwnProperty('retweeted_status')) {
                    status = resJson.retweeted_status;
                    retweetPostId = resJson.mblogid;
                    retweetUserName = resJson.user.screen_name;
                    retweetUserId = resJson.user.idstr;
                    retweetPostUid = resJson.idstr;
                    retweetPostTime = resJson.created_at;
                    retweetText = resJson.text_raw;
                }
                postId = status.mblogid;
                const picInfos = status.pic_infos;
                const picIds = status.pic_ids;
                const mixMediaInfo = status.mix_media_info;
                const userName = status.user.screen_name;
                const userId = status.user.idstr;
                const postUid = status.idstr;
                const postTime = status.created_at;
                const text = status.text_raw;
                let downloadList = [];
                if (picInfos) {
                    // console.log('download images');
                    let padLength = Object.entries(picInfos).length.toString().length;
                    // console.log(idx, picInfos);
                    const pic = Object.entries(picInfos)[idx][1];
                    downloadList = downloadList.concat(handlePic(pic, padLength, userName, userId, postId, postUid, idx + 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                }
                /*if (picIds) {
                    // console.log('download images');
                    let padLength = picIds.length.toString().length;
                    // console.log(idx, picInfos);
                    const picId = picIds[idx];
                    downloadList = downloadList.concat(handlePic(picId, padLength, userName, userId, postId, postUid, idx + 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                }*/
                if (mixMediaInfo && mixMediaInfo.items) {
                    // console.log('mix media');
                    // console.log(mixMediaInfo.items);
                    let padLength = Object.entries(mixMediaInfo.items).length.toString().length;
                    const media = Object.entries(mixMediaInfo.items)[idx][1];
                    if(media.type === 'video') {
                        downloadList = downloadList.concat(await handleVideo(media.data.media_info, padLength, userName, userId, postId, postUid, idx + 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                        if(GM_getValue('dlVidCov', true)) {
                            downloadList = downloadList.concat(handlePic(media.data.pic_info, padLength, userName, userId, postId, postUid, idx + 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                        }
                    } else if (media.type === 'pic') {
                        downloadList = downloadList.concat(handlePic(media.data, padLength, userName, userId, postId, postUid, idx + 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }
                }
                const packName = getName((GM_getValue('retweetMode', false) && retweetPostId) ? GM_getValue('retweetPackFileName', '{mblogid}.zip') : GM_getValue('packFileName', '{mblogid}.zip'), '{original}', '{ext}', userName, userId, postId, postUid, '{index}', postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText);
                handleDownloadList(downloadList, packName);
            }
        });
        imgCtn.appendChild(dlBtn);
    }

    function sAddDlBtn(footer) {
        // console.log('add download button on search');
        const lis = footer.getElementsByTagName('li');
        for (const li of lis) {
            li.style.width = '25%';
        }
        let dlBtnLi = document.createElement('li');
        dlBtnLi.style.width = '25%';
        let aInLi = document.createElement('a');
        aInLi.className = 'woo-box-flex woo-box-alignCenter woo-box-justifyCenter';
        aInLi.setAttribute('title', '下载');
        aInLi.setAttribute('href', 'javascript:void(0);');
        let dlBtn = document.createElement('button');
        dlBtn.className = 'woo-like-main toolbar_btn download-button';
        dlBtn.innerHTML = '<span class="woo-like-iconWrap"><svg class="woo-like-icon"><use xlink:href="#woo_svg_download"></use></svg></span><span class="woo-like-count">下载</span>';
        aInLi.addEventListener('click', function(event) { event.preventDefault(); });
        dlBtn.addEventListener('click', async function(event) {
            // console.log('download');
            event.preventDefault();
            const cardWrap = this.closest('div.card-wrap');
            // console.log(cardWrap);
            const mid = cardWrap.getAttribute('mid');
            // console.log(mid);
            if(mid) {
                // console.log('https://' + location.host + '/ajax/statuses/show?id=' + mid);
                const resJson = await gmHttpRequest('https://weibo.com/ajax/statuses/show?id=' + mid);
                // console.log(resJson);
                let status = resJson;
                let retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText;
                if(resJson.hasOwnProperty('retweeted_status')) {
                    status = resJson.retweeted_status;
                    retweetPostId = resJson.mblogid;
                    retweetUserName = resJson.user.screen_name;
                    retweetUserId = resJson.user.idstr;
                    retweetPostUid = resJson.idstr;
                    retweetPostTime = resJson.created_at;
                    retweetText = resJson.text_raw;
                }
                const postId = status.mblogid;
                const picInfos = status.pic_infos;
                const picIds = status.pic_ids;
                const mixMediaInfo = status.mix_media_info;
                const userName = status.user.screen_name;
                const userId = status.user.idstr;
                const postUid = status.idstr;
                const postTime = status.created_at;
                const text = status.text_raw;
                let downloadList = [];
                if(footer.parentElement.getElementsByTagName('video').length > 0) {
                    // console.log('download video');
                    if(resJson.page_info?.media_info) {
                        downloadList = downloadList.concat(await handleVideo(resJson.page_info.media_info, 1, userName, userId, postId, postUid, 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }
                    if(resJson.page_info?.pic_info && GM_getValue('dlVidCov', true)) {
                        downloadList = downloadList.concat(handlePic(resJson.page_info.pic_info, 1, userName, userId, postId, postUid, 1, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }
                }
                if (picInfos) {
                    // console.log('download images');
                    let index = 0;
                    let padLength = Object.entries(picInfos).length.toString().length;
                    for (const [id, pic] of Object.entries(picInfos)) {
                        index += 1;
                        downloadList = downloadList.concat(handlePic(pic, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }
                }
                /*if (picIds) {
                    // console.log('download images');
                    let index = 0;
                    let padLength = picIds.length.toString().length;
                    for (const picId of picIds) {
                        index += 1;
                        downloadList = downloadList.concat(handlePic(picId, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                    }
                }*/
                if (mixMediaInfo && mixMediaInfo.items) {
                    // console.log('mix media');
                    let index = 0;
                    let padLength = Object.entries(mixMediaInfo.items).length.toString().length;
                    for (const [id, media] of Object.entries(mixMediaInfo.items)) {
                        index += 1;
                        if(media.type === 'video') {
                            downloadList = downloadList.concat(await handleVideo(media.data.media_info, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                            if(GM_getValue('dlVidCov', true)) {
                                downloadList = downloadList.concat(handlePic(media.data.pic_info, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                            }
                        } else if (media.type === 'pic') {
                            downloadList = downloadList.concat(handlePic(media.data, padLength, userName, userId, postId, postUid, index, postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText));
                        }
                    }
                }
                const packName = getName((GM_getValue('retweetMode', false) && retweetPostId) ? GM_getValue('retweetPackFileName', '{mblogid}.zip') : GM_getValue('packFileName', '{mblogid}.zip'), '{original}', '{ext}', userName, userId, postId, postUid, '{index}', postTime, text, retweetPostId, retweetUserName, retweetUserId, retweetPostUid, retweetPostTime, retweetText);
                handleDownloadList(downloadList, packName);
            }
        });
        aInLi.appendChild(dlBtn);
        dlBtnLi.appendChild(dlBtn);
        footer.firstChild.appendChild(dlBtnLi);
        // console.log('added download button');
    }
/*
    function bodyMouseOver(event) {
        if (location.host == 'weibo.com' || location.host == 'www.weibo.com') {
            // let arts = document.getElementsByTagName('article');
            const footers = document.getElementsByTagName('footer');
            for (const footer of footers) {
                if(footer.getElementsByClassName('download-button').length > 0) {
                    // console.log('already added download button');
                } else {
                    // console.log(footer.parentElement);
                    if(footer.parentElement.tagName.toLowerCase() == 'article') {
                        const article = footer.parentElement;
                        const imgs = article.getElementsByTagName('img');
                        let added = false;
                        // console.log(imgs);
                        if(imgs.length > 0) {
                            let addFlag = false;
                            for (const img of imgs) {
                                if(['woo-picture-img', 'picture_focusImg_1z5In', 'picture-viewer_pic_37YQ3'].includes(img.className)) {
                                    addFlag = true;
                                }
                            }
                            if(addFlag == true) {
                                addDlBtn(footer);
                                added = true;
                            }
                        }
                        let videos = article.getElementsByTagName('video');
                        if(videos.length > 0 && added == false) {
                            addDlBtn(footer);
                        }
                    }
                }
            }
        }
        if (location.host == 's.weibo.com') {
            // let cards = document.querySelectorAll('#pl_feedlist_index .card-wrap');
            const footers = document.querySelectorAll('#pl_feedlist_index .card-act');
            for (const footer of footers) {
                if(footer.getElementsByClassName('download-button').length > 0) {
                    // console.log('already added download button');
                } else {
                    // console.log(footer.parentElement);
                    if(footer.parentElement.className == 'card' && footer.parentElement.parentElement.className == 'card-wrap') {
                        const card = footer.parentElement;
                        let added = false;
                        const media_prev = card.querySelector('div[node-type="feed_list_media_prev"]');
                        // console.log(media_prev);
                        if (media_prev) {
                            const imgs = media_prev.getElementsByTagName('img');
                            // console.log(imgs);
                            if(imgs.length > 0) {
                                sAddDlBtn(footer);
                                added = true;
                            }
                            const videos = card.getElementsByTagName('video');
                            if(videos.length > 0 && added == false) {
                                sAddDlBtn(footer);
                            }
                        }
                    }
                }
            }
        }
    }
*/
    function handleCard(card) {
        // console.log(card);
        const footer = card.querySelectorAll('footer')[1] || card.querySelector('footer');
        const imgs = card.querySelectorAll('img.woo-picture-img,img.picture_focusImg_1z5In,img.picture-viewer_pic_37YQ3,video.picture-viewer_pic_37YQ3');
        // console.log(imgs);
        if (footer) {
            if (footer.getElementsByClassName('download-button').length > 0) {
                // console.log('already added download button');
            } else {
                // console.log(footer.parentElement);
                let added = false;
                if (imgs.length > 0) {
                    addDlBtn(footer);
                    added = true;
                    if (imgs.length > 1) {
                        for (const [ idx, img ] of Object.entries(imgs)) {
                            if (img.parentElement.getElementsByClassName('download-single-button').length === 0) {
                                if (img.className.includes('picture-viewer_pic_37YQ3')) {
                                    const previews = card.querySelectorAll('div.picture-viewer_preview_2wOSq');
                                    for (const [ index, preview ] of Object.entries(previews)) {
                                        if (preview.className.includes('picture-viewer_cur_anUEY')) {
                                            addSingleDlBtn(img, parseInt(index));
                                        }
                                    }
                                } else {
                                    addSingleDlBtn(img, parseInt(idx));
                                }
                            }
                        }
                    }
                }
                let videos = card.getElementsByTagName('video');
                if(videos.length > 0 && added == false) {
                    addDlBtn(footer);
                }
            }
        }
    }
/*
    let startButton = document.createElement('button');
    startButton.textContent = text[0];
    startButton.id = 'startButton';
    startButton.style.position = 'fixed';
    startButton.style.top = '14rem';
    startButton.style.left = '1rem';
    startButton.style.zIndex = 400;
    startButton.style.backgroundColor = 'black';
    startButton.style.color = 'lightgray';
    startButton.style.paddingLeft = '1rem';
    startButton.style.paddingRight = '1rem';
    startButton.style.paddingTop = '0.5rem';
    startButton.style.paddingBottom = '0.5rem';
    startButton.style.fontWeight = 'bold';
    startButton.style.borderWidth = '0.15rem';
    startButton.style.borderColor = 'lightgray';
    startButton.style.borderRadius = '0.4rem';
    startButton.style.borderStyle = 'solid';
    startButton.addEventListener('mouseover', function(event) {
        startButton.style.backgroundColor = 'lightgray';
        startButton.style.color = 'black';
        startButton.style.borderColor = 'black';
    });
    startButton.addEventListener('mouseout', function(event) {
        startButton.style.backgroundColor = 'black';
        startButton.style.color = 'lightgray';
        startButton.style.borderColor = 'lightgray';
    });
    startButton.addEventListener('mousedown', function(event) {
        startButton.style.backgroundColor = 'gray';
    });
    startButton.addEventListener('mouseup', function(event) {
        startButton.style.backgroundColor = 'lightgray';
    });
    startButton.addEventListener('click', bodyMouseOver);

    function addStartButton() {
        document.body.appendChild(startButton);
        document.body.removeEventListener('mouseover', bodyMouseOver)
    }

    function addEventListener() {
        document.body.addEventListener('mouseover', bodyMouseOver);
        if(document.getElementById('startButton')) {
            document.body.removeChild(startButton);
        }
    }
*/
    // let addDlBtnMode = GM_getValue('addDlBtnMode', 0);

    function showModal(event) {
        // console.log(addDlBtnMode);
        let bg = document.createElement('div');
        bg.style.position = 'fixed';
        bg.style.top = 0;
        bg.style.left = 0;
        bg.style.zIndex = 500;
        bg.style.backgroundColor = 'black';
        bg.style.opacity = 0.5;
        let modal = document.createElement('div');
        document.body.appendChild(bg);
        modal.style.position = 'fixed';
        modal.style.width = '25rem';
        modal.style.height = 'auto';
        modal.style.maxHeight = '80vh';
        modal.style.zIndex = 600;
        modal.style.backgroundColor = 'white';
        modal.style.borderStyle = 'solid';
        modal.style.borderWidth = '0.2rem';
        modal.style.borderRadius = '0.5rem';
        modal.style.borderColor = 'black';
        modal.style.overflowX = 'hidden';
        modal.style.overflowY = 'auto';
        modal.style.fontSize = '1rem';
        let titleBar = document.createElement('div');
        titleBar.textContent = text[1];
        titleBar.style.width = '100%';
        titleBar.style.textAlign = 'center';
        titleBar.style.backgroundColor = 'black';
        titleBar.style.color = 'white';
        titleBar.style.fontSize = '1rem';
        titleBar.style.fontWeight = 'bold';
        titleBar.style.paddingTop = '0.5rem';
        titleBar.style.paddingBottom = '0.5rem';
        titleBar.style.borderTopLeftRadius = '0.3rem';
        titleBar.style.borderTopRightRadius = '0.3rem';
        modal.appendChild(titleBar);
        /*let question1 = document.createElement('p');
        question1.textContent = text[2];
        question1.style.paddingLeft = '2rem';
        question1.style.paddingRight = '2rem';
        question1.style.marginTop = '1rem';
        question1.style.marginBottom = '1rem';
        let chooseButton = document.createElement('input');
        chooseButton.type = 'radio';
        chooseButton.id = 'chooseButton';
        chooseButton.name = 'chooseSetting';
        chooseButton.value = 1;
        chooseButton.style.margin = '0.5rem 0.5rem 0 0.5rem';
        let labelForChooseButton = document.createElement('label');
        labelForChooseButton.htmlFor = 'chooseButton';
        labelForChooseButton.textContent = text[3];
        let divForChooseButton = document.createElement('div');
        divForChooseButton.appendChild(chooseButton);
        divForChooseButton.appendChild(labelForChooseButton);
        question1.appendChild(divForChooseButton);
        let chooseEvent = document.createElement('input');
        chooseEvent.type = 'radio';
        chooseEvent.id = 'chooseEvent';
        chooseEvent.name = 'chooseSetting';
        chooseEvent.value = 2;
        chooseEvent.style.margin = '0.5rem 0.5rem 0 0.5rem';
        if (addDlBtnMode == 2) {
            chooseEvent.checked = true;
        } else {
            chooseButton.checked = true;
        }
        let labelForChooseEvent = document.createElement('label');
        labelForChooseEvent.htmlFor = 'chooseEvent';
        labelForChooseEvent.textContent = text[4];
        let divForChooseEvent = document.createElement('div');
        divForChooseEvent.appendChild(chooseEvent);
        divForChooseEvent.appendChild(labelForChooseEvent);
        question1.appendChild(divForChooseEvent);
        modal.appendChild(question1);*/
        let question2 = document.createElement('p');
        question2.style.paddingLeft = '2rem';
        question2.style.paddingRight = '2rem';
        question2.style.marginTop = '1rem';
        question2.style.marginBottom = '1rem';
        let labelFileName = document.createElement('label');
        labelFileName.textContent = text[7];
        labelFileName.setAttribute('for', 'dlFileName');
        labelFileName.style.color = "black";
        question2.appendChild(labelFileName);
        let inputFileName = document.createElement('input');
        inputFileName.type = 'text';
        inputFileName.id = 'dlFileName';
        inputFileName.name = 'dlFileName';
        inputFileName.style.marginTop = '0.5rem';
        inputFileName.style.width = 'calc(100% - 1rem)';
        inputFileName.style.padding = '0.1rem 0.2rem 0.1rem 0.2rem';
        inputFileName.style.borderStyle = 'solid';
        inputFileName.style.borderColor = 'gray';
        inputFileName.style.borderWidth = '0.14rem';
        inputFileName.style.borderRadius = '0.2rem';
        inputFileName.defaultValue = GM_getValue('dlFileName', '{original}.{ext}');
        question2.appendChild(inputFileName);
        let fileNameExplain1 = document.createElement('p');
        fileNameExplain1.innerHTML = text[8];
        fileNameExplain1.style.marginTop = '0.5rem';
        fileNameExplain1.style.marginBottom = '0';
        fileNameExplain1.style.whiteSpace = 'pre';
        fileNameExplain1.style.color = 'gray';
        question2.appendChild(fileNameExplain1);
        let fileNameExplain2 = document.createElement('p');
        fileNameExplain2.innerHTML = text[26] + text[27];
        fileNameExplain2.style.marginTop = '0.5rem';
        fileNameExplain2.style.whiteSpace = 'pre';
        fileNameExplain2.style.color = 'gray';
        question2.appendChild(fileNameExplain2);
        modal.appendChild(question2);
        let question3 = document.createElement('p');
        question3.style.paddingLeft = '2rem';
        question3.style.paddingRight = '2rem';
        question3.style.marginTop = '1rem';
        question3.style.marginBottom = '0';
        let labelZipMode = document.createElement('label');
        labelZipMode.setAttribute('for', 'zipMode');
        labelZipMode.textContent = text[13];
        labelZipMode.style.display = 'inline-block';
        labelZipMode.style.paddingRight = '0.2rem';
        labelZipMode.style.color = GM_getValue('ariaMode', false) ? "gray" : "black";
        question3.appendChild(labelZipMode);
        let inputZipMode = document.createElement('input');
        inputZipMode.type = 'checkbox';
        inputZipMode.id = 'zipMode';
        inputZipMode.checked = GM_getValue('zipMode', false);
        inputZipMode.disabled = GM_getValue('ariaMode', false);
        question3.appendChild(inputZipMode);
        let labelPackName = document.createElement('label');
        labelPackName.textContent = text[14];
        labelPackName.setAttribute('for', 'packFileName');
        labelPackName.style.display = 'block';
        labelPackName.style.marginTop = '0.5rem';
        labelPackName.style.color = (GM_getValue('zipMode', false) && !GM_getValue('ariaMode', false)) ? "black" : "gray";
        // labelPackName.style.display = GM_getValue('zipMode', false) ? 'block' : 'none';
        question3.appendChild(labelPackName);
        let inputPackName = document.createElement('input');
        inputPackName.type = 'text';
        inputPackName.id = 'packFileName';
        inputPackName.name = 'packFileName';
        inputPackName.style.marginTop = '0.5rem';
        inputPackName.style.width = 'calc(100% - 1rem)';
        inputPackName.style.padding = '0.1rem 0.2rem 0.1rem 0.2rem';
        inputPackName.style.borderStyle = 'solid';
        inputPackName.style.borderColor = (GM_getValue('zipMode', false) && !GM_getValue('ariaMode', false)) ? 'black' : 'gray';
        inputPackName.style.borderWidth = '0.14rem';
        inputPackName.style.borderRadius = '0.2rem';
        inputPackName.defaultValue = GM_getValue('packFileName', '{mblogid}.zip');
        // inputPackName.style.display = GM_getValue('zipMode', false) ? 'block' : 'none';
        inputPackName.disabled = (GM_getValue('zipMode', false) && !GM_getValue('ariaMode', false)) ? false : true;
        question3.appendChild(inputPackName);
        let filePackExplain = document.createElement('p');
        filePackExplain.textContent = text[15];
        filePackExplain.style.marginTop = '0.5rem';
        filePackExplain.style.marginBottom = '0';
        filePackExplain.style.color = 'gray';
        // filePackExplain.style.display = GM_getValue('zipMode', false) ? 'block' : 'none';
        question3.appendChild(filePackExplain);
        modal.appendChild(question3);
        let question4 = document.createElement('p');
        question4.style.paddingLeft = '2rem';
        question4.style.paddingRight = '2rem';
        question4.style.marginTop = '1rem';
        question4.style.marginBottom = '0';
        let labelRetweetMode = document.createElement('label');
        labelRetweetMode.setAttribute('for', 'retweetMode');
        labelRetweetMode.textContent = text[16];
        labelRetweetMode.style.display = 'inline-block';
        labelRetweetMode.style.paddingRight = '0.2rem';
        labelRetweetMode.style.color = "black";
        question4.appendChild(labelRetweetMode);
        let inputRetweetMode = document.createElement('input');
        inputRetweetMode.type = 'checkbox';
        inputRetweetMode.id = 'retweetMode';
        inputRetweetMode.checked = GM_getValue('retweetMode', false);
        question4.appendChild(inputRetweetMode);
        let labelRetweetFileName = document.createElement('label');
        labelRetweetFileName.textContent = text[17];
        labelRetweetFileName.setAttribute('for', 'retweetFileName');
        labelRetweetFileName.style.display = 'block';
        labelRetweetFileName.style.marginTop = '0.5rem';
        labelRetweetFileName.style.color = GM_getValue('retweetMode', false) ? "black" : 'gray';
        // labelPackName.style.display = GM_getValue('retweetMode', false) ? 'block' : 'none';
        question4.appendChild(labelRetweetFileName);
        let inputRetweetFileName = document.createElement('input');
        inputRetweetFileName.type = 'text';
        inputRetweetFileName.id = 'retweetFileName';
        inputRetweetFileName.name = 'retweetFileName';
        inputRetweetFileName.style.marginTop = '0.5rem';
        inputRetweetFileName.style.width = 'calc(100% - 1rem)';
        inputRetweetFileName.style.padding = '0.1rem 0.2rem 0.1rem 0.2rem';
        inputRetweetFileName.style.borderStyle = 'solid';
        inputRetweetFileName.style.borderColor = 'lightgray';
        inputRetweetFileName.style.borderWidth = '0.14rem';
        inputRetweetFileName.style.borderRadius = '0.2rem';
        inputRetweetFileName.defaultValue = GM_getValue('retweetFileName', '{original}.{ext}');
        // inputRetweetFileName.style.display = GM_getValue('retweetMode', false) ? 'block' : 'none';
        inputRetweetFileName.disabled = GM_getValue('retweetMode', false) ? false : true;
        question4.appendChild(inputRetweetFileName);
        let retweetFileNameExplain = document.createElement('p');
        retweetFileNameExplain.textContent = text[18];
        retweetFileNameExplain.style.marginTop = '0.5rem';
        retweetFileNameExplain.style.whiteSpace = 'pre';
        retweetFileNameExplain.style.marginBottom = '0';
        retweetFileNameExplain.style.color = 'gray';
        // retweetFileNameExplain.style.display = GM_getValue('retweetMode', false) ? 'block' : 'none';
        question4.appendChild(retweetFileNameExplain);
        let labelRetweetPackName = document.createElement('label');
        labelRetweetPackName.textContent = text[19];
        labelRetweetPackName.setAttribute('for', 'retweetPackFileName');
        labelRetweetPackName.style.display = 'block';
        labelRetweetPackName.style.marginTop = '0.5rem';
        labelRetweetPackName.style.color = (GM_getValue('zipMode', false) && GM_getValue('retweetMode', false) && !GM_getValue('ariaMode', false)) ? "black" : 'gray';
        // labelRetweetPackName.style.display = GM_getValue('zipMode', false) ? 'block' : 'none';
        question4.appendChild(labelRetweetPackName);
        let inputRetweetPackName = document.createElement('input');
        inputRetweetPackName.type = 'text';
        inputRetweetPackName.id = 'retweetPackFileName';
        inputRetweetPackName.name = 'retweetPackFileName';
        inputRetweetPackName.style.marginTop = '0.5rem';
        inputRetweetPackName.style.width = 'calc(100% - 1rem)';
        inputRetweetPackName.style.padding = '0.1rem 0.2rem 0.1rem 0.2rem';
        inputRetweetPackName.style.borderStyle = 'solid';
        inputRetweetPackName.style.borderColor = 'lightgray';
        inputRetweetPackName.style.borderWidth = '0.14rem';
        inputRetweetPackName.style.borderRadius = '0.2rem';
        inputRetweetPackName.defaultValue = GM_getValue('retweetPackFileName', '{mblogid}.zip');
        // inputRetweetPackName.style.display = (GM_getValue('zipMode', false) && GM_getValue('retweetMode', false) && !GM_getValue('ariaMode', false)) ? 'block' : 'none';
        inputRetweetPackName.disabled = (GM_getValue('zipMode', false) && GM_getValue('retweetMode', false) && !GM_getValue('ariaMode', false)) ? false : true;
        question4.appendChild(inputRetweetPackName);
        let retweetPackExplain = document.createElement('p');
        retweetPackExplain.textContent = text[20];
        retweetPackExplain.style.marginTop = '0.5rem';
        retweetPackExplain.style.marginBottom = '0';
        retweetPackExplain.style.color = 'gray';
        // retweetPackExplain.style.display = (GM_getValue('zipMode', false) && GM_getValue('retweetMode', false) && !GM_getValue('ariaMode', false)) ? 'block' : 'none';
        question4.appendChild(retweetPackExplain);
        modal.appendChild(question4);
        let question5 = document.createElement('p');
        question5.style.paddingLeft = '2rem';
        question5.style.paddingRight = '2rem';
        question5.style.marginTop = '1rem';
        question5.style.marginBottom = '0';
        let labelAriaMode = document.createElement('label');
        labelAriaMode.setAttribute('for', 'ariaMode');
        labelAriaMode.textContent = text[21];
        labelAriaMode.style.display = 'inline-block';
        labelAriaMode.style.paddingRight = '0.2rem';
        labelAriaMode.style.color = 'black';
        question5.appendChild(labelAriaMode);
        let inputAriaMode = document.createElement('input');
        inputAriaMode.type = 'checkbox';
        inputAriaMode.id = 'ariaMode';
        inputAriaMode.checked = GM_getValue('ariaMode', false);
        question5.appendChild(inputAriaMode);
        let ariaModeExplain = document.createElement('p');
        ariaModeExplain.textContent = text[23];
        ariaModeExplain.style.marginTop = '0.5rem';
        ariaModeExplain.style.marginBottom = '0';
        ariaModeExplain.style.color = 'gray';
        question5.appendChild(ariaModeExplain);
        let labelAriaRpcUrl = document.createElement('label');
        labelAriaRpcUrl.textContent = text[22];
        labelAriaRpcUrl.setAttribute('for', 'ariaRpcUrl');
        labelAriaRpcUrl.style.display = 'block';
        labelAriaRpcUrl.style.marginTop = '0.5rem';
        labelAriaRpcUrl.style.color = GM_getValue('ariaMode', false) ? "black" : "gray";
        question5.appendChild(labelAriaRpcUrl);
        let inputAriaRpcUrl = document.createElement('input');
        inputAriaRpcUrl.type = 'text';
        inputAriaRpcUrl.id = 'ariaRpcUrl';
        inputAriaRpcUrl.name = 'ariaRpcUrl';
        inputAriaRpcUrl.style.marginTop = '0.5rem';
        inputAriaRpcUrl.style.width = 'calc(100% - 1rem)';
        inputAriaRpcUrl.style.padding = '0.1rem 0.2rem 0.1rem 0.2rem';
        inputAriaRpcUrl.style.borderStyle = 'solid';
        inputAriaRpcUrl.style.borderColor = 'lightgray';
        inputAriaRpcUrl.style.borderWidth = '0.14rem';
        inputAriaRpcUrl.style.borderRadius = '0.2rem';
        inputAriaRpcUrl.defaultValue = GM_getValue('ariaRpcUrl', 'http://localhost:6800/jsonrpc');
        // inputAriaRpcUrl.style.display = GM_getValue('ariaMode', false) ? 'block' : 'none';
        inputAriaRpcUrl.disabled = GM_getValue('ariaMode', false) ? false : true;
        question5.appendChild(inputAriaRpcUrl);
        let inputAriaExplain = document.createElement('p');
        inputAriaExplain.textContent = text[24];
        inputAriaExplain.style.marginTop = '0.5rem';
        inputAriaExplain.style.marginBottom = '0';
        inputAriaExplain.style.color = 'gray';
        question5.appendChild(inputAriaExplain);
        modal.appendChild(question5);
        let question6 = document.createElement('p');
        question6.style.paddingLeft = '2rem';
        question6.style.paddingRight = '2rem';
        question6.style.marginTop = '1rem';
        question6.style.marginBottom = '0';
        let labelDlVidCov = document.createElement('label');
        labelDlVidCov.setAttribute('for', 'dlVidCov');
        labelDlVidCov.textContent = text[28];
        labelDlVidCov.style.display = 'inline-block';
        labelDlVidCov.style.paddingRight = '0.2rem';
        labelDlVidCov.style.color = "black";
        question6.appendChild(labelDlVidCov);
        let inputDlVidCov = document.createElement('input');
        inputDlVidCov.type = 'checkbox';
        inputDlVidCov.id = 'dlVidCov';
        inputDlVidCov.checked = GM_getValue('dlVidCov', true);
        question6.appendChild(inputDlVidCov);
        modal.appendChild(question6);
        let question7 = document.createElement('p');
        question7.style.paddingLeft = '2rem';
        question7.style.paddingRight = '2rem';
        question7.style.marginTop = '1rem';
        question7.style.marginBottom = '0';
        let labelRmWtrMrk = document.createElement('label');
        labelRmWtrMrk.setAttribute('for', 'rmWtrMrk');
        labelRmWtrMrk.textContent = text[29];
        labelRmWtrMrk.style.display = 'inline-block';
        labelRmWtrMrk.style.paddingRight = '0.2rem';
        labelRmWtrMrk.style.color = "black";
        question7.appendChild(labelRmWtrMrk);
        let inputRmWtrMrk = document.createElement('input');
        inputRmWtrMrk.type = 'checkbox';
        inputRmWtrMrk.id = 'rmWtrMrk';
        inputRmWtrMrk.checked = GM_getValue('rmWtrMrk', false);
        question7.appendChild(inputRmWtrMrk);
        let rmWtrMrkExplain = document.createElement('p');
        rmWtrMrkExplain.innerHTML = text[30];
        rmWtrMrkExplain.style.marginTop = '0.5rem';
        rmWtrMrkExplain.style.marginBottom = '0';
        rmWtrMrkExplain.style.whiteSpace = 'pre';
        rmWtrMrkExplain.style.color = 'gray';
        question7.appendChild(rmWtrMrkExplain);
        modal.appendChild(question7);
        let question8 = document.createElement('p');
        question8.style.paddingLeft = '2rem';
        question8.style.paddingRight = '2rem';
        question8.style.marginTop = '1rem';
        question8.style.marginBottom = '0';
        let labelHidSetBtn = document.createElement('label');
        labelHidSetBtn.setAttribute('for', 'hidSetBtn');
        labelHidSetBtn.textContent = text[31];
        labelHidSetBtn.style.display = 'inline-block';
        labelHidSetBtn.style.paddingRight = '0.2rem';
        labelHidSetBtn.style.color = "black";
        question8.appendChild(labelHidSetBtn);
        let inputHidSetBtn = document.createElement('input');
        inputHidSetBtn.type = 'checkbox';
        inputHidSetBtn.id = 'hidSetBtn';
        inputHidSetBtn.checked = GM_getValue('hidSetBtn', false);
        question8.appendChild(inputHidSetBtn);
        let hidSetBtnExplain = document.createElement('p');
        hidSetBtnExplain.innerHTML = text[32];
        hidSetBtnExplain.style.marginTop = '0.5rem';
        hidSetBtnExplain.style.marginBottom = '0';
        hidSetBtnExplain.style.whiteSpace = 'pre';
        hidSetBtnExplain.style.color = 'gray';
        question8.appendChild(hidSetBtnExplain);
        modal.appendChild(question8);
        inputRetweetMode.addEventListener('change', function(event) {
            if (event.currentTarget.checked) {
                // labelRetweetFileName.style.display = 'block';
                // inputRetweetFileName.style.display = 'block';
                // retweetFileNameExplain.style.display = 'block';
                inputRetweetFileName.disabled = false;
                labelRetweetFileName.style.color = "black";
                inputRetweetFileName.style.borderColor = 'gray';
            } else {
                // labelRetweetFileName.style.display = 'none';
                // inputRetweetFileName.style.display = 'none';
                // retweetFileNameExplain.style.display = 'none';
                inputRetweetFileName.disabled = true;
                labelRetweetFileName.style.color = 'gray';
                inputRetweetFileName.style.borderColor = 'lightgray';
            }
            if (event.currentTarget.checked && inputZipMode.checked && !inputAriaMode.checked) {
                inputRetweetPackName.disabled = false;
                labelRetweetPackName.style.color = "black";
                inputRetweetPackName.style.borderColor = 'gray';
            } else {
                inputRetweetPackName.disabled = true;
                labelRetweetPackName.style.color = 'gray';
                inputRetweetPackName.style.borderColor = 'lightgray';
            }
        });
        inputZipMode.addEventListener('change', function(event) {
            if (event.currentTarget.checked) {
                // labelPackName.style.display = 'block';
                // inputPackName.style.display = 'block';
                // filePackExplain.style.display = 'block';
                inputPackName.disabled = false;
                labelPackName.style.color = "black";
                inputPackName.style.borderColor = 'gray';
                inputAriaMode.disabled = true;
                labelAriaMode.style.color = "gray";
                // inputAriaMode.checked = false;
            } else {
                // labelPackName.style.display = 'none';
                // inputPackName.style.display = 'none';
                // filePackExplain.style.display = 'none';
                inputPackName.disabled = true;
                labelPackName.style.color = 'gray';
                inputPackName.style.borderColor = 'lightgray';
                inputAriaMode.disabled = false;
                labelAriaMode.style.color = "black";
            }
            if (event.currentTarget.checked && inputRetweetMode.checked) {
                inputRetweetPackName.disabled = false;
                labelRetweetPackName.style.color = "black";
                inputRetweetPackName.style.borderColor = 'gray';
            } else {
                inputRetweetPackName.disabled = true;
                labelRetweetPackName.style.color = 'gray';
                inputRetweetPackName.style.borderColor = 'lightgray';
            }
        });
        inputAriaMode.addEventListener('change', function(event) {
            if (event.currentTarget.checked) {
                // labelAriaRpcUrl.style.display = 'block';
                // inputAriaRpcUrl.style.display = 'block';
                inputAriaRpcUrl.disabled = false;
                labelAriaRpcUrl.style.color = "black";
                inputAriaRpcUrl.style.borderColor = 'gray';
                inputZipMode.disabled = true;
                labelZipMode.style.color = 'gray';
                // inputZipMode.checked = false;
            } else {
                // labelAriaRpcUrl.style.display = 'none';
                // inputAriaRpcUrl.style.display = 'none';
                inputAriaRpcUrl.disabled = true;
                labelAriaRpcUrl.style.color = 'gray';
                inputAriaRpcUrl.style.borderColor = 'lightgray';
                inputZipMode.disabled = false;
                labelZipMode.style.color = "black";
            }
            if (!event.currentTarget.checked && inputZipMode.checked) {
                inputPackName.disabled = false;
                labelPackName.style.color = "black";
                inputPackName.style.borderColor = 'gray';
            } else {
                inputPackName.disabled = true;
                labelPackName.style.color = 'gray';
                inputPackName.style.borderColor = 'lightgray';
            }
            if (!event.currentTarget.checked && inputZipMode.checked && inputRetweetMode.checked) {
                inputRetweetPackName.disabled = false;
                labelRetweetPackName.style.color = "black";
                inputRetweetPackName.style.borderColor = 'gray';
            } else {
                inputRetweetPackName.disabled = true;
                labelRetweetPackName.style.color = 'gray';
                inputRetweetPackName.style.borderColor = 'lightgray';
            }
        });
        let okButton = document.createElement('button');
        okButton.textContent = text[5];
        okButton.style.paddingTop = '0.5rem';
        okButton.style.paddingBottom = '0.5rem';
        okButton.style.margin = '2rem';
        okButton.style.backgroundColor = 'darkblue';
        okButton.style.color = 'white';
        okButton.style.fontSize = '1.5rem';
        okButton.style.fontWeight = 'bold';
        okButton.style.width = '21rem';
        okButton.style.borderStyle = 'solid';
        okButton.style.borderRadius = '0.5rem';
        okButton.style.borderColor = 'black';
        okButton.style.borderWidth = '0.2rem';
        okButton.addEventListener('mouseover', function(event) {
            okButton.style.backgroundColor = 'blue';
        });
        okButton.addEventListener('mouseout', function(event) {
            okButton.style.backgroundColor = 'darkblue';
        });
        okButton.addEventListener('mousedown', function(event) {
            okButton.style.backgroundColor = 'darkblue';
        });
        okButton.addEventListener('mouseover', function(event) {
            okButton.style.backgroundColor = 'blue';
        });
        function resizeWindow(event) {
            // console.log('resize');
            bg.style.width = document.documentElement.clientWidth.toString() + 'px';
            bg.style.height = document.documentElement.clientHeight.toString() + 'px';
            modal.style.top = (( document.documentElement.clientHeight - modal.offsetHeight ) / 2).toString() + 'px';
            modal.style.left = (( document.documentElement.clientWidth - modal.offsetWidth ) / 2).toString() + 'px';
        }
        okButton.addEventListener('click', function(event) {
            /*if(document.getElementById('chooseButton').checked == true) {
                GM_setValue('addDlBtnMode', 1);
                addDlBtnMode = 1;
                addStartButton();
            } else {
                GM_setValue('addDlBtnMode', 2);
                addDlBtnMode = 2;
                addEventListener();
            }*/
            if (document.getElementById('zipMode').checked && !document.getElementById('dlFileName').value.includes('{original}') && !document.getElementById('dlFileName').value.includes('{index}')) {
                alert(text[27].replaceAll(/\n/g, ''));
                document.getElementById('dlFileName').focus();
                return;
            }
            GM_setValue('dlFileName', document.getElementById('dlFileName').value);
            GM_setValue('retweetMode', document.getElementById('retweetMode').checked);
            GM_setValue('retweetFileName', document.getElementById('retweetFileName').value);
            GM_setValue('zipMode', document.getElementById('zipMode').checked);
            GM_setValue('packFileName', document.getElementById('packFileName').value);
            GM_setValue('retweetPackFileName', document.getElementById('retweetPackFileName').value);
            GM_setValue('ariaMode', document.getElementById('ariaMode').checked);
            GM_setValue('ariaRpcUrl', document.getElementById('ariaRpcUrl').value);
            GM_setValue('dlVidCov', document.getElementById('dlVidCov').checked);
            GM_setValue('rmWtrMrk', document.getElementById('rmWtrMrk').checked);
            GM_setValue('hidSetBtn', document.getElementById('hidSetBtn').checked);
            GM_setValue('isSet', settingVersion);
            let setBtn = document.getElementById('wbDlSetBtn');
            if (setBtn) {
                setBtn.style.display = document.getElementById('hidSetBtn').checked ? 'none' : 'block';
            }
            document.body.removeChild(modal);
            document.body.removeChild(bg);
            window.removeEventListener('resize', resizeWindow);
        });
        modal.appendChild(okButton);
        document.body.appendChild(modal);
        /*bg.addEventListener('click', function(event) {
            document.body.removeChild(modal);
            document.body.removeChild(bg);
            window.removeEventListener('resize', resizeWindow);
        });*/
        resizeWindow();
        window.addEventListener('resize', resizeWindow);
    }

    let svg = document.getElementById('__SVG_SPRITE_NODE__');
    let symbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol');
    symbol.id = 'woo_svg_download';
    symbol.setAttribute('viewBox', '0 0 100 100');
    symbol.innerHTML = '<path d="m25,0l50,0l0,50l25,0l-50,50l-50,-50l25,0l0,-50" fill="currentColor"></path><path d="m30,5l40,0l0,50l20,0l-40,40l-40,-40l20,0l0,-50" fill="white"></path>';
    svg.appendChild(symbol);
/*
    if(addDlBtnMode == 0) {
        showModal();
    } else if (addDlBtnMode == 1) {
        addStartButton();
    } else if (addDlBtnMode == 2) {
        addEventListener();
    }
*/
    if(GM_getValue('isSet', null) !== settingVersion) {
        showModal();
    }
    new MutationObserver((mutationList, observer) => {
        // console.log(mutationList);
        if (location.host == 'weibo.com' || location.host == 'www.weibo.com') {
            const cards = document.body.querySelectorAll('article.woo-panel-main');
            // console.log(cards);
            for (const card of cards) {
                handleCard(card);
            }
            for (const mutation of mutationList) {
                // console.log(mutation.target);
                if (mutation.type === 'childList' && mutation.target.tagName === 'DIV' && (mutation.target.className.includes('wbpro-feed-content') || mutation.target.className.includes('Feed_retweet_JqZJb'))) {
                    for (const node of mutation.addedNodes) {
                        // console.log(node);
                        const imgs = node.querySelectorAll('img.woo-picture-img,img.picture_focusImg_1z5In,img.picture-viewer_pic_37YQ3,video.picture-viewer_pic_37YQ3');
                        // console.log(imgs);
                        for (const [ idx, img ] of Object.entries(imgs)) {
                            if (img.parentElement.getElementsByClassName('download-single-button').length === 0) {
                                if (img.className.includes('picture-viewer_pic_37YQ3')) {
                                    const previews = node.querySelectorAll('div.picture-viewer_preview_2wOSq');
                                    for (const [ index, preview ] of Object.entries(previews)) {
                                        if (preview.className.includes('picture-viewer_cur_anUEY')) {
                                            addSingleDlBtn(img, parseInt(index));
                                        }
                                    }
                                } else {
                                    addSingleDlBtn(img, parseInt(idx));
                                }
                            }
                        }
                    }
                }
            }
        } else if (location.host == 's.weibo.com') {
            // let cards = document.querySelectorAll('#pl_feedlist_index .card-wrap');
            const footers = document.querySelectorAll('#pl_feedlist_index .card-act');
            for (const footer of footers) {
                if(footer.getElementsByClassName('download-button').length > 0) {
                    // console.log('already added download button');
                } else {
                    // console.log(footer.parentElement);
                    if(footer.parentElement.className == 'card' && footer.parentElement.parentElement.className == 'card-wrap') {
                        const card = footer.parentElement;
                        let added = false;
                        const media_prev = card.querySelector('div[node-type="feed_list_media_prev"]');
                        // console.log(media_prev);
                        if (media_prev) {
                            const imgs = media_prev.getElementsByTagName('img');
                            // console.log(imgs);
                            if(imgs.length > 0) {
                                sAddDlBtn(footer);
                                added = true;
                            }
                            const videos = card.getElementsByTagName('video');
                            if(videos.length > 0 && added == false) {
                                sAddDlBtn(footer);
                            }
                        }
                    }
                }
            }
        }
    }).observe(document.body, { attributes: false, childList: true, subtree: true });

    let settingButton = document.createElement('button');
    settingButton.id = 'wbDlSetBtn';
    settingButton.textContent = text[6];
    settingButton.style.position = 'fixed';
    settingButton.style.top = '4rem';
    settingButton.style.left = '0rem';
    settingButton.style.fontSize = '0.7rem';
    settingButton.style.backgroundColor = 'gray';
    settingButton.style.color = 'white';
    settingButton.style.borderWidth = '0.2rem';
    settingButton.style.borderStyle = 'solid';
    settingButton.style.borderRadius = '0.5rem';
    settingButton.style.borderColor = 'lightgrey';
    settingButton.style.zIndex = 400;
    settingButton.style.paddingLeft = '1rem';
    settingButton.style.paddingRight = '1rem';
    settingButton.style.paddingTop = '0.2rem';
    settingButton.style.paddingBottom = '0.2rem';
    settingButton.addEventListener('mouseover', function(event) {
        settingButton.style.backgroundColor = 'darkgray';
        settingButton.style.color = 'black';
    });
    settingButton.addEventListener('mouseout', function(event) {
        settingButton.style.backgroundColor = 'gray';
        settingButton.style.color = 'white';
    });
    settingButton.addEventListener('mousedown', function(event) {
        settingButton.style.backgroundColor = 'gray';
        settingButton.style.color = 'white';
    });
    settingButton.addEventListener('mouseup', function(event) {
        settingButton.style.backgroundColor = 'darkgray';
        settingButton.style.color = 'black';
    });
    settingButton.addEventListener('click', showModal);
    document.body.appendChild(settingButton);
    GM_registerMenuCommand(text[25], showModal, "0");
    // console.log(GM_info.downloadMode);
})();