// ==UserScript==
// @name 哔哩哔哩 - 网页上传小视频
// @namespace esterTion
// @description 网页直传小视频,谁要下你的辣鸡app啊
// @include *://vc.bilibili.com/
// @include *://link.acg.tv/forum.php*
// @require https://static.hdslb.com/js/jquery.js
// @require https://static.hdslb.com/js/md5.js
// @require https://gitcdn.xyz/cdn/esterTion/live_html5_lib/8430cf8042bbe0940dd445a533458e3a248f43c7/ybuploader-1803061005.full.min.js
// @version 5
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @run-at document-end
// @connect passport.bilibili.com
// @connect https://passport.bilibili.com
// @connect https://passport.bilibili.com/*
// ==/UserScript==
/* global hex_md5 GM_xmlhttpRequest GM_getValue GM_setValue GM_deleteValue $ WebUploader */
if (location.href.match('link.acg.tv/forum.php') != null && location.href.match('access_key') != null && window.opener != null) {
window.stop();
document.children[0].innerHTML = '<title>BALH - 授权</title><meta charset="UTF-8" name="viewport" content="width=device-width">正在跳转……';
window.opener.postMessage('balh-login-credentials: ' + location.href, '*');
return;
}
function _(e, t, n) { var r = null; if ("text" === e) return document.createTextNode(t); r = document.createElement(e); for (var l in t) if ("style" === l) for (var a in t.style) r.style[a] = t.style[a]; else if ("className" === l) r.className = t[l]; else if ("event" === l) for (var a in t[l]) r.addEventListener(a, t[l][a]); else r.setAttribute(l, t[l]); if (n) for (var s = 0; s < n.length; s++)null != n[s] && r.appendChild(n[s]); return r }
var s = document.createElement('script');
s.src = '//static.hdslb.com/js/jquery.min.js';
document.head.appendChild(s);
s = document.createElement('script');
s.src = '//static.hdslb.com/js/md5.js';
document.head.appendChild(s);
const APPKEY = '27eb53fc9058f8c3';
const APPSEC = 'c2ed53a74eeefe3cf99fbd01d8c9c375';
function signParam(param) {
var paramList = [];
// 通用参数
param.appkey = APPKEY;
param.build = 3741;
param.ts = (Date.now() / 1e3) | 0;
for (var key in param) {
paramList.push(key + '=' + encodeURIComponent(param[key]));
}
paramList.sort();
var query = paramList.join('&');
var sign = hex_md5(query + APPSEC);
return query + '&sign=' + sign;
}
// 检查access_key
var userInfo = {
isLogin: false
};
function checkKey() {
userInfo.access_key = GM_getValue('access_key');
if (userInfo.access_key == undefined) {
return;
}
userInfo.isLogin = true;
checkKeyStatus();
return;
}
checkKey();
function checkKeyStatus() {
var needChk = true;
if (localStorage.BWVC_oauthTime && (localStorage.BWVC_oauthTime > Date.now() - 2 * 24 * 3600e3))
needChk = false;
if (needChk) {
fetch('https://passport.bilibili.com/api/oauth?' + signParam({
access_key: userInfo.access_key
})).then(r => r.json()).then(data => {
if (data.code == 0) {
var expire = data['access_info']['expires'] * 1e3;
if (expire - 5 * 24 * 3600e3 < Date.now()) {
$.ajax({
method: 'GET',
url: 'https://passport.bilibili.com/api/login/renewToken?' + signParam({
access_key: userInfo.access_key
})
});
}
localStorage.BWVC_oauthTime = Date.now();
} else if (data.code == -101 || data.code == -658) {
GM_deleteValue('access_key');
userInfo.isLogin = false;
delete userInfo.access_key;
console.log('[BWVC] access_key 已过期');
}
});
}
}
// 获取新access key
window.addEventListener('message', function (e) {
if (typeof e.data !== 'string') return // 只处理e.data为string的情况
switch (e.data.split(':')[0]) {
case 'balh-login-credentials': {
balh_auth_window.close();
let url = e.data.split(': ')[1];
gotKey({ finalUrl: url });
break;
}
}
})
function getKey() {
const balh_auth_window = window.open('about:blank');
balh_auth_window.document.title = 'BALH - 授权';
balh_auth_window.document.body.innerHTML = '<meta charset="UTF-8" name="viewport" content="width=device-width">正在获取授权,请稍候……';
window.balh_auth_window = balh_auth_window;
$.ajax('https://passport.bilibili.com/login/app/third?appkey=27eb53fc9058f8c3&api=http%3A%2F%2Flink.acg.tv%2Fforum.php&sign=67ec798004373253d60114caaad89a8c', {
xhrFields: { withCredentials: true },
type: 'GET',
dataType: 'json',
success: (data) => {
if (data.data.has_login) {
balh_auth_window.document.body.innerHTML = '<meta charset="UTF-8" name="viewport" content="width=device-width">正在跳转……';
balh_auth_window.location.href = data.data.confirm_uri;
} else {
balh_auth_window.close()
alert('必须登录B站才能正常授权')
}
},
error: (e) => {
getKeyFail();
}
})
}
function gotKey(detail) {
var access_key = detail.finalUrl.match(/access_key=([a-f0-9]{32})/);
if (access_key != null) {
userInfo.access_key = access_key[1];
userInfo.isLogin = true;
GM_setValue('access_key', userInfo.access_key);
localStorage.BWVC_oauthTime = Date.now();
console.log('[BWVC] 已获取并存储 access_key: ' + userInfo.access_key);
}
}
function getKeyFail() {
console.error('[BWVC] 获取 access_key 失败');
}
document.head.appendChild(_('style', {}, [_('text', `
.upload {
display: block;
float: left;
margin-top: 70px;
width: 259px;
cursor: pointer;
text-align:center
}
.upload.nonclick {
cursor: default
}
.upload-icon {
width: 64px;
height: 74px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAlCAYAAAAqXEs9AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QAAAAAAAD5Q7t/AAAACXBIWXMAAAsSAAALEgHS3X78AAACcUlEQVRYw9WXu24TQRSGzxJhFMWFeYFESro4wSkoLCUdFBTkJRIBHXSkhCcg3iiRKCjgFYCCmyUQBQVtRBqQENCk5iJHSvRR+Gx8vF7v7I53gjjSSrszO/98M/9cRUoGMAu81We2bPlKA1gFDhnEIbD6r2A2gB6j0QM2zxJkCnhgADo571OhYRrAC63wKOmJhEDfNzUP4CVwMSRQ14yVNZN+CqTfa2ZsdUMCLQPvgLlU+hCQps3pv8vBgHJAR4CqinNn3pr/HghY1Gl+kKw7ZQRxRw/4pHUs5gnVgF3gJK0wrlJPIBsnwENgOgvmtVlvYqAN1PMq9bEEqKt2bNavLlCzP+1pxg9gpagtPkApnRWtE2A3SWwCx0rrhKkSSLVaOq6OgaYA26q/U0IkiWfA3QqgdlRvW3Q2AbQ9gJJ4BVyZAKitOgdiBlathEAS68AX8/0YaHgAXUgmlNd4sGWAaeA+8EeTv/nYeKo5KZBJW9Ax5WVj5UAmz8vGYECan2Vjo5BmCCDzn7VxvYhm6N0efUREIq/WFmx5EcvuGcu+4zhrj7VsUiDgOvDZDOonLhgX0JFrkRwz7eeBp6lpf7WQTQ6gDzi2kYyFMW3PVlGQIkC3gdhR+L0W87LHBTS0l9HfV3pAK6fwFsNRyp4Mvbrq/Mzc7YFbOA5rCvXcx54MrWS337fnoTj1003tvQ5wGZiZtOIcoFgZOgIs0T+tjdhE/6x9B/gI/AoEc4nBiXEpSSx1pq4QpsXgTL2X7gl76whmEzCj2h3G3ToMVOa9LGCM3MuijBY0ReSGiFwTkXkROV9xJ/0Wka8i8kZEHkVRtG8z/wIV2ewJsbhgYwAAAABJRU5ErkJggg==);
background-repeat: no-repeat;
background-position: 50%;
vertical-align: middle;
filter: invert(1);
width:100%
}
.upload-progress {
height:10px;
width:100%;
background:#666;
position:relative;
border-radius:5px;
overflow:hidden
}
.upload-progress .upload-progress-inner {
animation: pre-loading 3s linear infinite;
background:#23ade5;
width:30px;
height:10px;
position:absolute;
top:0;
left:-50px;
}
.upload-progress.uploading .upload-progress-inner {
animation: none;
width:0;
left:0
}
@keyframes pre-loading {
from {
left: -50px;
}
to {
left: calc(100% + 20px)
}
}
`
)]));
var observer = new MutationObserver(function () {
if ($('.blink-qrcode').length)
$('.blink-qrcode')[0].replaceWith(_('div', { className: 'upload', event: { click: selectFile } }, [
_('div', { className: 'upload-icon' }),
_('div', { className: 'upload-text' }, [_('text', '上传视频')]),
_('input', { type: 'file', style: { visibility: 'hidden' }, event: { change: preUpload }, accept: '.mp4,.flv,.avi,.wmv,.mov,.webm,.mpeg4,.ts,.mpg,.rm,.rmvb,.mkv' }),
_('input', { type: 'file', style: { visibility: 'hidden' }, event: { change: preUpload }, accept: '.jpg,.jpeg,.png' })
])),
observer.disconnect();
});
observer.observe(document.body, { subtree: true, childList: true });
var uploading = false;
window.addEventListener('beforeunload', function () { return !uploading; });
function uploadStatusUpdate(text, progress) {
if (uploading && $('.upload .upload-icon').length) {
$('.upload')[0].replaceWith(_('div', { className: 'upload nonclick' }, [
_('div', { className: 'upload-progress' }, [_('div', { className: 'upload-progress-inner' })]),
_('div', { className: 'upload-text' }, [_('text', '')])
]));
}
$('.upload .upload-text')[0].innerHTML = text;
if (progress != undefined) {
var prog = $('.upload .upload-progress')[0];
prog.classList.add('uploading');
prog.firstChild.style.width = progress;
}
}
function selectFile(e) {
if (uploading || e.target.nodeName.toLowerCase() == 'input') return;
if (userInfo.isLogin) {
$('.upload input')[0].click();
$('.upload input')[1].click();
} else {
getKey();
//uploadStatusUpdate('缺少账户key');
}
}
var uploadFile, fileInput, imgInput;
function preUpload() {
fileInput = $('.upload input')[0];
imgInput = $('.upload input')[1];
if (!imgInput.files.length) return uploadStatusUpdate('请选择封面');
if (!fileInput.files.length) return;
uploadFile = fileInput.files[0];
uploading = true;
uploadStatusUpdate('确认权限');
$.ajax({
url: 'https://api.vc.bilibili.com/clip/v1/user/isMobileVerified?' + signParam({
access_key: userInfo.access_key,
actionKey: 'appkey'
}),
success: getTags,
error: function () { },
dataType: 'json',
});
function getTags(json) {
if (json.code == 0) {
uploadStatusUpdate('获取可用tag列表');
$.ajax({
type: 'GET',
url: 'https://api.vc.bilibili.com/api/v1/tag/list',
success: gotTags,
error: function () { },
dataType: 'json',
});
} else {
uploadStatusUpdate('未绑定手机,无法投稿');
uploading = false;
}
}
}
var detailForm;
function gotTags(json) {
if (json.code == 0) {
uploadStatusUpdate('请填写投稿信息');
json.data.tags.unshift('');
detailForm = $('.upload .upload-text')[0].appendChild(_('form', { event: { submit: uploadImg } }, [
_('text', '视频标题:'), _('input', { type: 'text', name: 'titleVal', placeholder: '稿件标题…', style: { border: '1px solid #888' } }),
_('div', { style: { display: 'flex', width: '100%', flexDirection: 'row', textAlign: 'left' } }, [
_('label', { style: { flex: 1, width: '50%' } }, [_('input', { type: 'radio', name: 'cfrom', value: 1 }), _('text', '自制')]),
_('label', { style: { flex: 1, width: '50%' } }, [_('input', { type: 'radio', name: 'cfrom', value: 2 }), _('text', '转载')]),
]),
_('br'),
_('text', '视频标签:'),
_('select', { name: 'tag' }, json.data.tags.map(function (i) {
return _('option', { value: i }, [_('text', i)]);
})),
_('br'),
_('text', '上传线程:'),
_('select', { name: 'thread' }, [1, 2, 3, 4, 5].map(function (i) {
return _('option', { value: i }, [_('text', i)]);
})),
_('br'),
_('input', { type: 'submit', value: '确认', style: { cursor: 'pointer', padding: '5px 20px' } }),
_('div')
]));
detailForm['titleVal'].value = '';
detailForm['cfrom'].value = 2;
detailForm['tag'].value = '';
detailForm['thread'].value = 1;
} else {
uploadStatusUpdate('获取tag列表失败');
uploading = false;
}
}
function uploadImg(e) {
e.preventDefault();
if (detailForm['titleVal'].value == '' || detailForm['tag'].value == '') {
detailForm.lastChild.textContent = '还没填完信息';
return;
}
uploadStatusUpdate('上传封面中');
var param = signParam({
access_key: userInfo.access_key,
actionKey: 'appkey',
device: 'phone',
mobi_app: 'biliLink',
platform: 'ios',
rnd: (Date.now() / 1e3) | 0,
timestamp: (Date.now() / 1e3) | 0,
ts: (Date.now() / 1e3) | 0
});
var form = new FormData();
form.append('rnd', (Date.now() / 1e3) | 0);
form.append('timestamp', (Date.now() / 1e3) | 0);
form.append('file_up', imgInput.files[0], 'coverImage.jpg');
$.ajax({
type: 'POST',
processData: false,
contentType: false,
url: 'https://api.vc.bilibili.com/api/v1/image/upload?' + param,
data: form,
success: createVC,
error: function () { },
dataType: 'json',
});
}
var uploadInfo;
function createVC(json) {
if (json.code == 0) {
uploadStatusUpdate('创建投稿');
var param = signParam({
_hwid: '',
access_key: userInfo.access_key,
actionKey: 'appkey',
category: 0,
cfrom: detailForm['cfrom'].value,
cover_urls: json.data.image_url,
description: detailForm['titleVal'].value,
device: 'phone',
latitude: '',
lontitude: '',
mobi_app: 'biliLink',
platform: 'ios',
rnd: Date.now(),
src: 'appstore',
tags: detailForm['tag'].value,
timestamp: (Date.now() / 1e3) | 0,
title: uploadFile.name
});
$.ajax({
type: 'POST',
processData: false,
url: 'https://api.vc.bilibili.com/clip/v1/video/create?' + param,
data: param,
success: createdVC,
error: function () { },
dataType: 'json',
});
} else {
uploadStatusUpdate('上传封面出错:' + JSON.stringify(json));
uploading = false;
}
}
function createdVC(json) {
if (json.code == 0) {
uploadStatusUpdate('开始上传');
uploadInfo = {
uploaded: 0,
filename: json.data.cloud_filename,
upload_url: json.data.cloud_upload_url.replace(/https:/, ''),
complete_url: json.data.cloud_complete_url,
id: json.data.video_id,
uploader: new WebUploader.create({
attachInfoToQuery: false,
browse_button: fileInput,
timeout: 5 * 60 * 1000,
runtimeOrder: 'html5',
prepareNextFile: true,
method: 'POST',
pick: {
id: '#upload',
multiple: false
},
dnd: '',
chunked: true,
chunkSize: 4 * 1024 * 1024, // options.chunk_size !== undefined ? options.chunk_size : 0,
fileSingleSizeLimit: 4 * 1024 * 1024 * 1024,
sendAsBinary: true,
chunkRetry: 10,
fileNumLimit: 1,
fileSizeLimit: 4 * 1024 * 1024 * 1024,
accept: [
{
extensions: "txt,mp3,mp4,flv,avi,wmv,mov,webm,mpeg4,ts,mpg,rm,rmvb,mkv",
title: 'videos',
}
],
auto: true,
duplicate: false, //options.prevent_duplicates,
threads: (detailForm['thread'].value | 0) || 1,
})
};
var uploader = uploadInfo.uploader;
uploader.total = {
bytesPerSec: 0
};
uploader.onUploadSuccess = uploadedVC;
uploader.onUploadProgress = uploadProgress;
uploader.trigger('change');
setTimeout(function () {
uploader.trigger('change');
uploader.addFile(uploadFile);
}, 50);
} else {
uploadStatusUpdate('创建失败:[' + json.code + ']' + json.message);
uploading = false;
}
}
function uploadProgress(file, perc) {
perc = (perc * 100).toFixed(2) + '%';
uploadStatusUpdate('已上传 ' + perc, perc);
}
function uploadedVC() {
uploadStatusUpdate('上传成功,id <a href="//vc.bilibili.com/video/' + uploadInfo.id + '" target="_blank">' + uploadInfo.id + '</a><br><a href="http://link.bilibili.com/p/world/index#/' + document.cookie.match(/DedeUserID=(\d+)/)[1] + '/world/vc" target="_blank">投稿列表</a>', '100%');
uploading = false;
}
WebUploader.Uploader.register({
'name': '_',
'before-send': function (block) {
var deferred = WebUploader.Deferred();
uploadInfo.uploader.md5File(block.blob).done(function (md5) {
var form = {
chunk: block.chunk,
filename: uploadInfo.filename,
filesize: block.end - block.start,
md5: md5,
file: block.blob
};
block.options = {
method: 'POST',
server: uploadInfo.upload_url,
formData: form,
sendAsBinary: false,
};
deferred.resolve(block);
});
return deferred.promise();
},
'after-send-file': function (file) {
var deferred = WebUploader.Deferred();
uploadInfo.uploader.md5File(file).done(function (md5) {
var param = signParam({
_hwid: '',
access_key: userInfo.access_key,
actionKey: 'appkey',
chunks: file.blocks.length,
filesize: file.size,
md5: md5,
device: 'phone',
mobi_app: 'biliLink',
platform: 'ios',
rnd: Date.now(),
src: 'appstore',
tags: '游戏片段',
timestamp: (Date.now() / 1e3) | 0,
name: uploadFile.name
});
$.ajax({
type: 'POST',
url: uploadInfo.complete_url,
data: param
}).success(function () {
deferred.resolve(file);
}).retry({
times: 5
}).fail(function () {
deferred.reject(file);
});
});
return deferred.promise();
}
});