Bangumi 标签批量管理

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

  1. // ==UserScript==
  2. // @name Bangumi 标签批量管理
  3. // @description Bangumi 标签批量管理和修改自己可见
  4. // @namespace org.upsuper.bangumi
  5. // @include /^https?://(bgm\.tv|chii\.in|bangumi\.tv)/(anime|book|music|game|real)/list/.+$/
  6. // @version 2.4
  7. // @grant GM_addStyle
  8. // ==/UserScript==
  9.  
  10. var TIMEOUT = 3000,
  11. RETRY_INTERVAL = 1000;
  12.  
  13. function $(q, e) { return (e ? e : document).querySelector(q); }
  14. function $a(q, e) { return (e ? e : document).querySelectorAll(q); }
  15. function $c(t) { return document.createElement(t); }
  16. String.prototype.u$format = function () {
  17. var args = arguments;
  18. return this.replace(/{(\d+)}/g, function (match, i) {
  19. return args[i] !== undefined ? args[i] : match;
  20. });
  21. };
  22.  
  23. // check username
  24. var username = $('.idBadgerNeue a.avatar').href.split('/').pop();
  25. var urlPieces = location.href.split(/[\/\?]/);
  26. if (username !== urlPieces[5])
  27. return;
  28. var urlBase = urlPieces.slice(0, 7).join('/');
  29.  
  30. // insert style
  31. GM_addStyle('\
  32. #userTagList>li>a.l { margin-right: 32px; } \
  33. #userTagList>li>a.__u_edit, #userTagList>li>a.__u_del { \
  34. width: 16px; height: 16px; \
  35. padding: 4px 0; float: right; \
  36. text-align: center; color: #aaa; \
  37. line-height: 16px; font-size: 10px; \
  38. } \
  39. a.__u_add { \
  40. width: 16px; height: 16px; \
  41. padding: 3px 0; float: right; \
  42. text-align: center; color: #fff; \
  43. line-height: 16px; font-size: 10px; \
  44. } \
  45. #__u_pb { \
  46. position: fixed; \
  47. top: 0; \
  48. width: 100%; \
  49. } \
  50. #__u_pb[max="0"] { \
  51. display: none; \
  52. } \
  53. ');
  54.  
  55. // add progress bar
  56. var $pb = $c('progress');
  57. var workingJobs = 0;
  58. $pb.id = '__u_pb';
  59. $pb.max = $pb.value = 0;
  60. document.body.appendChild($pb);
  61.  
  62. // update tag list
  63. var $tags = $a('#userTagList>li');
  64. for (var i = 0; i < $tags.length; i++) {
  65. var $tag = $tags[i],
  66. $anchor = $tag.getElementsByTagName('a')[0];
  67. var $button;
  68.  
  69. $button = $c('a');
  70. $button.href = '#';
  71. $button.className = '__u_del';
  72. $button.title = '删除';
  73. $button.textContent = 'x';
  74. $tag.insertBefore($button, $anchor);
  75.  
  76. $button = $c('a');
  77. $button.href = '#';
  78. $button.className = '__u_edit';
  79. $button.title = '编辑';
  80. $button.textContent = '#';
  81. $tag.insertBefore($button, $anchor);
  82. }
  83.  
  84. // add checkboxes to item list
  85. var $items = $a('#browserItemList>li');
  86. for (var i = 0; i < $items.length; i++) {
  87. var $item = $items[i],
  88. $modify = $('.collectModify', $item);
  89.  
  90. var $checkbox = $c('input');
  91. $checkbox.type = 'checkbox';
  92. $modify.insertBefore($checkbox, $modify.firstChild);
  93. }
  94.  
  95. // add new tag button
  96. var $panel = $('#userTagList').parentNode;
  97. var $newtag = $c('a');
  98. $newtag.href = '#';
  99. $newtag.className = '__u_add';
  100. $newtag.title = '添加';
  101. $newtag.textContent = '+';
  102. $newtag.addEventListener('click', function (evt) {
  103. var ids = [];
  104. evt.preventDefault();
  105.  
  106. var $items = $a('#browserItemList>li');
  107. for (var i = 0; i < $items.length; i++) {
  108. var $item = $items[i],
  109. $chk = $('input[type=checkbox]', $item);
  110. if ($chk.checked)
  111. ids.push($item.id.substr(5));
  112. }
  113.  
  114. if (!ids.length) {
  115. alert('请先选择条目');
  116. return;
  117. }
  118.  
  119. var newTag = prompt('请输入新标签名:');
  120. if (!newTag) return;
  121. batchChangeTag(ids, null, newTag, function (id) {
  122. //$('#item_{0} input[type=checkbox]'.u$format(id)).checked = false;
  123. });
  124. });
  125. $panel.insertBefore($newtag, $panel.firstChild);
  126.  
  127. // add a button for making subject privacy 2016/08/04
  128. function add_btn(class_name, title, text, callback) {
  129. var $panel = $('#userTagList').parentNode;
  130. var $newtag = $c('a');
  131. $newtag.href = '#';
  132. $newtag.className = class_name;
  133. $newtag.title = title;
  134. $newtag.textContent = text;
  135. $panel.insertBefore($newtag, $panel.firstChild);
  136. $newtag.addEventListener('click', function (evt) {
  137. var ids = [];
  138. evt.preventDefault();
  139.  
  140. var $items = $a('#browserItemList>li');
  141. for (var i = 0; i < $items.length; i++) {
  142. var $item = $items[i],
  143. $chk = $('input[type=checkbox]', $item);
  144. if ($chk.checked)
  145. ids.push($item.id.substr(5));
  146. }
  147.  
  148. if (!ids.length) {
  149. alert('请先选择条目');
  150. return;
  151. }
  152.  
  153. var flag_privacy = 1;
  154. if (text === 'c')
  155. flag_privacy = 0;
  156. callback(ids, flag_privacy, function (id) {
  157. //$('#item_{0} input[type=checkbox]'.u$format(id)).checked = false;
  158. });
  159. });
  160. }
  161.  
  162. add_btn('__u_add', '自己可见', 'p', batch_make_privacy);
  163. add_btn('__u_add', '取消自己可见', 'c', batch_make_privacy);
  164.  
  165. // bind event
  166. $('#userTagList').addEventListener('click', function (evt) {
  167. var className = evt.target.className;
  168. if (className != '__u_edit' && className != '__u_del')
  169. return;
  170.  
  171. var $li = evt.target.parentNode;
  172. var oldTag = decodeURIComponent($('a.l', $li).href.split('=')[1]);
  173. var newTag;
  174. evt.preventDefault();
  175. if (className == '__u_del') {
  176. if (!confirm('确认要删除标签“{0}”吗?'.u$format(oldTag)))
  177. return;
  178. newTag = '';
  179. } else {
  180. newTag = prompt('请输入新的标签名:');
  181. if (!newTag) return;
  182. }
  183.  
  184. changeTagName(oldTag, newTag, $li);
  185. }, true);
  186.  
  187. // process
  188. function changeTagName(oldTag, newTag, $li) {
  189. var $anchor = $('a.l', $li);
  190. var num = parseInt($('small', $anchor).textContent),
  191. pageNum = Math.ceil(num / 24);
  192. var ids = [];
  193. var url = urlBase + '?tag=' + encodeURIComponent(oldTag) + '&page=';
  194. $pb.max += num + pageNum;
  195. workingJobs++;
  196.  
  197. getListPage(1, function () {
  198. batchChangeTag(ids, oldTag, newTag, updateProcessBar);
  199. });
  200.  
  201. function updateProcessBar() {
  202. $pb.value++;
  203. }
  204.  
  205. function getListPage(page, callback) {
  206. var xhr = new XMLHttpRequest();
  207. var received = false;
  208.  
  209. var watchdog = setTimeout(function () {
  210. watchdog = 0;
  211. if (received) return;
  212. xhr.abort();
  213. getListPage(page, callback);
  214. }, TIMEOUT);
  215.  
  216. xhr.open('GET', url + page, true);
  217. xhr.send(null);
  218. xhr.onreadystatechange = function () {
  219. if (this.readyState != 4 || this.status != 200)
  220. return;
  221. received = true;
  222. if (watchdog) {
  223. clearTimeout(watchdog);
  224. watchdog = 0;
  225. }
  226.  
  227. var content = this.responseText;
  228. var regx = /<li id="item_(\d+)"/g, match;
  229. while (match = regx.exec(content))
  230. ids.push(match[1]);
  231.  
  232. updateProcessBar();
  233. if (page < pageNum)
  234. getListPage(page + 1, callback);
  235. else
  236. callback();
  237. };
  238. }
  239. }
  240.  
  241. function batchChangeTag(ids, oldTag, newTag, updateProcessBar) {
  242. var $iframe = $c('iframe');
  243. $iframe.style.display = 'none';
  244. document.body.appendChild($iframe);
  245.  
  246. function nextItem() {
  247. var id = ids.shift();
  248. if (id) {
  249. changeTag(id, oldTag, newTag,
  250. $iframe, updateProcessBar, nextItem);
  251. } else {
  252. workingJobs--;
  253. if (workingJobs == 0)
  254. location.reload();
  255. }
  256. }
  257. nextItem();
  258. }
  259.  
  260. function changeTag(id, oldTag, newTag,
  261. $iframe, updateProcessBar, callback) {
  262. var url = '/update/' + id;
  263. var watchdog;
  264. stage0();
  265.  
  266. function stage0() {
  267. $iframe.src = url;
  268. $iframe.onload = stage1;
  269. finished = false;
  270. watchdog = setTimeout(function () {
  271. if ($iframe.onload != stage1)
  272. return;
  273. url += '?';
  274. stage0();
  275. }, TIMEOUT);
  276. }
  277. function stage1() {
  278. if (watchdog) {
  279. clearTimeout(watchdog);
  280. watchdog = 0;
  281. }
  282.  
  283. var doc = $iframe.contentDocument;
  284. var $tags = doc.getElementById('tags');
  285. if (!$tag)
  286. setTimeout(stage0, RETRY_INTERVAL);
  287.  
  288. if (oldTag) {
  289. var tags = $tags.value.trim().split(/\s+/);
  290. for (var i = 0; i < tags.length; i++)
  291. if (tags[i].toLowerCase() == oldTag.toLowerCase())
  292. tags[i] = newTag;
  293. $tags.value = tags.join(' ');
  294. } else {
  295. $tags.value += ' ' + newTag;
  296. }
  297.  
  298. doc.forms[0].submit();
  299. $iframe.onload = stage2;
  300. watchdog = setTimeout(function () {
  301. if ($iframe.onload != stage2)
  302. return;
  303. doc.forms[0].submit();
  304. }, TIMEOUT);
  305. }
  306. function stage2() {
  307. if (watchdog) {
  308. clearTimeout(watchdog);
  309. watchdog = 0;
  310. }
  311.  
  312. $iframe.onload = undefined;
  313. updateProcessBar(id);
  314. callback();
  315. }
  316. }
  317.  
  318. // add a function for make subject privacy 2016/08/04
  319. function make_privacy(id, flag_privacy,
  320. $iframe, updateProcessBar, callback) {
  321. console.log('begin', id, flag_privacy);
  322. var url = '/update/' + id;
  323. var watchdog;
  324. stage0();
  325.  
  326. function stage0() {
  327. $iframe.src = url;
  328. $iframe.onload = stage1;
  329. finished = false;
  330. watchdog = setTimeout(function () {
  331. if ($iframe.onload != stage1)
  332. return;
  333. url += '?';
  334. stage0();
  335. }, TIMEOUT);
  336. }
  337. function stage1() {
  338. if (watchdog) {
  339. clearTimeout(watchdog);
  340. watchdog = 0;
  341. }
  342.  
  343. var doc = $iframe.contentDocument;
  344. var $privacy_checkbox = doc.getElementById('privacy');
  345. if (!$privacy_checkbox)
  346. window.setTimeout(stage0, RETRY_INTERVAL);
  347. if (flag_privacy)
  348. $privacy_checkbox.setAttribute('checked', 'checked');
  349. else {
  350. //$privacy_checkbox.setAttribute('checked', false);
  351. $privacy_checkbox.removeAttribute('checked');
  352. console.log('removed', id);
  353. }
  354.  
  355. doc.forms[0].submit();
  356. $iframe.onload = stage2;
  357. watchdog = setTimeout(function () {
  358. if ($iframe.onload != stage2)
  359. return;
  360. doc.forms[0].submit();
  361. }, TIMEOUT);
  362. }
  363. function stage2() {
  364. if (watchdog) {
  365. clearTimeout(watchdog);
  366. watchdog = 0;
  367. }
  368.  
  369. $iframe.onload = undefined;
  370. updateProcessBar(id);
  371. callback();
  372. }
  373. console.log('finished', id);
  374. }
  375.  
  376. function batch_make_privacy(ids, flag_privacy, updateProcessBar) {
  377. var $iframe = $c('iframe');
  378. $iframe.style.display = 'none';
  379. document.body.appendChild($iframe);
  380.  
  381. function nextItem() {
  382. var id = ids.shift();
  383. if (id) {
  384. make_privacy(id, flag_privacy,
  385. $iframe, updateProcessBar, nextItem);
  386. } else {
  387. workingJobs--;
  388. if (workingJobs === 0)
  389. location.reload();
  390. }
  391. }
  392. nextItem();
  393. }
  394.