// ==UserScript==
// @name 仓库用度盘投稿助手
// @name:en Baidu™ WebDisk Helper (dupan-helper)
// @namespace moe.jixun.dupan.galacg
// @version 1.3.22
// @description 简易功能增强, 方便仓库投稿用
// @description:en Enhancements for Baidu™ WebDisk.
// @author Jixun<https://jixun.moe/>
// @match https://pan.baidu.com/disk/home*
// @match https://yun.baidu.com/disk/home*
// @compatible firefox Greasemonkey (有限/Limited)
// @compatible firefox Tampermonkey
// @compatible firefox Violentmonkey
// @compatible chrome Violentmonkey
// @compatible chrome Tampermonkey
// @compatible opera Violentmonkey
// @compatible opera Tampermonkey
// @incompatible safari
// @license MIT
// @homepageURL https://jixun.moe/post/dupan-helper
// @supportURL https://github.com/JixunMoe/dupan-helper/issues
// @contributionURL https://jixun.moe/donate
// @grant none
// @run-at document-start
// ==/UserScript==
function entryPoint () {
'use strict';
var css_248z = ".jx_btn {\n background: #fefefe;\n background: linear-gradient(to bottom, #fefefe 0%,#f2f2f2 88%);\n\n display: inline-block;\n line-height: 25px;\n vertical-align: middle;\n margin: 0 0 0 10px;\n text-decoration: none;\n border: 1px solid #AAA;\n padding: 0 20px;\n height: 26px;\n border-radius: 2px;\n\n min-width: 3em;\n text-align: center;\n}\n.jx_btn, .jx_btn:hover, .jx_btn:focus {\n color: #666;\n}\n.jx_btn:active {\n color: #06C;\n background: #e3e3e3;\n background: -moz-linear-gradient(top, #e3e3e3 0%, #f7f7f7 12%);\n background: -webkit-linear-gradient(top, #e3e3e3 0%,#f7f7f7 12%);\n background: linear-gradient(to bottom, #e3e3e3 0%,#f7f7f7 12%);\n}\n.jx-input {\n margin: 9px 0;\n padding: 0.25em;\n width: 200px;\n line-height: 1;\n vertical-align: middle;\n border: 1px solid #3a8cff4d;\n background: #fff;\n border-radius: 2px;\n}\n\n.jx_hide { display: none }\n.jx_c_warn { color: red }\n\n.jx_list {\n text-align: left;\n max-height: 5.5em;\n overflow-y: scroll;\n overflow-x: hidden;\n line-height: 1;\n padding: .2em;\n margin-bottom: .5em;\n}\n\n/*\n.jx_list:not(:empty) {\n border: 1px solid #ddd;\n}\n*/\n\n.jx_list > li {\n display: flex;\n white-space: nowrap;\n line-height: 1.3;\n}\n\n.jx_list .name {\n color: black;\n\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.jx_list .size {\n color: #777;\n\n flex-grow: 1;\n}\n\n.save-complete-details {\n max-height: 30em;\n}\n\n.jx-status {\n padding-left: 0.25em;\n}\n\n.jx-status-success {\n color: green;\n}\n\n.jx-status-skip {\n color: gray;\n}\n\n.jx-status-fail {\n color: red;\n}\n\ntextarea.jx{\n width: 100%;\n min-height: 5em;\n line-height: 1;\n}\n\n.jx-form-options {\n display: flex;\n justify-content: left;\n}\n\n.jx-form-options > label {\n display: inline-flex;\n align-items: center;\n}\n\n.jx-form-options > label + label {\n margin-left: 0.5em;\n}\n\n.jx-form-options > label > input {\n margin-right: 0.25em;\n}\n\n.dialog-header-title > .select-text {\n pointer-events: none;\n}\n\n.g-button-violet .text,\n.g-button-violet .icon,\n.g-button.g-button-violet:hover .icon {\n color: #fff;\n}\n\n.g-button.g-button-violet:hover .icon {\n opacity: 0.9;\n}\n\n.g-button-violet {\n background: #A238FF;\n border: 1px solid #A238FF;\n}\n\n.g-button-violet:hover {\n background: #AE52FF;\n border: 1px solid #AE52FF\n}\n";
styleInject(css_248z);
const TAG = '[仓库助手]';
const pluginBlacklist = ['右上角广告位', '网盘APP下载', '满减活动', '会员提醒'];
let oRequire;
const hooks = new Map();
function fakeRequire(module) {
// console.info('%s Load module: %s', INFO, module);
const result = oRequire.apply(this, arguments);
const moduleHook = hooks.get(module);
if (moduleHook) {
try {
moduleHook();
} catch (e) {
console.error('%s: 执行 %s hook 时发生错误: %s', TAG, e.message);
console.trace(e);
}
hooks.delete(module);
}
return result;
}
function load(module) {
return oRequire.call(window, module);
}
function loadAsync(module) {
return new Promise(((resolve) => {
fakeRequire.async(module, resolve);
}));
}
function hook(module, fn) {
hooks.set(module, fn);
}
if (window.require) {
console.warn('%s 覆盖方式安装,若无效请强制刷新。', TAG);
oRequire = window.require;
window.require = fakeRequire;
Object.assign(fakeRequire, oRequire);
} else {
console.info('%s 钩子方式安装,若失效请报告。', TAG);
Object.defineProperty(window, 'require', {
set(require) {
oRequire = require;
},
get() {
return fakeRequire;
},
});
}
const cache = (value) => () => value;
function lazyCache(fn) {
let cacheWrapper = function uncached() {
const result = fn.apply(this, arguments);
cacheWrapper = cache(result);
return result;
};
return function cacheProxy() {
return cacheWrapper.apply(this, arguments);
};
}
const getFileList = lazyCache(() => load('disk-system:widget/pageModule/list/listInit.js'));
function getCheckedItems() {
return getFileList().getCheckedItems();
}
function anythingChecked() {
return getCheckedItems().length > 0;
}
function getCurrentDirectory() {
return getFileList().currentKey;
}
var css_248z$1 = ".jx-dialog-body {\n text-align:center;\n padding:22px;\n}\n";
styleInject(css_248z$1);
let id = 0;
function nextId() {
// eslint-disable-next-line no-plusplus
return id++;
}
function firstFunction(...fns) {
return fns.find((fn) => typeof fn === 'function');
}
const getJQuery = lazyCache(() => load('base:widget/libs/jquerypacket.js'));
function $$1() {
return getJQuery().apply(window, arguments);
}
function proxyJQuery(key) {
Object.defineProperty($$1, key, {
get: () => getJQuery()[key],
});
}
proxyJQuery('fn');
proxyJQuery('ajax');
proxyJQuery('isPlainObject');
const getDialog = lazyCache(() => load('system-core:system/uiService/dialog/dialog.js'));
const bigButton = {
type: 'big',
padding: ['50px', '50px'],
};
function confirmDialog(data) {
let dialog;
const hideDialog = () => dialog.hide();
const dialogData = {
id: `confirm-${nextId()}`,
show: true,
title: data.title,
body: $$1('<div class="jx-dialog-body">').append(data.body),
buttons: [{
...bigButton,
name: 'confirm',
title: data.sureText || '确定',
color: 'blue',
click: firstFunction(data.onSure, hideDialog),
}],
};
if (data.cancel !== false) {
dialogData.buttons.push({
...bigButton,
name: 'cancel',
title: data.cancelText || '取消',
click: firstFunction(data.onCancel, hideDialog),
});
}
const Dialog = getDialog();
dialog = new Dialog(dialogData);
return dialog;
}
function infoDialog(data) {
return confirmDialog({
...data,
cancel: false,
});
}
const getTip = lazyCache(() => load('system-core:system/uiService/tip/tip.js'));
function showTip() {
return getTip().show.apply(this, arguments);
}
function hideTip() {
return getTip().hide.apply(this, arguments);
}
const getContext = lazyCache(() => load('system-core:context/context.js').instanceForSystem);
function getErrorMessage(code) {
const msg = String(getContext().errorMsg(code));
return msg.replace(/\s+rapidupload 错误码$/, '');
}
function injectErrorMessage(obj) {
if ($$1.isPlainObject(obj)) {
obj.error = obj.show_msg || getErrorMessage(obj.errno || 0);
}
return obj;
}
async function ajax(data) {
return new Promise((resolve) => {
$$1.ajax(data)
.fail((err) => {
resolve({ errno: -1, error: '网络错误。' });
console.error('%s 网络请求错误: %o', TAG, err);
})
.success((result) => {
resolve(injectErrorMessage(result));
});
});
}
const div = document.createElement('a');
const escapeDict = {
'"': 'quot',
"'": 'apos',
};
function escapeHtml(text) {
div.textContent = text;
const result = div.innerHTML.replace(/["']/g, (x) => `&${escapeDict[x]};`);
div.textContent = '';
return result;
}
var template = "<div>\n <p><label>请输入分享密码: <input id=\"jx_shareKey\" class=\"jx-input\" style=\"width: 6em\"/></label></p>\n <p class=\"jx_errmsg jx_c_warn jx_hide\">无效的分享密码, 脚本将随机生成一个分享代码 …</p>\n</div>\n\n<div class=\"jx_hide\">\n <p><label>分享地址: <input id=\"jx_shortUrl\" class=\"jx-input\" style=\"width: 20em\" readonly/></label></p>\n <p><label>分享密码: <input id=\"jx_shareCode\" class=\"jx-input\" style=\"width: 5em; text-align: center\" readonly/></label></p>\n\n <p style=\"text-align: left\">\n <label for=\"jx_dlboxCode\">投稿代码:</label><br/>\n <textarea readonly id=\"jx_dlboxCode\" class=\"jx jx-input\"></textarea>\n </p>\n</div>\n";
const PREFIX = '__jx_';
class LocalStore {
constructor(id) {
this.id = id;
}
get value() {
return localStorage.getItem(this.id);
}
set value(value) {
return localStorage.setItem(this.id, value);
}
static create(instance, key) {
return new LocalStore(`${PREFIX}_${instance.constructor.name}_${key}`);
}
}
class OpDialog {
confirmText = '确定';
createStore(key) {
return LocalStore.create(this, key);
}
constructor(template, options = {}) {
this.root = $$1(template);
this.title = options.title || '';
if (options.confirmText) {
this.confirmText = options.confirmText;
}
this.bindContext();
this.createDialog();
this.bootstrap();
}
bindContext() {
this.show = this.show.bind(this);
this.hide = this.hide.bind(this);
this.onConfirm = this.onConfirm.bind(this);
this.onCancel = this.onCancel.bind(this);
}
createDialog() {
this.dialog = confirmDialog({
title: this.title,
body: this.root,
sureText: this.confirmText,
onSure: this.onConfirm,
onCancel: this.onCancel,
});
}
/**
* 选择对话框内的内容。
* @param selector
* @returns {JQuery<HTMLElement>}
*/
$(selector) {
return $$1(selector, this.root);
}
/**
* Bind events.
*/
bootstrap() {
return this;
}
show() {
this.dialog.show();
}
hide() {
this.dialog.hide();
}
async onConfirm() {
this.hide();
}
onCancel() {
this.hide();
}
}
/* 依赖函数表 */
function isCodeValid(code) {
// 百度现在改了规则;
// 只允许:由数字字母组成的提取码,并且不能全部都是同一个字符。
return /^[\da-z]{4}$/i.test(code) && (new Set(code)).size > 1;
}
function fixCode(code) {
return code.replace(/"/g, '"').replace(/]/g, ']');
}
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 = 4) {
// length => 26 + 10, 36
const keySet = 'abcdefghijklmnopqrstuvwxyz0123456789';
let r = '';
for (let i = size; i--;) {
// eslint-disable-next-line no-bitwise
r += keySet[0 | (Math.random() * 36)];
}
return r;
}
function getFileId(item) {
return item.fs_id;
}
class CustomShareDialog extends OpDialog {
/**
* @param {Object} config
* @return CustomShareDialog
*/
static create(config = {}) {
return new CustomShareDialog(config);
}
/**
* @param {Object} config
*/
constructor(config = {}) {
super(template, {
title: '自定义分享',
...config,
});
}
bindContext() {
super.bindContext();
this.validateCode = this.validateCode.bind(this);
this.hideError = this.hideError.bind(this);
}
bootstrap() {
this.codeStore = LocalStore.create(this, 'code');
this.$error = this.$('.jx_errmsg');
this.$footer = this.dialog.find(getDialog().QUERY.dialogFooter);
this.$key = this.$('#jx_shareKey').val(this.codeStore.value || genKey());
this.$key.on('input change blur', this.validateCode);
this.$key.on('focus', this.hideError);
}
async onConfirm() {
this.hide();
let key = this.$key.val();
if (!isCodeValid(key)) {
key = genKey(4);
this.value = key;
}
this.codeStore.value = key;
showTip({
mode: 'loading',
msg: '正在分享,请稍后 ...',
autoClose: false,
});
const sharedItems = getCheckedItems();
const resp = await ajax({
url: '/share/set',
type: 'POST',
data: {
fid_list: JSON.stringify(sharedItems.map(getFileId)),
schannel: 4,
channel_list: '[]',
pwd: key,
// 0: 永久
// 1、7: 天数
period: 0,
},
dataType: 'json',
});
hideTip();
if (resp.errno || !resp.shorturl) {
showTip({
mode: 'failure',
msg: `分享失败:${resp.error}`,
});
return;
}
showTip({
mode: 'success',
msg: '分享成功!',
});
this.$footer.children('.g-button-blue-large').hide();
this.$footer.children('.g-button-large').find('.text').text('关闭');
const url = `${resp.shorturl}#${key}`;
this.$('#jx_shortUrl').val(url);
this.$('#jx_shareCode').val(key);
this.root.toggleClass('jx_hide');
const title = fixCode(sharedItems[0].server_filename) + (sharedItems.length === 1 ? '' : ' 等文件');
const code = `[dlbox title="${escapeHtml(title)}" from="浩瀚的宇宙" time="${makeDate(new Date())}" `
+ `info="提取:${escapeHtml(key)}" link1="度娘|${url}"][/dlbox]`;
this.$('#jx_dlboxCode').val(code);
this.show();
}
/**
* @returns string
*/
get value() {
return this.$key.val();
}
set value(value) {
return this.$key.val(value);
}
get isValueValid() {
return isCodeValid(this.value);
}
hideError() {
this.$error.addClass('jx_hide');
}
validateCode() {
this.$error.toggleClass('jx_hide', this.isValueValid);
}
}
var template$1 = "<p>\n <label for=\"jx_nameRule\">请输入新的命名规则 (自动储存)</label>:\n <input id=\"jx_nameRule\" class=\"jx-input\" style=\"width:20em\" />\n</p>\n\n<p style=\"line-height: 1; padding-top: 1em;\">\n <code>:n</code> 表示不带扩展名的文件名; <code>:e</code> 表示扩展名; <code>:E</code> 表示 .扩展名;\n <br><code>:d</code> 表示一位随机数字; <code>:c</code> 表示一位随机字符; <code>:t</code> 表示当前时间戳\n</p>\n";
const getMessage = lazyCache(() => load('system-core:system/baseService/message/message.js'));
function trigger(event) {
getMessage().trigger(event);
}
/**
* 刷新当前文件列表
*/
function refreshFileListView() {
trigger('system-refresh');
}
const fixRules = {
n(name) {
const match = name.match(/^(.+)\./);
return match ? match[1] : match;
},
c() {
return String.fromCharCode(97 + Math.random() * 26);
},
d() {
return Math.random().toString().slice(3, 4);
},
t() {
return Date.now();
},
e(name) {
const ext = name.match(/\.([^.]+)$/);
return ext ? ext[1] : '';
},
E(name) {
return name.match(/\.[^.]+$/) || '';
},
};
/* 依赖函数表 */
function fixName(name, code) {
const fn = fixRules[code];
if (fn) {
return fn(name);
}
return null;
}
class BatchRenameDialog extends OpDialog {
/**
* @param {Object} config
* @return StandardCodeDialog
*/
static create(config = {}) {
return new BatchRenameDialog(config);
}
/**
* @param {Object} config
*/
constructor(config = {}) {
super(template$1, {
title: '批量重命名',
...config,
});
}
bindContext() {
super.bindContext();
this.namePatternStore = this.createStore('pattern');
}
bootstrap() {
this.$namePattern = this.$('#jx_nameRule');
this.$namePattern.val(this.namePatternStore.value || '[GalACG] :d:d:d:d:d:d:d:d:d:d:E');
}
async onConfirm() {
this.hide();
const namePattern = this.$namePattern.val();
this.namePatternStore.value = namePattern;
const fileList = getCheckedItems().map((item) => ({
path: item.path,
newname: namePattern.replace(/:([cdeEnt])/g, (_, code) => fixName(item.server_filename, code)),
}));
showTip({
mode: 'loading',
msg: '正在批量重命名,请稍后 ...',
autoClose: false,
});
const resp = await ajax({
url: '/api/filemanager?opera=rename',
type: 'POST',
data: {
filelist: JSON.stringify(fileList),
},
});
hideTip();
refreshFileListView();
if (resp.errno) {
showTip({
mode: 'failure',
msg: `批量重命名失败, 请稍后重试! (${resp.error})`,
});
} else {
showTip({
mode: 'success',
msg: '重命名成功!',
});
}
}
}
function menuInsertAfter(list, name, item, noPush) {
for (let i = 0; i < list.length; i++) {
if (list[i] instanceof Array) {
if (menuInsertAfter(list[i], name, item, true)) {
return false;
}
} else if (list[i].title === name) {
i++;
list.splice(i, 0, item);
return true;
}
}
if (!noPush) list.push(item);
return false;
}
function injectMenu() {
const faceData = load('system-core:data/faceData.js');
const fileCtxMenu = faceData.getData().contextMenu.file;
menuInsertAfter(fileCtxMenu, '分享', {
index: 8,
keyboard: 'u',
title: '自定义分享',
display: anythingChecked,
action: CustomShareDialog.create,
});
fileCtxMenu.forEach((m) => {
if (m.index >= 2) {
m.index += 1;
}
});
fileCtxMenu.push({
index: 2, // '删除' 的 index。
keyboard: 'r',
position: 'bottom',
title: '批量重命名',
display: anythingChecked,
action: BatchRenameDialog.create,
});
}
var template$2 = "<form>\n <p>\n <label>\n <textarea class=\"jx jx_code jx-input\" rows=\"7\" autocorrect=\"off\" autocapitalize=\"off\" spellcheck=\"false\"></textarea>\n </label>\n </p>\n\n <!-- 选择覆盖的時候好像并不会生效? -->\n <section class=\"jx-form-options jx_hide\">\n 文件重复时:\n <label><input name=\"ondup\" type=\"radio\" value=\"newcopy\" checked /> 建立副本</label>\n <label><input name=\"ondup\" type=\"radio\" value=\"overwrite\" disabled /> 覆盖</label>\n </section>\n\n <p style=\"text-align:left\">\n <em>文件列表</em> (版本: <span class=\"jx_version\" style=\"color:black\">--</span>):\n </p>\n <ul class=\"jx_list\"></ul>\n <p class=\"jx_c_warn jx_hide jx_errmsg\">识别不出任何有效的秒传链接。</p>\n</form>\n";
function debounce(fn) {
let timer;
return () => {
cancelAnimationFrame(timer);
timer = requestAnimationFrame(fn);
};
}
/**
* 将数值转换为 2 位数的十六进制文本。
* @param {Number} value
* @returns {string}
*/
function toStdHex(value) {
const hex = Math.floor(value).toString(16);
return (`0${hex}`).slice(-2);
}
const slice = Function.prototype.call.bind(Array.prototype.slice);
/**
* 一个简单的类似于 NodeJS Buffer 的实现.
* 用于解析游侠度娘提取码。
*/
class SimpleBuffer {
/**
* @param {String} str
*/
constructor(str) {
this.fromString(str);
}
fromString(str) {
const len = str.length;
this.buf = new Uint8Array(len);
for (let i = 0; i < len; i++) {
this.buf[i] = str.charCodeAt(i);
}
}
readUnicode(index, size) {
const bufText = slice(this.buf, index, index + size).map(toStdHex);
const buf = [''];
for (let i = 0; i < size; i += 2) {
buf.push(bufText[i + 1] + bufText[i]);
}
return JSON.parse(`"${buf.join('\\u')}"`);
}
readNumber(index, size) {
let ret = 0;
for (let i = index + size; i > index;) ret = this.buf[--i] + (ret * 256);
return ret;
}
readUInt(index) {
return this.readNumber(index, 4);
}
readULong(index) {
return this.readNumber(index, 8);
}
readHex(index, size) {
return Array.prototype.slice.call(this.buf, index, index + size).map(toStdHex).join('');
}
}
/**
* UTF-8 字符转换成 base64 后在 JS 里解析会出毛病。
* @param str
* @returns {string}
*/
function decodeBase64(str) {
try {
str = atob(str);
} catch (e) {
console.error('%s: base64 decode failed: %s', TAG, str);
console.trace(e);
return '';
}
return decodeURIComponent(str.replace(/[^\x00-\x7F]/g, (z) => `%${toStdHex(z.charCodeAt(0))}`));
}
const trim = (str) => String.prototype.trim.call(str);
/**
* 百度网盘用的(非官方)标准提取码。
* 支持解析:
* 1. 游侠的 `BDLINK` 提取码
* 2. 我的“标准提取码”
* 3. PanDownload 的 `bdpan://` 协议。
*/
class DuParser {
constructor() {
this.reset();
}
reset() {
this.results = [];
this.versions = new Set();
}
/**
* 判断地址类型并解析。
* @param url
*/
parse(url) {
// 游侠的格式是多行,不好判断结束位置。
// 所以一次只能解析一条数据。
if (url.indexOf('BDLINK') === 0) {
this.parseAli(url);
return;
}
// 其他两个格式一行一个文件信息。
const links = url.split('\n').map(trim);
for (const link of links) {
if (link.startsWith('bdpan://')) {
this.parsePanDownload(link);
} else {
this.parseStandard(link);
}
}
}
hasResults() {
return this.results.length;
}
parseAli(url) {
const raw = atob(url.slice(6).replace(/\s/g, ''));
if (raw.slice(0, 5) !== 'BDFS\x00') return null;
const buf = new SimpleBuffer(raw);
let ptr = 9;
const fileCount = buf.readUInt(5);
if (fileCount === 0) {
return null;
}
this.versions.add('游侠 v1');
for (let i = 0; i < fileCount; i++) {
// 大小 (8 bytes)
// MD5 + MD5S (0x20)
// nameSize (4 bytes)
// Name (unicode)
const fileInfo = Object.create(null);
fileInfo.size = buf.readULong(ptr);
fileInfo.md5 = buf.readHex(ptr + 8, 0x10);
fileInfo.md5s = buf.readHex(ptr + 0x18, 0x10);
const sizeofName = buf.readUInt(ptr + 0x28) * 2;
ptr += 0x2C;
fileInfo.name = buf.readUnicode(ptr, sizeofName);
this.results.push(fileInfo);
ptr += sizeofName;
}
return true;
}
parseStandard(szUrl) {
const match = szUrl.trim().match(/^([\dA-F]{32})#([\dA-F]{32})#([\d]{1,20})#([\s\S]+)$/i);
if (match) {
const [, md5, md5s, size, name] = match;
this.versions.add('梦姬标准');
this.results.push({
md5, md5s, size, name,
});
}
return null;
}
parsePanDownload(szUrl) {
const match = decodeBase64(szUrl.slice(8)).match(/^([\s\S]+)\|([\d]{1,20})\|([\dA-F]{32})\|([\dA-F]{32})$/i);
if (match) {
const [, name, size, md5, md5s] = match;
this.versions.add('PanDownload');
this.results.push({
md5, md5s, size, name,
});
}
return null;
}
}
/**
* 将文本形式的文件大小转换为
* @param {string} size
* @returns {string}
*/
function parseSize(size) {
let unit = 'MiB';
let sizeInUnit = parseInt(size, 10) / 1024 / 1024;
// 超过 GB
if (sizeInUnit > 1024) {
unit = 'GiB';
sizeInUnit /= 1024;
}
return `${sizeInUnit.toFixed(2)} ${unit}`;
}
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 wrapTag(tag) {
return (html) => `<${tag}>${html}</${tag}>`;
}
const lower = Function.prototype.call.bind(String.prototype.toLowerCase);
const upper = Function.prototype.call.bind(String.prototype.toUpperCase);
async function rapidUploadOnce(dir, name, md5, md5s, size, ondup) {
if (dir.slice(-1) !== '/') {
dir += '/';
}
return ajax({
url: '/api/rapidupload?rtype=1',
type: 'POST',
// https://github.com/iikira/BaiduPCS-Go/blob/9837f8e24328e5f881d6a07cf1249508c485a063/baidupcs/prepare.go#L272-L279
data: {
// overwrite: 表示覆盖同名文件; newcopy: 表示生成文件副本并进行重命名,命名规则为“文件名_日期.后缀”
ondup,
path: dir + name,
'content-md5': md5,
'slice-md5': md5s,
'content-length': size,
local_mtime: '',
},
});
}
async function rapidUpload(dir, file, ondup) {
const {
name,
md5,
md5s,
size,
} = file;
// 先尝试小写,如果失败则尝试大写。如果都失败则不重试。
const resp = await rapidUploadOnce(dir, name, lower(md5), lower(md5s), size, ondup);
if (resp.errno === 0) {
return resp;
}
return rapidUploadOnce(dir, name, upper(md5), upper(md5s), size, ondup);
}
function statusHtml(result) {
const className = result.success ? 'success' : 'fail';
return `<span class="jx-status jx-status-${className}">${result.error}</span>`;
}
const defaultConfirmCallback = async () => true;
class StandardCodeDialog extends OpDialog {
/**
* @param {Object} config
* @return StandardCodeDialog
*/
static create(config) {
return new StandardCodeDialog(config);
}
confirmText = '导入';
confirmCallback = defaultConfirmCallback;
constructor(config = {}) {
super(template$2, {
title: '从秒传链接导入',
...config,
});
if (config) {
this.setText(config.content);
this.setDirectory(config.directory);
this.setConfirmCallback(config.confirmCallback);
this.forceRefresh = config.forceRefresh || false;
}
}
bindContext() {
super.bindContext();
this.hideError = this.hideError.bind(this);
this.updatePreview = this.updatePreview.bind(this);
this.parser = new DuParser();
this.directory = getCurrentDirectory();
}
bootstrap() {
this.jx_list = this.$('.jx_list');
this.jx_code = this.$('.jx_code');
this.jx_errmsg = this.$('.jx_errmsg');
this.jx_version = this.$('.jx_version');
this.jx_ondup = this.$('input[name="ondup"]');
this.ondup = this.root[0].elements.ondup;
this.ondupStore = this.createStore('ondup');
this.jx_ondup.filter(`[value="${this.ondupStore.value}"]`).prop('checked', true);
this.jx_code.on('blur input', debounce(this.updatePreview));
this.jx_code.on('focus', this.hideError);
}
hideError() {
this.jx_errmsg.addClass('jx_hide');
}
get versions() {
return Array.from(this.parser.versions).join('、');
}
get results() {
return this.parser.results;
}
updatePreview() {
const code = this.getText();
this.parser.reset();
this.parser.parse(code);
const hasResults = this.parser.hasResults();
// 如果输入框不为空却没有解析到任何内容
this.jx_errmsg.toggleClass('jx_hide', Boolean(!code || hasResults));
if (hasResults) {
this.jx_version.text(this.versions);
this.jx_list.html(this.results.map(itemInfo).map(wrapTag('li')).join(''));
} else {
this.jx_version.text('--');
this.jx_list.text('');
}
}
setText(content) {
this.jx_code.val(content || '');
this.updatePreview();
}
getText() {
return this.jx_code.val();
}
getDirectory() {
return this.directory;
}
setConfirmCallback(confirmCallback) {
this.confirmCallback = confirmCallback || defaultConfirmCallback;
}
setDirectory(directory) {
if (!directory) {
directory = getCurrentDirectory();
}
this.directory = directory;
}
async onConfirm() {
this.hide();
// 取消了操作
if (!await this.confirmCallback()) {
return;
}
const ondup = this.ondup.value;
this.ondupStore.value = ondup;
const totalCount = this.results.length;
let failed = 0;
let counter = 1;
for (const file of this.results) {
showTip({
mode: 'loading',
msg: `正在转存文件 (${counter}/${totalCount}), 请稍后 ..`,
autoClose: false,
});
const resp = await rapidUpload(this.getDirectory(), file, ondup);
file.success = resp.errno === 0;
file.errno = resp.errno;
file.error = resp.error;
file.resp = resp;
if (!file.success) {
failed++;
}
counter++;
}
if (this.forceRefresh || this.getDirectory() === getCurrentDirectory()) {
refreshFileListView();
}
infoDialog({
title: `转存完毕 (失败 ${failed} 个, 共 ${totalCount} 个)!`,
body: `
<ul class="save-complete-details jx_list">
${this.results.map((result) => `${itemInfo(result)}${statusHtml(result)}`).map(wrapTag('li')).join('')}
</ul>
`,
cancel: false,
});
}
}
function registerPlugin() {
// 注入到 manifest 定义文件
window.define('function-widget:jixun/standard-code.js', (require, exports) => {
// require, exports, module
exports.start = StandardCodeDialog.create;
});
window.manifest = window.manifest.filter((plugin) => !pluginBlacklist.includes(plugin.name));
window.manifest.push({
name: '秒传链接支持',
group: 'moe.jixun.code',
version: '1.0',
type: '1',
description: '类似于 115 的标准提取码',
filesType: '*',
buttons: [{
index: 2,
disabled: 'none',
color: 'violet',
icon: 'icon-upload',
title: '秒传链接',
buttonStyle: 'normal',
pluginId: 'JIXUNSTDCODE',
position: 'tools',
}],
preload: false,
depsFiles: [],
entranceFile: 'function-widget:jixun/standard-code.js',
pluginId: 'JIXUNSTDCODE',
});
}
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
// If obj.hasOwnProperty has been overridden, then calling
// obj.hasOwnProperty(prop) will break.
// See: https://github.com/joyent/node/issues/1707
function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function parseQueryString(qs, sep, eq, options) {
sep = sep || '&';
eq = eq || '=';
const obj = {};
if (typeof qs !== 'string' || qs.length === 0) {
return obj;
}
qs = qs.split(sep);
let maxKeys = 1000;
if (options && typeof options.maxKeys === 'number') {
maxKeys = options.maxKeys;
}
let len = qs.length;
// maxKeys <= 0 means that we should not limit keys count
if (maxKeys > 0 && len > maxKeys) {
len = maxKeys;
}
for (let i = 0; i < len; ++i) {
const x = qs[i];
const idx = x.indexOf(eq);
let kstr;
let vstr;
if (idx >= 0) {
kstr = x.substr(0, idx);
vstr = x.substr(idx + 1);
} else {
kstr = x;
vstr = '';
}
const k = decodeURIComponent(kstr);
const v = decodeURIComponent(vstr);
if (!hasOwnProperty(obj, k)) {
obj[k] = v;
} else if (Array.isArray(obj[k])) {
obj[k].push(v);
} else {
obj[k] = [obj[k], v];
}
}
return obj;
}
class Query {
constructor() {
this.search = {};
}
parse(source) {
this.search = parseQueryString(source.replace(/^(#\??|\?)/g, '').replace(/\+/g, '%2b'));
}
has(name) {
return Object.prototype.hasOwnProperty.call(this.search, name);
}
get(name) {
return this.search[name];
}
}
var css_248z$2 = ".jx-prev-path > span {\n white-space: nowrap;\n display: flex;\n padding: 0 12px;\n}\n\n.jx-prev-path code {\n padding-left: 0.5em;\n flex-grow: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n";
styleInject(css_248z$2);
var css_248z$3 = ".jx-checkbox {\n display: none;\n}\n\n.jx-label {\n cursor: pointer;\n}\n\n.jx-label span {\n display: flex;\n}\n\n.jx-checkbox + span::before {\n content: '';\n padding-left: 20px;\n background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADYAAAAOBAMAAACWQvIuAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAwUExURQAAAP///8rKyujt8TWJ4ViV2/b29srb7vLy8m2l4fX2+K3L6lKt8u7y93/G+Pn5+VodYkgAAAABdFJOUwBA5thmAAAAbElEQVQY02NQggAGZAATUzYGASMUOZiYRqIgEAihyIHFLIQY1AQx5UBiUjOFGBSxyIHElrpswi5nLCgf4roIq5yyy8WlLgexm6nt4h7iKYjDvhIXl4O45MRdPAVxyQmWPMQtJyGIWw4ihiesAfWzHJ0JZlnhAAAAAElFTkSuQmCC') no-repeat left;\n}\n\n.jx-checkbox:checked + span::before {\n background-position-x: -40px;\n}\n";
styleInject(css_248z$3);
class Checkbox {
constructor(options = {}) {
const {
content = '',
className = '',
checked = false,
} = options;
this.root = $$1('<label class="jx-label">').addClass(className);
this.$input = $$1('<input class="jx-checkbox" type="checkbox" />');
this.$text = $$1('<span>');
if (typeof content === 'string') {
this.$text.text(content);
} else {
this.$text.append(content);
}
this.$input.prop('checked', checked);
this.root.append(this.$input).append(this.$text);
}
get checked() {
return this.$input.prop('checked');
}
set checked(checked) {
return this.$input.prop('checked', Boolean(checked));
}
appendTo(target) {
this.root.appendTo(target);
}
}
class ImportOnLoad {
static create(content) {
return new ImportOnLoad(content);
}
constructor(content = '') {
this.content = content;
this.onConfirm = this.onConfirm.bind(this);
this.selectDirectory = this.selectDirectory.bind(this);
this.tryAndInitTreeSelector().catch(console.error);
}
async tryAndInitTreeSelector() {
for (let i = 5; i >= 0; i--) {
try {
await this.initTreeSelector();
// init success
return;
} catch (error) {
console.error(error);
}
await new Promise((resolve) => setTimeout(resolve, 5000));
}
throw new Error('Could not init tree selector.');
}
async initTreeSelector() {
// 百度的这个依赖没处理好啊,还得我手动照着顺序来加载
await loadAsync('disk-system:widget/plugin/moveCopy/start.js');
this.fileTreeDialog = await loadAsync('disk-system:widget/system/uiService/fileTreeDialog/fileTreeDialog.js');
this.ui = getContext().ui;
this.directoryStore = LocalStore.create(this, 'import_dir');
this.prevPath = this.directoryStore.value || '/';
this.confirmFileList();
}
selectDirectory() {
this.dirSelectDialog = this.fileTreeDialog.show({
title: '导入至…',
confirm: this.onConfirm,
isZip: true,
showShareDir: false,
path: '/',
});
this.$dialogBody = this.dirSelectDialog.dialog.$dialog.find(getDialog().QUERY.dialogBody);
this.checkUsePrevPath = new Checkbox({
content: '使用上次储存的位置',
className: 'jx-prev-path',
checked: true,
});
this.checkUsePrevPath.appendTo(this.$dialogBody);
this.$prevPath = $('<code>').text(this.prevPath);
this.checkUsePrevPath.$text.append(this.$prevPath);
this.checkUsePrevPath.root.prop('title', this.prevPath);
return new Promise(((resolve) => {
this.resolveDirectorySelect = resolve;
}));
}
confirmFileList() {
const { content } = this;
this.stdCodeDialog = StandardCodeDialog.create({
content,
forceRefresh: true,
confirmText: '选择目录',
confirmCallback: this.selectDirectory,
});
}
onConfirm(targetDir) {
this.fileTreeDialog.hide();
const directory = this.checkUsePrevPath.checked ? this.prevPath : targetDir;
this.directoryStore.value = directory;
this.stdCodeDialog.setDirectory(directory);
this.resolveDirectorySelect(true);
}
}
const KEY_BDLINK = 'bdlink';
const { search, hash } = window.location;
function initialiseQueryLink() {
const query = new Query();
query.parse(search);
if (!query.has(KEY_BDLINK)) {
query.parse(hash);
}
if (query.has(KEY_BDLINK)) {
ImportOnLoad.create(decodeBase64(query.get(KEY_BDLINK).replace(/#.{4}$/, '')));
}
}
hook('disk-system:widget/system/uiRender/menu/listMenu.js', injectMenu);
hook('system-core:pluginHub/register/register.js', registerPlugin);
hook('system-core:system/uiService/list/list.js', initialiseQueryLink);
// ESC 将关闭所有漂浮窗口
document.addEventListener('keyup', (e) => {
if (e.keyCode === 0x1b) {
$$1('.dialog-close').click();
}
}, false);
// eslint-disable-next-line no-unused-vars
function styleInject(css) {
function addStyle(cssText) {
const style = document.createElement('style');
style.textContent = cssText;
document.head.appendChild(style);
}
if (document.head) {
addStyle(css);
} else if (styleInject.pending) {
styleInject.pending.push(css);
} else {
const injectPendingCSS = () => {
styleInject.pending.forEach(addStyle);
styleInject.pending = undefined;
window.removeEventListener('DOMContentLoaded', injectPendingCSS);
};
styleInject.pending = [css];
window.addEventListener('DOMContentLoaded', injectPendingCSS);
}
}
}
const isGm = (typeof unsafeWindow !== 'undefined') && (unsafeWindow !== window);
if (isGm) {
const INFO = '[仓库助手]';
console.info('%s 以 GreaseMonkey 兼容模式执行。该脚本管理器所遇到的问题不能保证能够修复。', INFO);
unsafeWindow.eval(`;(${entryPoint})();`);
} else {
entryPoint();
}