仓库用度盘投稿助手

简易功能增强, 方便仓库投稿用

目前為 2020-04-11 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         仓库用度盘投稿助手
// @namespace    moe.jixun.dupan.galacg
// @version      1.2.2
// @description  简易功能增强, 方便仓库投稿用
// @author       Jixun<https://jixun.moe/>
// @include      http://pan.baidu.com/disk/home*
// @include      http://yun.baidu.com/disk/home*
// @include      https://pan.baidu.com/disk/home*
// @include      https://yun.baidu.com/disk/home*

// @compatible   firefox GreaseMonkey (有限)
// @compatible   firefox Violentmonkey
// @compatible   chrome Violentmonkey

// @grant        none
// @run-at       document-start

// ==/UserScript==

const isGm = (typeof unsafeWindow !== 'undefined') && (unsafeWindow !== window);
if (isGm) {
	const INFO = '[仓库助手]';

  console.info("%s 以 GreaseMonkey 兼容模式执行。该脚本管理器所遇到的问题不能保证能够修复。", INFO);
  unsafeWindow.eval(`;(${entry})();`);
} else {
  entry();
}

function entry () {
	const INFO = '[仓库助手]';

  var oRequire;
  if (window.require) {
    console.warn("%s 覆盖方式安装,若无效请强制刷新。", INFO);
    oRequire = window.require;
    window.require = fakeRequire;
    Object.assign(fakeRequire, oRequire);
  } else {
    console.info("%s 钩子方式安装,若失效请报告。", INFO);
    Object.defineProperty(window, 'require', {
      set: function (require) {
        oRequire = require;
      },
      get: function () {
        return fakeRequire;
      }
    });
  }

  const __AppId = 250528;
  const pluginBlacklist = ['右上角广告位', '网盘APP下载', '满减活动', '会员提醒'];
  var _G = {};
  // window.__debug_G = _G;

  function fakeRequire(module) {
    // console.info('%s Load module: %s', INFO, module);
    if (module == 'disk-system:widget/system/uiRender/menu/listMenu.js') {
      if (!_G.injected) {
        initScript();
        _G.injected = true;
      }
    } else if (module == 'system-core:pluginHub/register/register.js') {
        if (!_G.btnInjected) {
            registerPlugin();
            _G.btnInjected = true;
        }
    }
    return oRequire.apply(this, arguments);
  }

  function menuInsertAfter(list, name, item, noPush) {
    for (var i = 0; i < list.length; i++) {
      if (list[i] instanceof Array) {
        if (menuInsertAfter(list[i], name, item, true)) {
          return;
        }
      } else if (list[i].title === name) {
        i++;
        list.splice(i, i, item);
        return true;
      }
    }

    if (!noPush) list.push(item);
  }

  function initScript() {
    // Load module
    _G.FaceData = oRequire('system-core:data/faceData.js');
    _G.ListInit = oRequire("disk-system:widget/pageModule/list/listInit.js");

    var fileCtxMenu = _G.FaceData.getData().contextMenu.file;
    menuInsertAfter(fileCtxMenu, '分享', {
      index: 8,
      keyboard: 'u',
      title: '自定义分享',
      display: isFileChecked,
      action: lazyDialog('loadCustomShareDialog')
    });

    fileCtxMenu.forEach(m => {
      if (m.index >= 2) {
        m.index++;
      }
    });

    fileCtxMenu.push({
      index: 2, // '删除' 的 index。
      keyboard: 'r',
      position: 'bottom',
      title: '批量重命名',
      display: isFileChecked,
      action: lazyDialog('loadBatchRenameDialog')
    });

    setTimeout(delayLoad, 200);
    delayLoadDuCode();

    // ESC 将关闭所有漂浮窗口
    document.addEventListener('keyup', function(e) {
      if (e.keyCode == 0x1b) {
        $('.dialog-close').click();
      }
    }, false);
  }

  function registerPlugin() {
    window.define('function-widget:jixun/standard-code.js', function(require, exports, module) {
      exports.start = lazyDialog('loadStandardCodeDialog');
    });

    window.manifest = window.manifest.filter(plugin => pluginBlacklist.indexOf(plugin.name) === -1);

    window.manifest.push({
      "name": "标准提取码插件",
      "group": "moe.jixun.code",
      "version": '1.0',
      "type": "1",
      "description": "类似于 115 的标准提取码",
      "filesType": "*",
      "buttons": [{
        "index": 2,
        "disabled": "none",
        "title": "标准提取码",
        "buttonStyle": "normal",
        "pluginId": "JIXUNSTDCODE",
        "position": 'tools'
      }],
      "preload": false,
      "depsFiles": [],
      "entranceFile": "function-widget:jixun/standard-code.js",
      "pluginId": "JIXUNSTDCODE"
    });
  }

  function isFileChecked() {
    return _G.ListInit.getCheckedItems().length > 0;
  }

  function lazyDialog(name) {
    return function() {
      if (_G[name]) {
        _G[name]();
      } else {
        loadDependencies();
        _G.Tip.show({
          mode: 'loading',
          msg: '正在努力加载中 ...'
        });
      }
    };
  }

  function nextId() {
    if (!nextId.id) nextId.id = 0;
    return nextId.id++;
  }

  function confirmDialog(data) {
    loadDependencies();

    var dialog;
    var dialogData = {
      id: "confirm-" + nextId(),
      show: true,
      title: data.title,
      body: $('<div style="text-align:center; padding:22px;">').append(data.body),
      buttons: [{
        name: "confirm",
        title: data.sureText || "确定",
        type: "big",
        color: "blue",
        padding: ["50px", "50px"],
        click: function() {
          if ($.isFunction(data.onSure)) {
            data.onSure.apply(this, arguments);
          } else {
            dialog.hide();
          }
        }
      }, {
        name: "cancel",
        title: data.cancelText || "取消",
        type: "big",
        padding: ["50px", "50px"],
        click: function() {
          if ($.isFunction(data.onCancel)) {
            data.onCancel.apply(this, arguments);
          } else {
            dialog.hide();
          }
        }
      }]
    };
    if (data.cancel === false) {
      dialogData.buttons.pop();
    }
    dialog = new _G.Dialog(dialogData);
    return dialog;
  }

  function delayLoad() {
    _G.loadCustomShareDialog = (function() {
      var _tplCustomShare = `
        <div>
          <p>请输入提取码:  <input id="jx_shareKey" class="jx-input" style="width: 6em" /></p>
          <p id="jx_errmsg" class="jx_c_warn jx_hide">无效的提取码, 脚本将随机生成一个分享代码 :&lt;</p>
        </div>
        <div class="jx_hide">
          <p>分享地址: <input id="jx_shortUrl" class="jx-input" style="width: 20em" readonly /></p>
          <p>提取码: <input id="jx_shareCode" class="jx-input" style="width: 5em; text-align: center" readonly /></p>

          <p style="text-align: left">投稿代码:<br /><textarea readonly id="jx_dlboxCode" class="jx jx-input"></textarea></p>
        </div>
      `;

      var dialog, $footer, $key, $tplCustomShare;

      function showDialog() {
        $tplCustomShare = $(_tplCustomShare);
        dialog = confirmDialog({
          title: '自定义分享',
          body: $tplCustomShare,
          onSure: function() {
            handleConfirm();
            dialog.hide();
          },
          onCancel: function() {
            dialog.hide();
          },
        });

        $footer = dialog.$dialog.find(_G.Dialog.QUERY.dialogFooter);

        $key = $('#jx_shareKey', $tplCustomShare)
          .blur(function() {
            if (!isCodeValid(this.value = this.value.trim())) {
              $('#jx_errmsg', $tplCustomShare)
                .removeClass('jx_hide');
            }
          })
          .focus(function() {
            $('#jx_errmsg', $tplCustomShare)
              .addClass('jx_hide');
          })
          .val(genKey(4));
      }

      function handleConfirm() {
        var key = $key.val();
        if (!isCodeValid(key)) {
          key = genKey(4);
          $key.val(key);
        }

        _G.Tip.show({
          mode: 'loading',
          msg: '正在分享,请稍后 ...',
          autoClose: false
        });

        var sharedItems = _G.ListInit.getCheckedItems();
        $.ajax({
          url: '/share/set?' + $.param({
            channel: 'chunlei',
            bdstoken: yunData.MYBDSTOKEN,
            app_id: __AppId
          }),
          type: 'POST',
          data: {
            fid_list: JSON.stringify(sharedItems.map(getFileId)),
            schannel: 4,
            channel_list: '[]',
            pwd: key
          },
          dataType: 'json'
        }).success(function(r) {
          _G.Tip.hide();

          _G.Tip.show({
            mode: 'success',
            msg: '分享成功!'
          });

          $footer.children('.g-button-blue-large').hide();
          $footer.children('.g-button-large').find('.text').text('关闭');

          let url = r.shorturl ? `${r.shorturl}#${key}` : '很抱歉, 分享失败 :<';
          $('#jx_shortUrl', $tplCustomShare).val(url);
          $('#jx_shareCode', $tplCustomShare).val(key);

          $tplCustomShare.toggleClass('jx_hide');

          var title = fixCode(sharedItems[0].server_filename) + (sharedItems.length == 1 ? '' : ' 等文件');

          $('#jx_dlboxCode', $tplCustomShare).val(
            `[dlbox title="${escapeHtml(title)}" from="浩瀚的宇宙" time="${makeDate(new Date())}" info="提取:${escapeHtml(key)}" link1="度娘|${url}"][/dlbox]]`
          );

          dialog.show();
        }).fail(function() {
          _G.Tip.hide();

          _G.Tip.show({
            mode: 'failure',
            msg: '网络错误,请稍后重试。'
          });
        });
      }

      /* 依赖函数表 */
      function isCodeValid(code) {
        return encodeURIComponent(code).replace(/%[A-F\d]{2}/g, '-').length == 4;
      }

      function fixCode(code) {
        return code.replace(/"/g, '&#x22;').replace(/\]/g, '&#x5D;');
      }

      function fixWidthDigits(d) {
        return ('0' + d.toString()).slice(-2);
      }

      function makeDate(d) {
        return `${d.getFullYear()}.${fixWidthDigits(d.getMonth() + 1)}.${fixWidthDigits(d.getDate())}`;
      }

      function genKey(size) {
        // length => 26 + 10, 36
        var keySet = 'abcdefghijklmnopqrstuvwxyz0123456789';
        for (var i = (size || 4) | 0, r = ''; i--;)
          r += keySet[0 | (Math.random() * 36)];

        return r;
      }

      function getFileId(item) {
        return item.fs_id;
      }

      /* 收尾 */
      return showDialog;
    })();

    _G.loadBatchRenameDialog = (function() {
      var _tplConfirmRename = `
        <p>请输入新的命名规则 (自动储存):  <input id="jx_nameRule" class="jx-input" style="width:20em" /></p>
        <p style="line-height: 1; padding-top: 1em;">
          <code>:n</code> 表示不带扩展名的文件名; <code>:e</code> 表示扩展名; <code>:E</code> 表示 .扩展名;
        <br><code>:d</code> 表示一位随机数字; <code>:c</code> 表示一位随机字符; <code>:t</code> 表示当前时间戳</p>
      `;

      var dialog, $tplConfirmRename;

      function showDialog() {
        $tplConfirmRename = $(_tplConfirmRename);
        $('#jx_nameRule', $tplConfirmRename).val(localStorage.jxRenameRule || '[GalACG] :d:d:d:d:d:d:d:d:d:d:E');

        dialog = confirmDialog({
          title: '确认批量重命名',
          body: $tplConfirmRename,
          onSure: function() {
            handleConfirm();
            dialog.hide();
          },
          onCancel: function() {
            dialog.hide();
          },
        });
      }

      function handleConfirm() {
        // 储存批量重命名记录
        var _nameRule = $('#jx_nameRule', $tplConfirmRename).val();
        localStorage.jxRenameRule = _nameRule;

        var _flist = _G.ListInit.getCheckedItems().map(toReplacePayload);

        _G.Tip.show({
          mode: 'loading',
          msg: '正在批量重命名,请稍后 ...',
          autoClose: false
        });

        $.ajax({
          url: '/api/filemanager?' + $.param({
            channel: 'chunlei',
            bdstoken: yunData.MYBDSTOKEN,
            app_id: __AppId,
            opera: 'rename'
          }),
          type: 'POST',
          data: {
            filelist: JSON.stringify(_flist)
          }
        }).success(function() {
          _G.Tip.hide();
          _G.Message.trigger('system-refresh');
        }).fail(function() {
          _G.Tip.hide();

          _G.Tip.show({
            mode: 'failure',
            msg: '批量重命名失败, 请稍后重试!'
          });
        });

        function toReplacePayload(item) {
          return {
            path: item.path,
            newname: _nameRule.replace(/:([cdeEn])/g, fixName.bind(item.server_filename))
          };
        }
      }

      /* 依赖函数表 */
      function fixName(z, code) {
        switch (code) {
          case 'n':
            var name = this.match(/^(.+)\./);
            return name ? name[1] : this;
          case 'c':
            return String.fromCharCode(97 + Math.random() * 26);
          case 'd':
            return Math.random().toString().slice(3, 4);
          case 't':
            return +new Date();
          case 'e':
            var ext = this.match(/\.([^.]+)$/);
            return ext ? ext[1] : '';
          case 'E':
            return this.match(/\.[^.]+$/) || '';
        }
      }

      /* 收尾 */
      return showDialog;
    })();
  }

  let div = document.createElement('a');
  let escapeDict = {
    '"': 'quot',
    "'": 'apos',
  };
  function escapeHtml(text) {
    div.textContent = text;
    const result = div.innerHTML.replace(/["']/g, x => `&${escapeDict[x]};`);
    div.textContent = '';
    return result;
  }

  function parseSize(size) {
    // 超过 GB
    if (size.length > 9) {
      size = size.slice(0, -7);
      return size.slice(0, -2) + '.' + size.slice(-2) + ' GB';
    }

    return (parseInt(size) / 1024 / 1024).toFixed(2) + ' MB';
  }

  function itemInfo(item) {
    const name = escapeHtml(item.name);
    return `
      <span class="name" title="${name}">${name}</span>
      <span class="size">(${escapeHtml(parseSize(item.size))})</span>
    `;
  }

  function delayLoadDuCode() {
    _G.loadStandardCodeDialog = delayLoadDuStdCode();

    function delayLoadDuStdCode() {
      var _tplStandardCode = `
        <form>
          <p><textarea class="jx jx_code jx-input" rows="7" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea></p>
          <section class="jx-form-options">
            文件重复时:
            <!-- <label><input name="ondup" type="radio" value="" /> 忽略</label> -->
            <label><input name="ondup" type="radio" value="newcopy" checked /> 建立副本</label>
            <label><input name="ondup" type="radio" value="overwrite" /> 覆盖</label>
          </section>
          <p style="line-height: 1;   padding: .5em 0;">
            扩展阅读: 
            <a href="http://game.ali213.net/thread-5465798-1-1.html" target="_blank">肚娘代码说明 [游侠]</a>
            | <a href="https://jixun.moe/2017/06/13/du-code-gen/" target="_blank">标准度娘提取码 [梦姬]</a>
          </p>
          <p style="text-align:left">
            <b>文件列表</b> (版本: <span class="jx_version" style="color:black">--</span>):
          </p>
          <ul class="jx_list"></ul>
          <p class="jx_c_warn jx_hide jx_errmsg">无效的提取码! 请确保您输入的提取码无误。</p>
        </form>
      `;

      var codeInfo = [];
      var dialog, $tplStandardCode, jx_list, jx_code, jx_errmsg, jx_version;
      var jx_ondup, ondup;
      var failed = 0;

      function showDialog() {
        $tplStandardCode = $(_tplStandardCode);

        dialog = confirmDialog({
          title: '通用提取码',
          body: $tplStandardCode,
          onSure: function() {
            handleConfirm();
            dialog.hide();
          },
          onCancel: function() {
            dialog.hide();
          },
        });

        jx_list = $('.jx_list', $tplStandardCode);
        jx_code = $('.jx_code', $tplStandardCode);
        jx_errmsg = $('.jx_errmsg', $tplStandardCode);
        jx_version = $('.jx_version', $tplStandardCode);
        jx_ondup = $('input[name="ondup"]', $tplStandardCode);
        ondup = $tplStandardCode[0].elements.ondup;
        jx_ondup.filter(`[value="${localStorage.getItem('__jx_ondup')}"]`).prop('checked', true);

        function updatePreview () {
          var code = jx_code.val();
          codeInfo = DuParser.parse(code);
          jx_errmsg.toggleClass('jx_hide', Boolean(!code || codeInfo.length));
          if (codeInfo.length) {
            jx_version.text(codeInfo.ver);
            jx_list.html(codeInfo.map(itemInfo).map(x => `<li>${x}</li>`).join(''));
          } else {
            jx_version.text('--');
            jx_list.text('');
          }
        }
        
        const debounce = fn => {
          let timer;
          return () => {
            cancelAnimationFrame(timer);
            timer = requestAnimationFrame(fn);
          };
        };

        jx_code.on('blur input', debounce(updatePreview)).focus(function() {
          jx_errmsg.addClass('jx_hide');
        });
      }

      var _curDir;

      function handleConfirm() {
        _curDir = _G.ListInit.currentKey;
        if (_curDir.slice(-1) != '/')
          _curDir += '/';

        failed = 0;
        localStorage.setItem('__jx_ondup', ondup.value);
        saveFile(0);
      }

      const statusMap = new Map([
        [0, {
          text: '成功',
          status: 'success'
        }],
        [404, {
          text: '文件不存在',
          status: 'fail'
        }],
        [666, {
          text: '网络错误',
          status: 'fail'
        }],
        [-8, {
          text: '忽略',
          status: 'skip'
        }],
      ]);
      
      function statusHtml(code) {
        if (!Number.isInteger(code)) {
          code = 666;
        }
        const success = !code;
        const status = statusMap.get(code) || { text: `错误 (${code})`, status: 'fail' };
        const className = success ? 'success' : 'fail';
        return `<span class="jx-status jx-status-${className}">${status.text}</span>`;
      }

      function saveFile(i) {
        if (i >= codeInfo.length) {
          _G.Message.trigger('system-refresh');
          const dialog = confirmDialog({
            title: `转存完毕 (失败 ${failed} 个, 共 ${i} 个)!`,
            body: `
            <ul class="save-complete-details jx_list">
              ${codeInfo.map(info => `<li>${itemInfo(info)}${statusHtml(info.code)}</li>`).join('')}
            </ul>
            `,
            onSure: function() {
              dialog.hide();
            },
            cancel: false,
          });
          return;
        }

        _G.Tip.show({
          mode: 'loading',
          msg: `正在转存文件 (${ i + 1 }/${ codeInfo.length }), 请稍后 ..`,
          autoClose: false
        });

        var file = codeInfo[i];

        $.ajax({
          url: '/api/rapidupload?rtype=1',
          type: 'POST',
          // https://github.com/iikira/BaiduPCS-Go/blob/9837f8e24328e5f881d6a07cf1249508c485a063/baidupcs/prepare.go#L272-L279
          data: {
            path: _curDir + file.name,
            'content-md5': file.md5,
            'slice-md5': file.md5s,
            'content-length': file.size,
            // overwrite: 表示覆盖同名文件; newcopy: 表示生成文件副本并进行重命名,命名规则为“文件名_日期.后缀”
            'ondup': 'newcopy',
            'local_mtime': ''
          }
        }).success(function(r) {
          _G.Tip.hide();
          codeInfo[i].code = r.errno;
          if (r.errno) failed++;
        }).fail(function(r) {
          failed++;
        }).always(function() {
          saveFile(i + 1);
        });
      }

      /**
       * 一个简单的类似于 NodeJS Buffer 的实现.
       * 用于解析游侠度娘提取码。
       * @param {SimpleBuffer}
       */
      function SimpleBuffer(str) {
        this.fromString(str);
      }

      SimpleBuffer.toStdHex = function toStdHex(n) {
        return ('0' + n.toString(16)).slice(-2);
      };

      SimpleBuffer.prototype.fromString = function fromString(str) {
        var len = str.length;

        this.buf = new Uint8Array(len);
        for (var i = 0; i < len; i++) {
          this.buf[i] = str.charCodeAt(i);
        }
      };

      SimpleBuffer.prototype.readUnicode = function readUnicode(index, size) {
        if (size & 1)
          size++;

        var bufText = Array.prototype.slice.call(this.buf, index, index + size).map(SimpleBuffer.toStdHex);

        var buf = [''];
        for (var i = 0; i < size; i += 2)
          buf.push(bufText[i + 1] + bufText[i]);

        return JSON.parse('"' + buf.join('\\u') + '"');
      };

      SimpleBuffer.prototype.readNumber = function readNumber(index, size) {
        var ret = 0;

        for (var i = index + size; i > index;)
          ret = this.buf[--i] + (ret * 256);

        return ret;
      };

      SimpleBuffer.prototype.readUInt = function readUInt(index) {
        return this.readNumber(index, 4);
      };

      SimpleBuffer.prototype.readULong = function readULong(index) {
        return this.readNumber(index, 8);
      };

      SimpleBuffer.prototype.readHex = function readHex(index, size) {
        return Array.prototype.slice.call(this.buf, index, index + size).map(SimpleBuffer.toStdHex).join('');
      };

      function DuParser() {}
      DuParser.parse = function generalDuCodeParse(szUrl) {
        var r;
        if (szUrl.indexOf('BDLINK') === 0) {
          r = DuParser.parseDu_v1(szUrl);
          r.ver = '游侠 v1';
        } else if (szUrl.indexOf('bdpan://') === 0) {
          r = DuParser.parseDu_bdpan(szUrl);
          r.ver = 'PanDownload';
        } else {
          r = DuParser.parseDu_v2(szUrl);
          r.ver = '梦姬标准';
        }
        return r;
      };

      DuParser.parseDu_v1 = function parseDu_v1(szUrl) {
        var raw = atob(szUrl.slice(6).replace(/\s/g, ''));
        if (raw.slice(0, 5) !== 'BDFS\x00')
          return null;

        var buf = new SimpleBuffer(raw);

        var ptr = 9;
        var arrFiles = [];
        var fileInfo, nameSize;
        var total = buf.readUInt(5);

        for (i = 0; i < total; i++) {
          // 大小 (8 bytes)
          // MD5 + MD5S (0x20)
          // nameSize (4 bytes)
          // Name (unicode)
          fileInfo = {};
          fileInfo.size = buf.readULong(ptr + 0);
          fileInfo.md5 = buf.readHex(ptr + 8, 0x10);
          fileInfo.md5s = buf.readHex(ptr + 0x18, 0x10);
          nameSize = buf.readUInt(ptr + 0x28) << 1;
          fileInfo.nameSize = nameSize;
          ptr += 0x2C;

          fileInfo.name = buf.readUnicode(ptr, nameSize);
          arrFiles.push(fileInfo);
          ptr += nameSize;
        }

        return arrFiles;
      };

      DuParser.parseDu_v2 = function parseDu_v2(szUrl) {
        return szUrl.split('\n').map(function(z) {
          // unsigned long long: 0~18446744073709551615
          return z.trim().match(/([\dA-F]{32})#([\dA-F]{32})#([\d]{1,20})#([\s\S]+)/);
        }).filter(function(z) {
          return z;
        }).map(function(info) {
          return {
            md5: info[1],
            md5s: info[2],
            size: info[3],
            name: info[4]
          };
        });
      };

      DuParser.parseDu_bdpan = function parseDu_bdpan(szUrl) {
        return szUrl.split('\n').map(z => z.trim()).filter(url => szUrl.indexOf('bdpan://') === 0).map(z => {
          return atob(z.slice('bdpan://'.length)).trim().match(/^([\s\S]+)\|([\d]{1,20})\|([\dA-F]{32})\|([\dA-F]{32})$/i);
        }).filter(function(z) {
          return z;
        }).map(function([, name, size, md5, md5s]) {
          name = decodeURIComponent(name.split('').map(z => `%${('0' + z.charCodeAt(0).toString(16)).slice(-2)}`).join(''));
          return { name, size, md5, md5s };
        });
      };

      /* 收尾 */
      return showDialog;
    }
  }

  function loadDependencies() {
    _G.Message = oRequire("system-core:system/baseService/message/message.js");
    _G.Tip = oRequire("system-core:system/uiService/tip/tip.js");
    _G.Dialog = oRequire("system-core:system/uiService/dialog/dialog.js");
    _G.Context = oRequire("system-core:context/context.js").instanceForSystem;
    _G.ui = _G.Context.ui;
  }
};

// Inject style
var style = document.createElement('style');
style.textContent = `
.jx_btn {
	background: #fefefe;
	background: linear-gradient(to bottom,  #fefefe 0%,#f2f2f2 88%);

	display: inline-block;
	line-height: 25px;
	vertical-align: middle;
	margin: 0 0 0 10px;
	text-decoration: none;
	border: 1px solid #AAA;
	padding: 0px 20px;
	height: 26px;
	border-radius: 2px;

	min-width: 3em;
	text-align: center;
}
.jx_btn, .jx_btn:hover, .jx_btn:focus {
	color: #666;
}
.jx_btn:active {
	color: #06C;
	background: #e3e3e3;
	background: -moz-linear-gradient(top,  #e3e3e3 0%, #f7f7f7 12%);
	background: -webkit-linear-gradient(top,  #e3e3e3 0%,#f7f7f7 12%);
	background: linear-gradient(to bottom,  #e3e3e3 0%,#f7f7f7 12%);
}
.jx-input {
    margin: 9px 0;
    _margin: 7px 0;
    padding: 0 0 0 5px;
    width: 200px;
    height: 24px;
    vertical-align: middle;
    border: 1px solid #3b8cff;
    border: 1px solid rgba(58,140,255,.3);
    background: #fff;
    border-radius: 2px;
}

.jx_hide   { display: none }
.jx_c_warn { color: red }

.jx_list {
	text-align: left;
	max-height: 5.5em;
	overflow-y: scroll;
	overflow-x: hidden;
	line-height: 1;
	padding: .2em;
	margin-bottom: .5em;
}

/*
.jx_list:not(:empty) {
  border: 1px solid #ddd;
}
*/

.jx_list > li {
    display: flex;
    white-space: nowrap;
    line-height: 1.3;
}

.jx_list .name {
	color: black;

  overflow: hidden;
  text-overflow: ellipsis;
}

.jx_list .size {
	color: #777;

  flex-grow: 1;
}

.save-complete-details {
  max-height: 30em;
}

.jx-status {
    padding-left: 0.25em;
}

.jx-status-success {
    color: green;
}

.jx-status-skip {
    color: gray;
}

.jx-status-fail {
    color: red;
}

textarea.jx{
	width: 100%;
  min-height: 5em;
  line-height: 1;
}

.jx-form-options {
  display: flex;
  justify-content: left;
}

.jx-form-options > label {
  display: inline-flex;
  align-items: center;
}

.jx-form-options > label + label {
  margin-left: 0.5em;
}

.jx-form-options > label > input {
  margin-right: 0.25em;
}

`;

document.head.appendChild(style);