// ==UserScript==
// @name Bangumi 标签批量管理
// @description Bangumi 标签批量管理和修改自己可见
// @namespace org.upsuper.bangumi
// @include /^https?://(bgm\.tv|chii\.in|bangumi\.tv)/(anime|book|music|game|real)/list/.+$/
// @version 2.4
// @grant GM_addStyle
// ==/UserScript==
var TIMEOUT = 3000,
RETRY_INTERVAL = 1000;
function $(q, e) { return (e ? e : document).querySelector(q); }
function $a(q, e) { return (e ? e : document).querySelectorAll(q); }
function $c(t) { return document.createElement(t); }
String.prototype.u$format = function () {
var args = arguments;
return this.replace(/{(\d+)}/g, function (match, i) {
return args[i] !== undefined ? args[i] : match;
});
};
// check username
var username = $('.idBadgerNeue a.avatar').href.split('/').pop();
var urlPieces = location.href.split(/[\/\?]/);
if (username !== urlPieces[5])
return;
var urlBase = urlPieces.slice(0, 7).join('/');
// insert style
GM_addStyle('\
#userTagList>li>a.l { margin-right: 32px; } \
#userTagList>li>a.__u_edit, #userTagList>li>a.__u_del { \
width: 16px; height: 16px; \
padding: 4px 0; float: right; \
text-align: center; color: #aaa; \
line-height: 16px; font-size: 10px; \
} \
a.__u_add { \
width: 16px; height: 16px; \
padding: 3px 0; float: right; \
text-align: center; color: #fff; \
line-height: 16px; font-size: 10px; \
} \
#__u_pb { \
position: fixed; \
top: 0; \
width: 100%; \
} \
#__u_pb[max="0"] { \
display: none; \
} \
');
// add progress bar
var $pb = $c('progress');
var workingJobs = 0;
$pb.id = '__u_pb';
$pb.max = $pb.value = 0;
document.body.appendChild($pb);
// update tag list
var $tags = $a('#userTagList>li');
for (var i = 0; i < $tags.length; i++) {
var $tag = $tags[i],
$anchor = $tag.getElementsByTagName('a')[0];
var $button;
$button = $c('a');
$button.href = '#';
$button.className = '__u_del';
$button.title = '删除';
$button.textContent = 'x';
$tag.insertBefore($button, $anchor);
$button = $c('a');
$button.href = '#';
$button.className = '__u_edit';
$button.title = '编辑';
$button.textContent = '#';
$tag.insertBefore($button, $anchor);
}
// add checkboxes to item list
var $items = $a('#browserItemList>li');
for (var i = 0; i < $items.length; i++) {
var $item = $items[i],
$modify = $('.collectModify', $item);
var $checkbox = $c('input');
$checkbox.type = 'checkbox';
$modify.insertBefore($checkbox, $modify.firstChild);
}
// add new tag button
var $panel = $('#userTagList').parentNode;
var $newtag = $c('a');
$newtag.href = '#';
$newtag.className = '__u_add';
$newtag.title = '添加';
$newtag.textContent = '+';
$newtag.addEventListener('click', function (evt) {
var ids = [];
evt.preventDefault();
var $items = $a('#browserItemList>li');
for (var i = 0; i < $items.length; i++) {
var $item = $items[i],
$chk = $('input[type=checkbox]', $item);
if ($chk.checked)
ids.push($item.id.substr(5));
}
if (!ids.length) {
alert('请先选择条目');
return;
}
var newTag = prompt('请输入新标签名:');
if (!newTag) return;
batchChangeTag(ids, null, newTag, function (id) {
//$('#item_{0} input[type=checkbox]'.u$format(id)).checked = false;
});
});
$panel.insertBefore($newtag, $panel.firstChild);
// add a button for making subject privacy 2016/08/04
function add_btn(class_name, title, text, callback) {
var $panel = $('#userTagList').parentNode;
var $newtag = $c('a');
$newtag.href = '#';
$newtag.className = class_name;
$newtag.title = title;
$newtag.textContent = text;
$panel.insertBefore($newtag, $panel.firstChild);
$newtag.addEventListener('click', function (evt) {
var ids = [];
evt.preventDefault();
var $items = $a('#browserItemList>li');
for (var i = 0; i < $items.length; i++) {
var $item = $items[i],
$chk = $('input[type=checkbox]', $item);
if ($chk.checked)
ids.push($item.id.substr(5));
}
if (!ids.length) {
alert('请先选择条目');
return;
}
var flag_privacy = 1;
if (text === 'c')
flag_privacy = 0;
callback(ids, flag_privacy, function (id) {
//$('#item_{0} input[type=checkbox]'.u$format(id)).checked = false;
});
});
}
add_btn('__u_add', '自己可见', 'p', batch_make_privacy);
add_btn('__u_add', '取消自己可见', 'c', batch_make_privacy);
// bind event
$('#userTagList').addEventListener('click', function (evt) {
var className = evt.target.className;
if (className != '__u_edit' && className != '__u_del')
return;
var $li = evt.target.parentNode;
var oldTag = decodeURIComponent($('a.l', $li).href.split('=')[1]);
var newTag;
evt.preventDefault();
if (className == '__u_del') {
if (!confirm('确认要删除标签“{0}”吗?'.u$format(oldTag)))
return;
newTag = '';
} else {
newTag = prompt('请输入新的标签名:');
if (!newTag) return;
}
changeTagName(oldTag, newTag, $li);
}, true);
// process
function changeTagName(oldTag, newTag, $li) {
var $anchor = $('a.l', $li);
var num = parseInt($('small', $anchor).textContent),
pageNum = Math.ceil(num / 24);
var ids = [];
var url = urlBase + '?tag=' + encodeURIComponent(oldTag) + '&page=';
$pb.max += num + pageNum;
workingJobs++;
getListPage(1, function () {
batchChangeTag(ids, oldTag, newTag, updateProcessBar);
});
function updateProcessBar() {
$pb.value++;
}
function getListPage(page, callback) {
var xhr = new XMLHttpRequest();
var received = false;
var watchdog = setTimeout(function () {
watchdog = 0;
if (received) return;
xhr.abort();
getListPage(page, callback);
}, TIMEOUT);
xhr.open('GET', url + page, true);
xhr.send(null);
xhr.onreadystatechange = function () {
if (this.readyState != 4 || this.status != 200)
return;
received = true;
if (watchdog) {
clearTimeout(watchdog);
watchdog = 0;
}
var content = this.responseText;
var regx = /<li id="item_(\d+)"/g, match;
while (match = regx.exec(content))
ids.push(match[1]);
updateProcessBar();
if (page < pageNum)
getListPage(page + 1, callback);
else
callback();
};
}
}
function batchChangeTag(ids, oldTag, newTag, updateProcessBar) {
var $iframe = $c('iframe');
$iframe.style.display = 'none';
document.body.appendChild($iframe);
function nextItem() {
var id = ids.shift();
if (id) {
changeTag(id, oldTag, newTag,
$iframe, updateProcessBar, nextItem);
} else {
workingJobs--;
if (workingJobs == 0)
location.reload();
}
}
nextItem();
}
function changeTag(id, oldTag, newTag,
$iframe, updateProcessBar, callback) {
var url = '/update/' + id;
var watchdog;
stage0();
function stage0() {
$iframe.src = url;
$iframe.onload = stage1;
finished = false;
watchdog = setTimeout(function () {
if ($iframe.onload != stage1)
return;
url += '?';
stage0();
}, TIMEOUT);
}
function stage1() {
if (watchdog) {
clearTimeout(watchdog);
watchdog = 0;
}
var doc = $iframe.contentDocument;
var $tags = doc.getElementById('tags');
if (!$tag)
setTimeout(stage0, RETRY_INTERVAL);
if (oldTag) {
var tags = $tags.value.trim().split(/\s+/);
for (var i = 0; i < tags.length; i++)
if (tags[i].toLowerCase() == oldTag.toLowerCase())
tags[i] = newTag;
$tags.value = tags.join(' ');
} else {
$tags.value += ' ' + newTag;
}
doc.forms[0].submit();
$iframe.onload = stage2;
watchdog = setTimeout(function () {
if ($iframe.onload != stage2)
return;
doc.forms[0].submit();
}, TIMEOUT);
}
function stage2() {
if (watchdog) {
clearTimeout(watchdog);
watchdog = 0;
}
$iframe.onload = undefined;
updateProcessBar(id);
callback();
}
}
// add a function for make subject privacy 2016/08/04
function make_privacy(id, flag_privacy,
$iframe, updateProcessBar, callback) {
console.log('begin', id, flag_privacy);
var url = '/update/' + id;
var watchdog;
stage0();
function stage0() {
$iframe.src = url;
$iframe.onload = stage1;
finished = false;
watchdog = setTimeout(function () {
if ($iframe.onload != stage1)
return;
url += '?';
stage0();
}, TIMEOUT);
}
function stage1() {
if (watchdog) {
clearTimeout(watchdog);
watchdog = 0;
}
var doc = $iframe.contentDocument;
var $privacy_checkbox = doc.getElementById('privacy');
if (!$privacy_checkbox)
window.setTimeout(stage0, RETRY_INTERVAL);
if (flag_privacy)
$privacy_checkbox.setAttribute('checked', 'checked');
else {
//$privacy_checkbox.setAttribute('checked', false);
$privacy_checkbox.removeAttribute('checked');
console.log('removed', id);
}
doc.forms[0].submit();
$iframe.onload = stage2;
watchdog = setTimeout(function () {
if ($iframe.onload != stage2)
return;
doc.forms[0].submit();
}, TIMEOUT);
}
function stage2() {
if (watchdog) {
clearTimeout(watchdog);
watchdog = 0;
}
$iframe.onload = undefined;
updateProcessBar(id);
callback();
}
console.log('finished', id);
}
function batch_make_privacy(ids, flag_privacy, updateProcessBar) {
var $iframe = $c('iframe');
$iframe.style.display = 'none';
document.body.appendChild($iframe);
function nextItem() {
var id = ids.shift();
if (id) {
make_privacy(id, flag_privacy,
$iframe, updateProcessBar, nextItem);
} else {
workingJobs--;
if (workingJobs === 0)
location.reload();
}
}
nextItem();
}