bilipager

人类能用的B站分P列表

  1. // ==UserScript==
  2. // @name bilipager
  3. // @namespace http://s.xmcp.ml/
  4. // @version 0.3.3
  5. // @description 人类能用的B站分P列表
  6. // @author xmcp
  7. // @match *://www.bilibili.com/video/*
  8. // @supportURL https://github.com/xmcp/bilipager
  9. // @contributionURL https://s.xmcp.ml/pakkujs/donate.png
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. const ZINDEX_NORMAL=114514;
  14. const ZINDEX_FULLSCREEN=2147483647;
  15. const WIDTH=350;
  16. const ANIMATION_TIME_MS=200;
  17. const CSSTEXT=`
  18. .bilipager-list::-webkit-scrollbar {
  19. width: 10px;
  20. height: 10px;
  21. }
  22. .bilipager-list::-webkit-scrollbar-track {
  23. background-color: rgba(255,255,255,.3);
  24. }
  25. .bilipager-list::-webkit-scrollbar-thumb {
  26. background-color: rgba(128,128,128,.6);
  27. }
  28. .bilipager-list::-webkit-scrollbar-thumb:active {
  29. background-color: rgba(128,128,128,1);
  30. }
  31.  
  32. .bilipager-list:empty, .bilipager-popover:empty {
  33. display: none;
  34. }
  35.  
  36. .bilipager-list {
  37. width: ${WIDTH}px;
  38. position: fixed;
  39. height: 100%;
  40. background-color: rgba(225,225,225,.9);
  41. top: 0;
  42. left: -${WIDTH-1}px;
  43. opacity: 0;
  44. z-index: ${ZINDEX_FULLSCREEN};
  45. box-sizing: border-box;
  46. padding: 2em 0;
  47. overflow-y: auto;
  48. word-break: break-all;
  49. transition: left ${ANIMATION_TIME_MS}ms ease-out, opacity ${ANIMATION_TIME_MS}ms ease-out;
  50. }
  51. .bilipager-list:hover, .bilipager-list.hover {
  52. left: 0;
  53. opacity: 1;
  54. }
  55.  
  56. .bilipager-list p {
  57. overflow: hidden;
  58. padding: .5em .3em .5em .5em;
  59. cursor: pointer;
  60. white-space: nowrap;
  61. transition: padding .2s ease;
  62. }
  63.  
  64. .bilipager-list p code {
  65. font-family: Consolas, Courier, monospace;
  66. }
  67.  
  68. .bilipager-list p span {
  69. font-size: 1.2em;
  70. }
  71.  
  72. .bilipager-list p:hover {
  73. background-color: rgba(255,255,255,.8);
  74. white-space: normal;
  75. padding: .5em 0 .5em .8em;
  76. box-shadow: 0 1px 25px rgba(0,0,0,.3);
  77. }
  78.  
  79. .bilipager-list p.bilipager-curp {
  80. background-color: black;
  81. color: white;
  82. }
  83.  
  84. .bilipager-list p.animation {
  85. color: black;
  86. -webkit-animation: page-switch 1s ease;
  87. animation: page-switch 1s ease;
  88. }
  89.  
  90. @-webkit-keyframes page-switch {
  91. 0% {box-shadow: 0 1px 25px rgba(0,0,0,.3); background-color: rgba(255,255,255,.8);}
  92. 20% {box-shadow: 0 1px 25px rgba(0,0,255,.5); background-color: rgba(205,205,255,1); }
  93. 50% {box-shadow: 0 1px 25px rgba(0,0,255,.5); background-color: rgba(205,205,255,1); }
  94. 100% {box-shadow: 0 1px 25px rgba(0,0,0,.3); background-color: rgba(255,255,255,.8);}
  95. }
  96. @keyframes page-switch {
  97. 0% {box-shadow: 0 1px 25px rgba(0,0,0,.3); background-color: rgba(255,255,255,.8);}
  98. 20% {box-shadow: 0 1px 25px rgba(0,0,255,.5); background-color: rgba(205,205,255,1); }
  99. 50% {box-shadow: 0 1px 25px rgba(0,0,255,.5); background-color: rgba(205,205,255,1); }
  100. 100% {box-shadow: 0 1px 25px rgba(0,0,0,.3); background-color: rgba(255,255,255,.8);}
  101. }
  102.  
  103. .bilipager-popover {
  104. min-width: 150px;
  105. position: absolute;
  106. height: 1.7em;
  107. line-height: 1.7em;
  108. font-size: 1.2em;
  109. padding: 0 .5em;
  110. background-color: black;
  111. color: white;
  112. top: 42px;
  113. left: 10px;
  114. z-index: ${ZINDEX_NORMAL};
  115. border-radius: 3px;
  116. word-break: break-all;
  117. white-space: nowrap;
  118. }
  119.  
  120. .bilipager-popover:before {
  121. content: "";
  122. position: absolute;
  123. width: 0;
  124. height: 0;
  125. top: 50%;
  126. left: -4px;
  127. margin-top: -5px;
  128. border-width: 5px 5px 5px 0;
  129. border-color: transparent;
  130. border-right-color: black;
  131. border-style: solid;
  132. }
  133. `;
  134.  
  135. (function() {
  136. 'use strict';
  137.  
  138. let list_root=document.createElement('div');
  139. list_root.className='bilipager-list';
  140. list_root.addEventListener('mousewheel',function(e) {
  141. e.stopPropagation();
  142. });
  143.  
  144. let popover=document.createElement('div');
  145. popover.className='bilipager-popover';
  146. popover.addEventListener('mouseover',function(e) {
  147. list_root.classList.add('hover');
  148. setTimeout(function() {
  149. list_root.classList.remove('hover');
  150. },ANIMATION_TIME_MS+10);
  151. });
  152.  
  153. let playlist_cache={};
  154.  
  155. function format_duration(d) {
  156. function pad(t) {
  157. return (''+t).padStart(2,'0');
  158. }
  159. return d<3600 ?
  160. (Math.floor(d/60)+':'+pad(d%60)) :
  161. (Math.floor(d/3600)+':'+pad(Math.floor((d%3600)/60))+':'+pad(d%60));
  162. }
  163.  
  164. function reload_ui(aid) {
  165. if(!playlist_cache[aid]) {
  166. playlist_cache[aid]=fetch('https://api.bilibili.com/x/player/pagelist?aid='+aid).then(res=>res.json());
  167. }
  168. playlist_cache[aid].then(function(plist) {
  169. list_root.textContent='';
  170. popover.textContent='';
  171.  
  172. console.log('!!',plist);
  173. if(plist.data.length<=1) return;
  174.  
  175. plist.data.forEach(function(p) {
  176. let li=document.createElement('p');
  177.  
  178. let li_1=document.createElement('code');
  179. li_1.textContent=`[${p.page}] ${format_duration(p.duration)} `;
  180. li.appendChild(li_1);
  181. let li_2=document.createElement('span');
  182. li_2.textContent=`${p.part}`;
  183. li.appendChild(li_2);
  184.  
  185. li.addEventListener('click',function() {
  186. li.classList.add('animation');
  187.  
  188. const ind_10=Math.floor((p.page-1)/10)*10+1;
  189. const ind_30=Math.floor((p.page-1)/30)*30+1;
  190.  
  191. function paginate_failed() {
  192. //alert('pagination failed');
  193. location.href='//www.bilibili.com/video/av'+aid+'/?p='+p.page;
  194. }
  195.  
  196. for(const pager_30 of document.querySelectorAll('#multi_page .more-box li')) {
  197. if(pager_30.textContent.startsWith(ind_30+'-')) {
  198. pager_30.click();
  199. setTimeout(function() {
  200. for(const pager_10 of document.querySelectorAll('#multi_page .paging li')) {
  201. if(pager_10.textContent.startsWith(ind_10+'-')) {
  202. pager_10.click();
  203. setTimeout(function() {
  204. const paginate_link=document.querySelector(`a.router-link-active[href="/video/av${aid}/?p=${p.page}"]`);
  205. if(paginate_link) {
  206. console.log('switch: pagniate link');
  207. paginate_link.click();
  208. return;
  209. }
  210. paginate_failed();
  211. },1);
  212. return;
  213. }
  214. }
  215. paginate_failed();
  216. },1);
  217. return;
  218. }
  219. }
  220. paginate_failed();
  221. });
  222. list_root.appendChild(li);
  223.  
  224. if(p.cid===parseInt(window.cid)) {
  225. li.className='bilipager-curp';
  226. if(li.scrollIntoViewIfNeeded) {
  227. li.scrollIntoViewIfNeeded();
  228. } else {
  229. li.scrollIntoView(false);
  230. }
  231. popover.textContent=`[${p.page}/${plist.data.length}] ${p.part}`;
  232. }
  233. });
  234. });
  235. }
  236.  
  237. function setup_listener() {
  238. function onfschange(e) {
  239. const elem=document.fullscreenElement||document.webkitFullscreenElement||document.mozFullScreenElement||document.body;
  240. if(list_root.parentNode!==elem) {
  241. if(list_root.parentNode) {
  242. list_root.parentNode.removeChild(list_root);
  243. }
  244. elem.appendChild(list_root);
  245. }
  246. }
  247. document.addEventListener('fullscreenchange',onfschange);
  248. document.addEventListener('webkitfullscreenchange',onfschange);
  249. document.addEventListener('mozfullscreenchange',onfschange);
  250.  
  251. addEventListener('message',function(e) {
  252. if(e.data.type==='pakku_event_danmaku_loaded') {
  253. reload_ui(window.aid);
  254. }
  255. });
  256. }
  257.  
  258. if(window.aid) {
  259. let cssobj=document.createElement('style');
  260. cssobj.textContent=CSSTEXT;
  261. document.head.appendChild(cssobj);
  262. document.body.appendChild(list_root);
  263. document.body.appendChild(popover);
  264. setup_listener();
  265. reload_ui(window.aid);
  266. }
  267. })();