Bangumi 标签批量管理

Bangumi 标签批量管理和修改自己可见

// ==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();
}