Greasy Fork is available in English.

仓库用度盘投稿助手

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

Per 28-03-2016. Zie de nieuwste versie.

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

// 因为需要抢在页面脚本执行之前, 因此必须申请一个权限。
// TamperMonkey 测试通过, GreaseMonkey 未知.
// @grant        unsafeWindow
// @run-at       document-start

// The following thrid party scripts were injected to the page:
// https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js

// ==/UserScript==

var script = document.createElement('script');
script.innerHTML = ';(' + (function () {
	var oRequire;
	Object.defineProperty(window, 'require', {
		set: function (require) {
			oRequire = require;
		},
		get: function () {
			return fakeRequire;
		}
	});

	var __AppId = 250528;

	function _ (fn) {
		return fn.toString().match(/\/\*([\s\S]+)\*\//)[1];
	}
	
	function sprintf () {
		var arg = arguments,
			len = arg.length - 1,
			inp = arg[0];
		
		if (len < 1) return;
		
		for (var i=len; i>0; i--) {
			inp = inp.replace (new RegExp('(\\$|%)'+i,'g'), arg[i])
		}
		return inp;
	}

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

	function appendAfter (list, name, item, noPush) {
		for (var i = 0; i < list.length; i++) {
			if (list[i] instanceof Array) {
				if (appendAfter(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);
	}

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

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

		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 () {
		define('function-widget:jixun/standard-code.js', function (require, exports, module) {
			exports.start = lazyDialog('loadStandardCodeDialog');
		});

		window.manifest.push ({
			"name": "标准提取码插件",
			"group": "moe.jixun.code",
			"version": '1.0',
			"type": "1",
			"description": "类似于 115 的标准提取码",
			"filesType": "*",
			"buttons": [
				{
					"index": 3,
					"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();
					}
				}
			}]
		};
		dialog = new _G.Dialog(dialogData);
		return dialog;
	}

	function delayLoad () {
		_G.loadCustomShareDialog = (function () {
			var _tplCustomShare = _(function () {/*
				<div>
					<p>请输入提取码:  <input id="jx_shareKey" 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" style="width: 20em" readonly /></p>
					<p>提取码: <input id="jx_shareCode" style="width: 5em; text-align: center" readonly /></p>

					<p style="text-align: left">投稿代码:<br /><textarea readonly id="jx_dlboxCode" class="jx"></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: this.token,
						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('关闭');

					$('#jx_shortUrl',  $tplCustomShare).val (r.shorturl || '很抱歉, 分享失败 :<');
					$('#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="' + 
						title + '" from="浩瀚的宇宙" time="' +
						makeDate(new Date()) + '" info="提取:' + key +
						'" link1="度娘|' + r.shorturl + '#' + key + '"][/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 sprintf('%1.%2.%3', 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 = _(function () {/*
				<p>请输入新的命名规则 (自动储存):  <input id="jx_nameRule" 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;
		})();
	}

	function delayLoadDuCode () {
		var _amd = define.amd;
		delete define.amd;

		var libHandlebars = document.createElement('script');
		libHandlebars.src = 'https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js';
		libHandlebars.onload = function () {
			define.amd = _amd;
			_G.loadStandardCodeDialog = delayLoadDuStdCode();
		};
		document.head.appendChild(libHandlebars);


		function delayLoadDuStdCode () {
			Handlebars.registerHelper('size', parseSize);

			var _tplEncodeFiles = Handlebars.compile(_(function () {/*
				{{#each files}}
					<li>
						<span class="name">{{ name }}</span>
						<span class="size">({{size size}})</span>
					</li>
				{{/each}}
			*/}));
			var _tplStandardCode = _(function () {/*
				<div>
					<p><textarea class="jx jx_code" rows="7" autocorrect="off" autocapitalize="off" spellcheck="false"></textarea></p>
					<p style="line-height: 1;   padding: .5em 0;">
						扩展阅读: 
						<a href="http://game.ali213.net/thread-5465798-1-1.html" target="_blank">肚娘代码说明 [游侠]</a>
						| <a href="http://jixun.org/p/3232" 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>
				</div>
			*/});


			var dialog, $tplStandardCode, codeInfo, jx_list, jx_code, jx_errmsg, jx_version;
			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_code.blur(function () {
					var code = jx_code.val();
					codeInfo = DuParser.parse(code);
					jx_errmsg.toggleClass('jx_hide', !code || !!codeInfo);
					if (codeInfo) {
						jx_version.text(codeInfo.ver);
						jx_list.html(_tplEncodeFiles({
							files: codeInfo
						}));
					} else {
						jx_version.text('--');
					}
				}).focus(function () {
					jx_errmsg.addClass('jx_hide');
					jx_list.text('');
					jx_version.text('--');
				});
			}

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

			function saveFile (i) {
				if (i >= codeInfo.length) {
					_G.Tip.show({
						mode: 'success',
						msg: sprintf('转存完毕 (失败 %1 个)!', failed)
					});
					_G.Message.trigger('system-refresh');
					return ;
				}
				
				_G.Tip.show({
					mode: 'loading',
					msg: sprintf('正在转存文件 (%1/%2), 请稍后 ..', i + 1, codeInfo.length),
					autoClose: false
				});

				var file = codeInfo[i];
				
				$.ajax ({
					url: '/api/rapidupload',
					type: 'POST',
					data: {
						path: _curDir + file.name,
						'content-md5': file.md5,
						'slice-md5':   file.md5s,
						'content-length': file.size
					}
				}).success(function (r) {
					_G.Tip.hide();
					if (r.errno) failed++;
				}).fail(function (r) {
					failed++;
				}).always(function () {
					saveFile(i + 1);
				});
			}

			/* 依赖函数表 */
			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';
			}

			/**
			 * 一个简单的类似于 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 {
					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]
					};
				});
			};



			/* 收尾 */
			return showDialog;
		}
	}

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

	// Inject style
	var style = document.createElement('style');
	style.textContent = _(function () {/*
.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_hide   { display: none }
.jx_c_warn { color: red }

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

.jx_list .name {
	color: black;
}
.jx_list .size {
	color: #777;
}

textarea.jx{
	width: 100%;
}
	*/});
	document.head.appendChild(style);
}).toString () + ')();';
document.head.appendChild(script);