CharWhoIWouldveknow

show voiced characters that are from subjects in your collections

  1. // ==UserScript==
  2. // @name CharWhoIWouldveknow
  3. // @namespace https://jirehlov.com
  4. // @version 0.2.5
  5. // @description show voiced characters that are from subjects in your collections
  6. // @author Jirehlov
  7. // @include /^https?:\/\/(bgm\.tv|bangumi\.tv|chii\.in)\/person\/\d+\/works\/voice+/
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. let collectStatus = {};
  14. const limit = 50;
  15. let guess = 1000000;
  16. let totalItems = 0;
  17. let allData = [];
  18. let username = null;
  19. let updatingData = false;
  20. let getLi = null;
  21. let numCollected = 0;
  22. let numNotCollected = 0;
  23. if (localStorage.getItem('bangumi_subject_collectStatus')) {
  24. collectStatus = JSON.parse(localStorage.getItem('bangumi_subject_collectStatus'));
  25. }
  26. const idBadgerNeue = document.querySelector('.idBadgerNeue');
  27. if (idBadgerNeue) {
  28. const avatarLink = idBadgerNeue.querySelector('.avatar');
  29. if (avatarLink) {
  30. const href = avatarLink.getAttribute('href');
  31. username = href.substring(href.lastIndexOf('/') + 1);
  32. }
  33. }
  34. let subject_type = [
  35. 1,
  36. 2,
  37. 3,
  38. 4,
  39. 6
  40. ];
  41. let collection_type = [
  42. 1,
  43. 2,
  44. 3,
  45. 4,
  46. 5
  47. ];
  48. let subject_type_index = 0;
  49. function isSubjectCollected(subjectId) {
  50. return collectStatus[subjectId] === 'collect';
  51. }
  52. function findSubjectId(element) {
  53. const aElement = element.querySelector('a');
  54. if (aElement) {
  55. const href = aElement.getAttribute('href');
  56. const subjectIdMatch = href.match(/\/subject\/(\d+)/);
  57. if (subjectIdMatch) {
  58. return subjectIdMatch[1];
  59. }
  60. }
  61. return null;
  62. }
  63. async function copyListItemIfMultipleClearitItems() {
  64. const browserList = document.querySelectorAll('.browserList > li');
  65. for (const browserListItem of browserList) {
  66. browserListItem.classList.add('filteredchars1');
  67. const clearitItems = browserListItem.querySelectorAll('.innerRightList.rr li.clearit');
  68. if (clearitItems.length > 1) {
  69. const clone1 = browserListItem.cloneNode(true);
  70. const clone2 = browserListItem.cloneNode(true);
  71. clone1.classList.remove('filteredchars1');
  72. clone2.classList.remove('filteredchars1');
  73. clone1.classList.add('filteredchars2');
  74. clone2.classList.add('filteredchars3');
  75. clone1.style.display = 'none';
  76. clone2.style.display = 'none';
  77. browserListItem.parentNode.insertBefore(clone1, browserListItem.nextSibling);
  78. browserListItem.parentNode.insertBefore(clone2, browserListItem.nextSibling);
  79. const clearitItems2 = clone1.querySelectorAll('.innerRightList.rr li.clearit');
  80. for (const clearitItem of clearitItems2) {
  81. const subjectId = findSubjectId(clearitItem);
  82. if (subjectId && !isSubjectCollected(subjectId)) {
  83. clearitItem.remove();
  84. }
  85. }
  86. const clearitItems3 = clone2.querySelectorAll('.innerRightList.rr li.clearit');
  87. for (const clearitItem of clearitItems3) {
  88. const subjectId = findSubjectId(clearitItem);
  89. if (subjectId && isSubjectCollected(subjectId)) {
  90. clearitItem.remove();
  91. }
  92. }
  93. if (clone1.querySelectorAll('.innerRightList.rr li.clearit').length === 0) {
  94. clone1.remove();
  95. }
  96. if (clone2.querySelectorAll('.innerRightList.rr li.clearit').length === 0) {
  97. clone2.remove();
  98. }
  99. } else {
  100. const clearitItem = browserListItem.querySelector('.innerRightList.rr li.clearit');
  101. const subjectId = findSubjectId(clearitItem);
  102. if (subjectId) {
  103. browserListItem.classList.add(isSubjectCollected(subjectId) ? 'filteredchars2' : 'filteredchars3');
  104. }
  105. }
  106. await new Promise(resolve => setTimeout(resolve, 1));
  107. }
  108. }
  109. function checkCharactersInPage(filterType) {
  110. const browserListItems = document.querySelectorAll('.browserList > li');
  111. browserListItems.forEach(item => {
  112. if (filterType === 1 && item.classList.contains('filteredchars1')) {
  113. item.style.display = 'block';
  114. } else if (filterType === 2 && item.classList.contains('filteredchars2')) {
  115. item.style.display = 'block';
  116. } else if (filterType === 3 && item.classList.contains('filteredchars3')) {
  117. item.style.display = 'block';
  118. } else {
  119. item.style.display = 'none';
  120. }
  121. });
  122. }
  123. function countCollectedAndNotCollected() {
  124. const filteredChars2Items = document.querySelectorAll('.filteredchars2 .innerRightList.rr li.clearit');
  125. const filteredChars3Items = document.querySelectorAll('.filteredchars3 .innerRightList.rr li.clearit');
  126. numCollected = filteredChars2Items.length;
  127. numNotCollected = filteredChars3Items.length;
  128. }
  129. function createFilterButtons() {
  130. const subjectFilterElement = document.querySelector('.subjectFilter');
  131. if (subjectFilterElement) {
  132. const groupedUL = document.createElement('ul');
  133. groupedUL.className = 'grouped clearit';
  134. const titleLi = document.createElement('li');
  135. titleLi.classList.add('title');
  136. titleLi.innerHTML = '<span>收藏状态</span>';
  137. const collectedLi = document.createElement('li');
  138. collectedLi.innerHTML = `<a href="javascript:;" class="l"><span>已收藏 (${ numCollected })</span></a>`;
  139. collectedLi.addEventListener('click', () => {
  140. checkCharactersInPage(2);
  141. });
  142. const notCollectedLi = document.createElement('li');
  143. notCollectedLi.innerHTML = `<a href="javascript:;" class="l"><span>未收藏 (${ numNotCollected })</span></a>`;
  144. notCollectedLi.addEventListener('click', () => {
  145. checkCharactersInPage(3);
  146. });
  147. const allLi = document.createElement('li');
  148. allLi.innerHTML = '<a href="javascript:;" class="l"><span>全部</span></a>';
  149. allLi.addEventListener('click', () => {
  150. checkCharactersInPage(1);
  151. });
  152. getLi = document.createElement('li');
  153. getLi.innerHTML = '<a href="javascript:;" class="l"><span>收藏数据有误\uFF1F单击手动刷新</span></a>';
  154. getLi.addEventListener('click', () => {
  155. getData();
  156. });
  157. groupedUL.appendChild(titleLi);
  158. groupedUL.appendChild(allLi);
  159. groupedUL.appendChild(collectedLi);
  160. groupedUL.appendChild(notCollectedLi);
  161. groupedUL.appendChild(getLi);
  162. subjectFilterElement.appendChild(groupedUL);
  163. }
  164. }
  165. async function fetchData(collection_type, offset) {
  166. const url = `https://api.bgm.tv/v0/users/${ username }/collections?subject_type=${ subject_type[subject_type_index] }&type=${ collection_type }&limit=${ limit }&offset=${ offset }`;
  167. const headers = { 'Accept': 'application/json' };
  168. const response = await fetch(url, { headers });
  169. const data = await response.json();
  170. return data;
  171. }
  172. async function getData() {
  173. if (updatingData) return;
  174. updatingData = true;
  175. console.log(`Update started.`);
  176. if (getLi) {
  177. getLi.innerHTML = '<a href="javascript:;" class="l"><span>更新中</span></a>';
  178. getLi.removeEventListener('click', getData);
  179. getLi.style.pointerEvents = 'none';
  180. }
  181. for (let ct = 1; ct < collection_type.length; ct++) {
  182. for (let i = 0; i < subject_type.length; i++) {
  183. subject_type_index = i;
  184. const initialData = await fetchData(ct, guess);
  185. if ('description' in initialData && initialData.description.includes('equal to')) {
  186. totalItems = parseInt(initialData.description.split('equal to ')[1]);
  187. console.log(`Updated totalItems to: ${ totalItems }`);
  188. } else {
  189. totalItems = 0;
  190. }
  191. for (let offset = 0; offset < totalItems; offset += limit) {
  192. const data = await fetchData(ct, offset);
  193. allData.push(...data.data);
  194. console.log(`Fetched ${ offset + 1 }-${ offset + limit } items...`);
  195. }
  196. }
  197. }
  198. for (const item of allData) {
  199. const subjectId = item.subject_id;
  200. collectStatus[subjectId] = 'collect';
  201. }
  202. localStorage.setItem('bangumi_subject_collectStatus', JSON.stringify(collectStatus));
  203. updatingData = false;
  204. if (getLi) {
  205. getLi.innerHTML = '<a href="javascript:;" class="l"><span>更新结束</span></a>';
  206. }
  207. console.log(`Update completed.`);
  208. }
  209. (async () => {
  210. await copyListItemIfMultipleClearitItems();
  211. countCollectedAndNotCollected();
  212. createFilterButtons();
  213. })();
  214. }());

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址