indefined的脚本排版修改

网页端首页添加APP首页推荐、全站排行、可选提交不喜欢的视频

  1. // ==UserScript==
  2. // @name indefined的脚本排版修改
  3. // @namespace indefined
  4. // @version 0.1
  5. // @description 网页端首页添加APP首页推荐、全站排行、可选提交不喜欢的视频
  6. // @author indefined
  7. // @supportURL https://github.com/indefined/UserScripts/issues
  8. // @match *://www.bilibili.com/
  9. // @include https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png?*
  10. // @license MIT
  11. // @connect app.bilibili.com
  12. // @connect api.bilibili.com
  13. // @connect passport.bilibili.com
  14. // @connect link.acg.tv
  15. // @connect www.mcbbs.net
  16. // @grant GM_xmlhttpRequest
  17. // @grant GM_getValue
  18. // @grant GM_setValue
  19. // @grant GM_deleteValue
  20. // @grant GM_addStyle
  21. // @run-at document-idle
  22. // ==/UserScript==
  23.  
  24. (function() {
  25. 'use strict';
  26. if (location.href.startsWith('https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png?')) {
  27. //用于获取授权
  28. window.stop();
  29. return window.top.postMessage(location.href, 'https://www.bilibili.com');
  30. }
  31.  
  32. const style = `<style>
  33. #recommend .video-card-common {
  34. margin-bottom: 12px;
  35. }
  36. .dislike-botton,.tname {
  37. position: absolute;
  38. top: 2px;
  39. opacity: 0;
  40. overflow: hidden;
  41. white-space: nowrap;
  42. text-overflow: ellipsis;
  43. text-align: right;
  44. font-weight: bold;
  45. transition: all .3s;
  46. text-shadow: 0 1px black, 1px 0 black, -1px 0 black, 0 -1px black;
  47. color: white;
  48. z-index: 22;
  49. }
  50. .spread-module .tname,
  51. .video-card-common .tname {
  52. left: 6px;
  53. }
  54. .spread-module .dislike-botton,
  55. .video-card-common .dislike-botton {
  56. right: 6px;
  57. font-size: 14px;
  58. }
  59. .dislike-list {
  60. display:none;
  61. }
  62. .dislike-list>div:hover {
  63. text-decoration: line-through;
  64. }
  65. .video-card-common:hover .tname,
  66. .video-card-common:hover .dislike-botton,
  67. .spread-module:hover .pic .tname,
  68. .spread-module .pic:hover .dislike-botton{
  69. opacity: 1;
  70. }
  71. .dislike-botton:hover .dislike-list{
  72. display:unset;
  73. }
  74. .dislike-cover {
  75. position: absolute!important;
  76. top: 0px;
  77. width: 100%;
  78. height: 100%;
  79. background:hsla(0,0%,100%,.9);
  80. text-align: center;
  81. font-size: 15px;
  82. z-index: 22;
  83. }
  84. #ranking-all ul.rank-list {
  85. overflow-y: auto;
  86. padding-top: 0 !important;
  87. }
  88. #ranking-all .rank-head {
  89. margin-bottom: 20px !important;
  90. }
  91. #ranking-all .rank-list .rank-item.show-detail .ri-detail{
  92. width: calc(100% - 90px) !important;
  93. }
  94. </style>`;
  95.  
  96. //APP首页推荐
  97. function InitRecommend () {
  98. //初始化标题栏并注入推荐下方
  99. element.mainDiv.id = 'recommend';
  100. let scrollBox;
  101. if(element.isNew){
  102. element._s(element.mainDiv.querySelector('.storey-title'),{
  103. innerHTML:style,
  104. childs:[
  105. '<div class="l-con"><svg aria-hidden="true" class="svg-icon"><use xlink:href="#bili-douga"></use></svg><span class="name">猜你喜欢</span></div>',
  106. {
  107. nodeType:'div',
  108. className:'exchange-btn',
  109. childs:[
  110. {
  111. nodeType:'div',
  112. style: 'width: 86px;',
  113. className:'btn btn-change',
  114. innerHTML:'<i class="bilifont bili-icon_caozuo_huanyihuan"></i><span class="info">加载更多</span>',
  115. onclick:()=>{ for(let i=0;i<setting.manualFreshCount;i++) getRecommend();}
  116. },
  117. {
  118. nodeType:'span',
  119. className:'btn more',
  120. innerHTML:'<span>设置</span><i class="bilifont bili-icon_caozuo_qianwang"></i>',
  121. onclick:()=>setting.show()
  122. }
  123. ]
  124. }
  125. ]
  126. });
  127. scrollBox = element.mainDiv.querySelector('div.zone-list-box');
  128. scrollBox.classList.add('storey-box')
  129. }
  130. else {
  131. element._s(element.mainDiv.querySelector('div.zone-title'),{
  132. innerHTML:style,
  133. childs:[
  134. {
  135. nodeType:'div',
  136. className:'headline clearfix',
  137. innerHTML:'<i class="icon icon_t icon-douga"></i><span class="name">猜你喜欢</span>',
  138. childs:[
  139. {
  140. nodeType:'div',
  141. className:'link-more',style:'cursor:pointer;user-select: none;',
  142. innerHTML:'<span>设置 </span><i class="icon"></i>',
  143. onclick:()=>setting.show()
  144. },
  145. {
  146. nodeType:'div',
  147. className:'read-push',style:'cursor:pointer;user-select: none;',
  148. innerHTML:'<i class="icon icon_read"></i><span class="info">加载更多</span>',
  149. onclick:()=>{ for(let i=0;i<setting.manualFreshCount;i++) getRecommend();}
  150. }
  151. ]
  152. }
  153. ]
  154. });
  155. scrollBox = element.mainDiv.querySelector('div.storey-box.clearfix');
  156. }
  157. let listBox;
  158. element._s(scrollBox,{
  159. innerHTML:'',style:'overflow:hidden auto;display:block',
  160. childs:[listBox = element._c({
  161. nodeType:'div',className:scrollBox.className,
  162. id:'recommend-list',
  163. style:'overflow:auto',
  164. innerHTML:'<span style="display:none">empty</span>'
  165. })]
  166. });
  167. const moreButton = element._c({
  168. nodeType:'div',className:"clearfix",
  169. innerHTML:'<div class="load-state" style="cursor: pointer;padding: 4px;text-align: center;">回到推荐顶部</div>',
  170. onclick:()=>{
  171. listBox.scrollTop = 0;
  172. scrollBox.scrollTop = 0;
  173. element.mainDiv.scrollIntoView();
  174. }
  175. });
  176. scrollBox.insertAdjacentElement('afterend',moreButton);
  177. if(element.isNew) {
  178. document.querySelector('.proxy-box').insertAdjacentElement('afterbegin',element.mainDiv);
  179. }
  180. else {
  181. document.querySelector('#home_popularize').insertAdjacentElement('afterend',element.mainDiv);
  182. }
  183.  
  184. const recommends = [];//保存当前页面中的推荐元素,用于清除多余内容
  185. //显示历史推荐
  186. if(setting.historyData) updateRecommend(setting.historyData);
  187. //加载新推荐
  188. for(let i=0;i<setting.autoFreshCount;i++) getRecommend();
  189.  
  190. //如果是新版页面,因为弹性布局原因,需要根据情况设置宽度避免因为不显示滚动条干扰溢出
  191. if(element.isNew) {
  192. setting.setListWidth = function() {
  193. if(listBox.scrollHeight>listBox.clientHeight && setting.noScrollBar) {
  194. listBox.style = 'overflow-y: auto;align-content: flex-start;width: calc(100% + 20px) !important';
  195. }
  196. else {
  197. listBox.style = 'overflow-y: auto;align-content: flex-start;width: 100% !important';
  198. }
  199. }
  200. setting.setListWidth();
  201. new MutationObserver(setting.setListWidth).observe(listBox,{childList:true});
  202. }
  203. //获取推荐视频数据
  204. function getRecommend () {
  205. let loadingDiv;
  206. listBox.insertAdjacentElement('afterBegin',loadingDiv=element.getLoadingDiv('recommend'));
  207. GM_xmlhttpRequest({
  208. method: 'GET',
  209. url: 'https://app.bilibili.com/x/feed/index?build=1&mobi_app=android&idx='
  210. + (Date.now()/1000).toFixed(0) + (setting.accessKey?'&access_key='+setting.accessKey:''),
  211. onload: res=>{
  212. try {
  213. const rep = JSON.parse(res.response);
  214. if (rep.code!=0){
  215. loadingDiv.firstChild.innerText = `请求app首页失败 code ${rep.code}</br>msg ${rep.message}`;
  216. return console.error('请求app首页失败',rep);
  217. }
  218. setting.pushHistory(rep.data);
  219. updateRecommend(rep.data);
  220. loadingDiv.style.display = 'none';
  221. } catch (e){
  222. loadingDiv.firstChild.innerText = `请求app首页发生错误 ${e}`;
  223. console.error(e,'请求app首页发生错误');
  224. }
  225. },
  226. onerror: e=>{
  227. loadingDiv.firstChild.innerText = `请求app首页发生错误 ${e}`;
  228. console.error(e,'请求app首页发生错误');
  229. }
  230. });
  231. }
  232.  
  233. //旧版创建视频卡
  234. function createOldRecommend(data) {
  235. return element._c({
  236. nodeType:'div',
  237. className:'spread-module',
  238. childs:[{
  239. nodeType:'a',target:'_blank',
  240. onmouseenter: data.goto=='av'&&tools.preview,
  241. onmouseleave: data.goto=='av'&&tools.preview,
  242. onmousemove: data.goto=='av'&&tools.preview,
  243. href:data.goto=='av'?`/video/av${data.param}`:data.uri,
  244. dataset:{
  245. tag_id:data.tag?data.tag.tag_id:'',
  246. id:data.param,goto:data.goto,mid:data.mid,rid:data.tid
  247. },
  248. childs:[
  249. {
  250. nodeType:'div',className:'pic',
  251. childs:[
  252. `<div class="lazy-img"><img src="${data.cover}@160w_100h.${tools.imgType}" /></div>`,
  253. `<div class="cover-preview-module"></div>`,
  254. `<div class="mask-video"></div>`,
  255. `<div class="danmu-module"></div>`,
  256. `<span title="分区:${data.tname||data.badge}" class="tname">${data.tname||data.badge}</span>`,
  257. data.duration&&`<span class="dur">${tools.formatNumber(data.duration,'time')}</span>`||'',
  258. data.goto=='av'?{
  259. nodeType:'div',
  260. dataset:{aid:data.param},title:'稍后再看',
  261. className:'watch-later-trigger w-later',
  262. onclick:tools.watchLater
  263. }:'',
  264. (data.dislike_reasons&&setting.accessKey)?{
  265. nodeType:'div',innerText:'X',
  266. className:'dislike-botton',
  267. childs:[{
  268. nodeType:'div',
  269. className:'dislike-list',
  270. childs:data.dislike_reasons.map(reason=>({
  271. nodeType:'div',
  272. dataset:{reason_id:reason.reason_id},
  273. innerText:reason.reason_name,
  274. title:`提交因为【${reason.reason_name}】不喜欢`,
  275. onclick:dislike,
  276. }))
  277. }]
  278. }:''
  279. ]
  280. },
  281. `<p title="${data.title}" class="t">${data.title}</p>`,
  282. `<p class="num"><span class="play"><i class="icon"></i>${tools.formatNumber(data.play)}</span>`
  283. +`<span class="danmu"><i class="icon"></i>${tools.formatNumber(data.danmaku)}</span>`
  284. ]
  285. }]
  286. })
  287. }
  288. //新版创建视频卡
  289. function createNewRecommend(data) {
  290. return element._c({
  291. nodeType:'div',style:'display:block',
  292. className:'video-card-common',
  293. childs:[
  294. {
  295. nodeType:'div',className:'card-pic',
  296. onmouseenter: data.goto=='av'&&tools.preview,
  297. onmouseleave: data.goto=='av'&&tools.preview,
  298. onmousemove: data.goto=='av'&&tools.preview,
  299. dataset:{
  300. tag_id:data.tag?data.tag.tag_id:'',
  301. id:data.param,goto:data.goto,mid:data.mid,rid:data.tid
  302. },
  303. childs:[
  304. `<a href="${data.goto=='av'?`/video/av${data.param}`:data.uri}" target="_blank">`
  305. + `<img src="${data.cover}@216w_122h_1c_100q.${tools.imgType}"><div class="count">`
  306. + `<div class="left"><span><i class="bilifont bili-icon_shipin_bofangshu"></i>${tools.formatNumber(data.play)}</span>`
  307. +(data.like&&`<span><i class="bilifont bili-icon_shipin_dianzanshu"></i>${tools.formatNumber(data.like)}</span></div>`||'</div>')
  308. + `<div class="right"><span>${data.duration&&tools.formatNumber(data.duration,'time')||''}</span></div></div></a>`,
  309. `<div class="cover-preview-module van-framepreview"></div>`,
  310. `<div class="danmu-module van-danmu"></div>`,
  311. `<span title="分区:${data.tname||data.badge}" class="tname">${data.tname||data.badge}</span>`,
  312. data.goto=='av'?{
  313. nodeType:'div',
  314. dataset:{aid:data.param},title:'稍后再看',
  315. className:'watch-later-video van-watchlater black',
  316. onclick:tools.watchLater
  317. }:'',
  318. (data.dislike_reasons&&setting.accessKey)?{
  319. nodeType:'div',innerText:'X',
  320. className:'dislike-botton',
  321. childs:[{
  322. nodeType:'div',
  323. className:'dislike-list',
  324. childs:data.dislike_reasons.map(reason=>({
  325. nodeType:'div',
  326. dataset:{reason_id:reason.reason_id},
  327. innerText:reason.reason_name,
  328. title:`提交因为【${reason.reason_name}】不喜欢`,
  329. onclick:dislike,
  330. }))
  331. }]
  332. }:''
  333. ]
  334. },
  335. `<a href="${data.goto=='av'?`/video/av${data.param}`:data.uri}" target="_blank" title="${data.title}" class="title">${data.title}</a>`,
  336. `<a href="//space.bilibili.com/${data.mid}/" target="_blank" class="up"><i class="bilifont bili-icon_xinxi_UPzhu"></i>${data.name||data.badge}</a>`,
  337. ]
  338. })
  339. }
  340. //显示推荐视频
  341. function updateRecommend (datas){
  342. const point = listBox.firstChild;
  343. datas.forEach(data=>{
  344. const recommend = element.isNew?createNewRecommend(data):createOldRecommend(data);
  345. recommends.push(point.insertAdjacentElement('beforeBegin',recommend));
  346. });
  347. //移除多余的显示内容
  348. while(setting.pageLimit && recommends.length>setting.pageLimit) listBox.removeChild(recommends.shift());
  349. listBox.scrollTop = 0;
  350. scrollBox.scrollTop = 0;
  351. }
  352.  
  353. //提交不喜欢视频,视频数据提前绑定在页面元素上
  354. function dislike (ev) {
  355. let target=ev.target,parent=target.parentNode;
  356. let cancel;
  357. let url = `https://app.bilibili.com/x/feed/dislike`;
  358. if (parent.className!='dislike-list'){
  359. cancel = true;
  360. let deep = 1;
  361. while(!parent.dataset.id&&deep++<4){
  362. target = parent;
  363. parent=target.parentNode;
  364. }
  365. if (!parent.dataset.id){
  366. tools.toast('请求撤销稍后再看失败:页面元素异常',ev);
  367. return false;
  368. }
  369. url += `/cancel`;
  370. }else{
  371. parent = parent.parentNode.parentNode;
  372. if(!element.isNew) parent = parent.parentNode;
  373. }
  374. url += `?goto=${parent.dataset.goto}&id=${parent.dataset.id}&mid=${parent.dataset.mid}`
  375. +`&reason_id=${target.dataset.reason_id}&rid=${parent.dataset.rid}&tag_id=${parent.dataset.tag_id}`;
  376. if (setting.accessKey) url += '&access_key='+ setting.accessKey;
  377. const handleCover = ()=>{
  378. if (cancel){
  379. parent.removeChild(target);
  380. }else{
  381. const cover = document.createElement('div');
  382. cover.className = 'dislike-cover';
  383. cover.dataset.reason_id = target.dataset.reason_id;
  384. cover.innerHTML = `<a class="lazy-img"><br><br>提交成功,但愿服务器以后少给点这种东西。<br><br><b>点击撤销操作</b></a>`;
  385. cover.onclick = dislike;
  386. parent.appendChild(cover);
  387. }
  388. };
  389. //console.log(url);
  390. GM_xmlhttpRequest({
  391. method: 'GET',url,
  392. onload: res=>{
  393. try {
  394. const par = JSON.parse(res.response);
  395. if (par.code == 0){
  396. handleCover();
  397. }else if((par.code==-101 && par.message=='账号未登录(不可用)') || par.code==-400){
  398. setting.storageAccessKey(undefined);
  399. tools.toast(`未获取授权或者授权失效,请点击设置重新获取授权`);
  400. }
  401. else{
  402. tools.toast(`请求不喜欢错误 code ${par.code}</br>msg ${par.message}`,{par,url});
  403. }
  404. } catch (e){
  405. tools.toast(`请求不喜欢发生错错误${e}`,e);
  406. }
  407. },
  408. onerror: e=>{
  409. tools.toast(`请求不喜欢发生错误`,e);
  410. }
  411. });
  412. return false;
  413. }
  414. }
  415.  
  416. //全站排行榜
  417. function InitRanking(){
  418. let rankingAll;
  419. if(element.isNew) {
  420. //……直接把旧版的排行修一修搬过来用吧
  421. rankingAll = element.mainDiv.querySelector('.rank-list');
  422. element._s(rankingAll,{
  423. className:'sec-rank report-wrap-module zone-rank rank-list',
  424. innerHTML:`
  425. <style>
  426. .bili-dropdown{position:relative;display:inline-block;vertical-align:middle;background-color:#fff;cursor:default;padding:0
  427. 7px;height:22px;line-height:22px;border:1px solid #ccd0d7;border-radius:4px}.bili-dropdown:hover{border-radius:4px 4px 0
  428. 0;box-shadow:0 2px 4px rgba(0,0,0,.16)}.bili-dropdown:hover .dropdown-list{display:block}.bili-dropdown
  429. .selected{display:inline-block;vertical-align:top}.bili-dropdown .icon-arrow-down{background-position:-475px
  430. -157px;display:inline-block;vertical-align:middle;width:12px;height:6px;margin-left:5px;margin-top:-1px}.bili-dropdown
  431. .dropdown-list{position:absolute;width:100%;background:#fff;border:1px solid
  432. #ccd0d7;border-top:0;left:-1px;top:22px;z-index:10;display:none;border-radius:0 0 4px 4px}.bili-dropdown .dropdown-list
  433. .dropdown-item{cursor:pointer;margin:0;padding:3px 7px}.bili-dropdown .dropdown-list
  434. .dropdown-item:hover{background-color:#e5e9ef}.rank-list
  435. .rank-item{position:relative;padding-left:25px;margin-top:20px;overflow:hidden}.rank-list
  436. .rank-item.first{margin-top:0;margin-bottom:15px}.rank-list .rank-item
  437. .ri-num{position:absolute;color:#999;height:18px;line-height:18px;width:18px;top:0;left:0;font-size:12px;min-width:12px;text-align:center;padding:0
  438. 3px;font-weight:bolder;font-style:normal}.rank-list .rank-item.highlight .ri-num{background:#00a1d6;color:#fff}.rank-list
  439. .rank-item .ri-info-wrap{position:relative;display:block;cursor:pointer}.rank-list .rank-item .ri-info-wrap
  440. .w-later{right:160px}.rank-list .rank-item .ri-info-wrap:hover .w-later{display:block}.rank-list .rank-item
  441. .ri-preview{margin-right:5px;width:80px;height:50px;float:left;display:none;border-radius:4px;overflow:hidden}.rank-list
  442. .rank-item.show-detail .ri-preview{display:block}.rank-list .rank-item .ri-detail{float:left}.rank-list .rank-item .ri-detail
  443. .ri-title{line-height:18px;height:18px;overflow:hidden;color:#222}.rank-list .rank-item .ri-detail
  444. .ri-point{line-height:12px;color:#99a2aa;height:12px;margin-top:5px;display:none;overflow:hidden}.rank-list .rank-item.show-detail
  445. .ri-detail .ri-title{height:36px;line-height:18px;width:150px;padding:0}.rank-list .rank-item.show-detail
  446. .ri-point{display:block}.rank-list .rank-item:hover .ri-title{color:#00a1d6}.sec-rank{overflow:hidden}
  447. .sec-rank .rank-head h3{float:left;font-size:18px;font-weight:400}.sec-rank .rank-head
  448. .rank-tab{margin-left:20px;float:left}.sec-rank .rank-head .rank-dropdown{float:right}.sec-rank
  449. .rank-list-wrap{width:200%;overflow:hidden;zoom:1;transition:all .2s linear}.sec-rank .rank-list-wrap
  450. .rank-list{padding-bottom:15px;min-height:278px;width:50%;float:left;padding-top:20px;position:relative}.sec-rank .rank-list-wrap
  451. .rank-list .state{line-height:100px}.sec-rank .rank-list-wrap.show-origin{margin-left:-100%}.sec-rank
  452. .more-link{display:block;height:24px;line-height:24px;background-color:#e5e9ef;text-align:center;border:1px solid
  453. #e0e6ed;color:#222;border-radius:4px;transition:.2s}.sec-rank
  454. .more-link:hover{background-color:#ccd0d7;border-color:#ccd0d7}.sec-rank .more-link
  455. .icon-arrow-r{display:inline-block;vertical-align:middle;background-position:-478px -218px;width:6px;height:12px;margin:-2px 0 0
  456. 5px}.bili-tab{overflow:hidden;zoom:1}.bili-tab
  457. .bili-tab-item{float:left;position:relative;height:20px;line-height:20px;cursor:pointer;padding:1px 0 2px;border-bottom:1px solid
  458. transparent;margin-left:12px;transition:.2s;transition-property:border,color}.bili-tab
  459. .bili-tab-item:before{content:&quot;&quot;;display:none;position:absolute;left:50%;margin-left:-3px;bottom:0;width:0;height:0;border-bottom:3px
  460. solid #00a1d6;border-top:0;border-left:3px dashed transparent;border-right:3px dashed transparent}.bili-tab
  461. .bili-tab-item.on{background-color:transparent;border-color:#00a1d6;color:#00a1d6}.bili-tab
  462. .bili-tab-item.on:before{display:block}.bili-tab .bili-tab-item:hover{color:#00a1d6}.bili-tab
  463. .bili-tab-item:first-child{margin-left:0}ul.rank-list{width:50%!important}.video-info-module{position:absolute;top:0;left:0;width:320px;border:1px
  464. solid #ccd0d7;border-radius:4px;box-shadow:0 2px 4px
  465. rgba(0,0,0,.16);box-sizing:border-box;z-index:10020;overflow:hidden;background-color:#fff;padding:12px}.video-info-module
  466. .v-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;height:20px;line-height:12px}.video-info-module
  467. .v-info{color:#99a2aa;padding:4px 0 6px}.video-info-module .v-info
  468. span{display:inline-block;vertical-align:top;height:16px;line-height:12px}.video-info-module .v-info
  469. .name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:150px}.video-info-module .v-info
  470. .line{display:inline-block;border-left:1px solid #99a2aa;height:12px;margin:1px 10px 0}.video-info-module .v-preview{padding:8px 0
  471. 12px;border-top:1px solid #e5e9ef;height:64px}.video-info-module .v-preview
  472. .lazy-img{width:auto;float:left;margin-right:8px;margin-top:4px;height:auto;border-radius:4px;overflow:hidden;width:96px;height:60px}.video-info-module
  473. .v-preview
  474. .txt{height:60px;overflow:hidden;line-height:21px;word-wrap:break-word;word-break:break-all;color:#99a2aa}.video-info-module
  475. .v-data{border-top:1px solid #e5e9ef;padding-top:10px}.video-info-module .v-data
  476. span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:inline-block;width:70px;color:#99a2aa;line-height:12px}.video-info-module
  477. .v-data span .icon{margin-right:4px;vertical-align:top;display:inline-block;width:12px;height:12px}.video-info-module .v-data .play
  478. .icon{background-position:-282px -90px}.video-info-module .v-data .danmu .icon{background-position:-282px -218px}.video-info-module
  479. .v-data .star .icon{background-position:-282px -346px}.video-info-module .v-data .coin .icon{background-position:-282px -410px}
  480. </style>
  481. <header class="rank-head rank-header" style="margin-bottom: 16px !important;"><h3 class="name">排行</h3>
  482. <div class="bili-tab rank-tab"><div class="bili-tab-item on">全部</div><div class="bili-tab-item">原创</div></div>
  483. <div class="bili-dropdown rank-dropdown"><span class="selected">三日</span><i class="icon icon-arrow-down"></i>
  484. <ul class="dropdown-list"><li class="dropdown-item" style="display: none;">三日</li><li class="dropdown-item">一周</li></ul></div></header>
  485. <div class="rank-list-wrap"><ul class="rank-list hot-list"><li class="state"><div class="b-loading"></div></li></ul><ul class="rank-list origin-list">
  486. <li class="state"><div class="b-loading"></div></li></ul></div><a href="/ranking/all/1/1/3/" target="_blank" class="more-link">查看更多<i class="icon icon-arrow-r"></i></a>`
  487. });
  488. }
  489. else {
  490. rankingAll = element.mainDiv.querySelector('#ranking_douga');
  491. }
  492. rankingAll.id = 'ranking-all';
  493. const rankingHead = rankingAll.querySelector('.rank-head');
  494. rankingHead.firstChild.innerText = '全站排行';
  495. const tab = rankingHead.querySelector('.bili-tab.rank-tab');
  496. const dropDown = rankingHead.querySelector('.bili-dropdown.rank-dropdown');
  497. const warp = rankingAll.querySelector('.rank-list-wrap');
  498. let type = 1,day = setting.rankingDay;
  499. const data = {1:{},2:{}};
  500. const loading = element.getLoadingDiv();
  501. const detail = {};
  502. const arc_type = 1;//202010月左右全站全部排行失效,改为提供近期排行
  503. //*
  504. //20121203旧排行彻底失效,删除日期选项
  505. rankingAll.lastChild.style = tab.style = dropDown.style = 'display: none;';
  506. rankingHead.insertAdjacentHTML('beforeend',`
  507. <a href="//www.bilibili.com/v/popular/rank" target="_blank" class="more more-link" style="float: right;background: white;">
  508. 更多<i class="bilifont bili-icon_caozuo_qianwang"></i><i class="icon icon-arrow-r"></i></a>`);
  509. //*/
  510. dropDown.firstChild.innerText = setting.rankingDays[day];
  511. element._s(dropDown.lastChild,{
  512. innerHTML:'',
  513. childs:Object.entries(setting.rankingDays).map(([value,text])=>({
  514. nodeType:'li',innerText:text,
  515. dataset:{day:value},
  516. className:'dropdown-item'
  517. }))
  518. });
  519. //创建一个显示详情的浮窗
  520. detail.div = element._c({
  521. nodeType:'div',style:'display:none',
  522. className:'spread-module video-info-module',
  523. onmouseenter: ()=> (detail.div.style.display = 'block'),
  524. onmouseleave: ()=> (detail.div.style.display = 'none'),
  525. });
  526. warp.insertBefore(detail.div,warp.lastChild);
  527.  
  528. //更新显示详情浮窗内容
  529. function updateDetail(data,offsetTop){
  530. element._s(detail.div,{
  531. style: `display:"none";left:${rankingAll.offsetLeft}px;top:${offsetTop}px;`,
  532. innerHTML:['<style>.clearfix.v-data>div>span{display: block;margin-bottom: 4px;width: 100%;}',
  533. '.cover-preview-module.show {opacity: 1}',
  534. '.cover-preview-module .cover {position: absolute;left: 0;top: 7px;height: 98px;width: 100%;margin-top: 2px}',
  535. '.spread-module .pic {position: relative;display: block;overflow: hidden;border-radius: 4px}</style>',
  536. ].join(''),
  537. childs:[
  538. `<a class="v-title" target="_blank" style="color: rgb(0, 0, 0);" title="${data.title}" href="${`/video/av${data.aid}/`}">${data.title}</a>`,
  539. {
  540. nodeType:'div',
  541. className:'clearfix v-data',
  542. childs:[
  543. {nodeType:'div',style:'display: inline-block;width:160px',
  544. childs:[{
  545. nodeType:'a',target:'_blank',
  546. href: '/video/av'+data.aid,
  547. onmouseenter: tools.preview,
  548. onmouseleave: tools.preview,
  549. onmousemove: tools.preview,
  550. dataset:{id:data.aid},
  551. childs:[
  552. {
  553. nodeType:'div',className:'pic',
  554. childs:[
  555. `<div class="lazy-img" style="height:100px"><img src="${data.pic.replace(/https?:/,'')}@160w_100h.${tools.imgType}" /></div>`,
  556. `<div class="cover-preview-module ${element.isNew?'van-framepreview':''} ranking"></div>`,
  557. `<div class="mask-video"></div>`,
  558. `<div class="danmu-module van-danmu"></div>`,
  559. //`<span title="分区:${data.tname||data.badge}" class="tname">${data.tname||data.badge}</span>`,
  560. {
  561. nodeType:'div',
  562. dataset:{aid:data.aid},title:'稍后再看',
  563. className:'watch-later-trigger w-later watch-later-video van-watchlater black',
  564. onclick:tools.watchLater
  565. },
  566. ]
  567. },
  568. ]
  569. }]},
  570. {
  571. nodeType:'div',
  572. style:'display: inline-block;vertical-align: top;width: 130px;margin-left:3px',
  573. childs:[
  574. '<span class="name"><i class="icon bilifont bili-icon_xinxi_UPzhu" style="background-position: -282px -154px;"></i>'+
  575. `<a href="//space.bilibili.com/${data.owner&&data.owner.mid||data.mid}/" target="_blank" title="${data.owner&&data.owner.name||data.author}">${data.owner&&data.owner.name||data.author}</a></span>`,
  576. '<span class="play"><i class="icon bilifont bili-icon_shipin_bofangshu"></i>'+
  577. `<span title="${data.stat&&data.stat.view||data.play}">${tools.formatNumber(data.stat&&data.stat.view||data.play)}</span></span>`,
  578. '<span class="danmu"><i class="icon bilifont bili-icon_shipin_danmushu"></i>'+
  579. `<span title="${data.stat&&data.stat.danmaku||data.video_review}">${tools.formatNumber(data.stat&&data.stat.danmaku||data.video_review)}</span></span>`,
  580. '<span class="coin"><i class="icon bilifont bili-icon_shipin_yingbishu"></i>'+
  581. `<span title="${data.stat&&data.stat.coin||data.coins}">${tools.formatNumber(data.stat&&data.stat.coin||data.coins)}</span></span>`,
  582. `<span>时长:<span style="vertical-align: top;" title="${tools.formatNumber(data.duration,'time')}">${tools.formatNumber(data.duration,'time')}</span>`,
  583. `<span>综合评分:<span style="vertical-align: top;" title="${data.score||data.pts}">${tools.formatNumber(data.score||data.pts)}</span></span>`,
  584. ]
  585. },
  586. ]
  587. }
  588. ]
  589. });
  590. };
  591. //将排行数据显示到指定目标中
  592. function showData(target,data){
  593. for (let i = 0;i<data.length;i++){
  594. const itemData = data[i];
  595. element._c({
  596. nodeType:'li',
  597. className:i==0?'rank-item show-detail first highlight':i<3?'rank-item highlight':'rank-item',
  598. childs:[
  599. {
  600. nodeType:'i',className:'ri-num',innerText:i+1,
  601. onmouseenter: (ev)=> updateDetail(itemData,ev.pageY),
  602. onmouseleave: ()=> (detail.div.style.display = 'none'),
  603. },
  604. {
  605. nodeType:'a', target:"_blank",
  606. href:`/video/av${itemData.aid}/`,
  607. title:`${itemData.title}\r\n播放:${itemData.stat&&itemData.stat.view||itemData.play} ${tools.formatNumber(itemData.duration, 'time')}`,
  608. className:'ri-info-wrap clearfix',
  609. childs:[
  610. (i==0?`<div class="lazy-img ri-preview"><img src="${itemData.pic.split(':')[1]}@72w_45h.${tools.imgType}"></div>`:''),
  611. `<div class="ri-detail"><p class="ri-title">${itemData.title}</p><p class="ri-point">综合评分:${tools.formatNumber(itemData.score||itemData.pts)}</p></div>`,
  612. (i==0?{
  613. nodeType:'div',title:'添加到稍后再看',
  614. dataset:{aid:itemData.aid},className:"watch-later-trigger w-later",
  615. onclick:tools.watchLater
  616. }:'')
  617. ]
  618. }
  619. ],
  620. parent:target
  621. });
  622. }
  623. };
  624. //获取并缓存数据
  625. async function getData(type,day){
  626. if (data[type][day]) return data[type][day];
  627. //20121203旧排行彻底失效,删除日期选项
  628. return fetch('https://api.bilibili.com/x/web-interface/ranking/v2?rid=0&type=all')
  629. .then(res=>res.json())
  630. .then(list=>{
  631. if (list.code!=0){
  632. throw `请求排行榜失败 code ${list.code}</br>msg ${list.message}`;
  633. }
  634. return (data[type][day] = list.data.list);
  635. });
  636. };
  637. //更新页面排行显示状态,并调用更新显示视频
  638. function update(ev){
  639. if (ev.target.className =='dropdown-item'){
  640. dropDown.firstChild.innerText = ev.target.innerText;
  641. [].forEach.call(dropDown.lastChild.childNodes,c => {c.style.display=c==ev.target?'none':'unset';});
  642. day = ev.target.dataset.day;
  643. }else{
  644. [].forEach.call(tab.childNodes,c=>{
  645. if (c==ev.target) c.removeEventListener('mouseover',update);
  646. else c.addEventListener('mouseover',update);
  647. c.classList.toggle('on');
  648. });
  649. type = ev.target.innerText=='全部'?1:2;
  650. warp.classList.toggle('show-origin');
  651. }
  652. const target = type==1?warp.firstChild:warp.lastChild;
  653. while(target.firstChild) target.removeChild(target.firstChild);
  654. loading.firstChild.innerText = '正在加载...';
  655. target.appendChild(loading);
  656. rankingAll.lastChild.href = `/ranking/${type==1?'all':'origin'}/0/0/${day}/`;
  657. getData(type,day)
  658. .then((data)=>showData(target,data))
  659. .then(()=>target.removeChild(loading))
  660. .catch(e=>{
  661. loading.firstChild.innerText = `请求排行榜发生错误 ${e}`;
  662. console.error('请求排行榜发生错误',e);
  663. });
  664. };
  665. [].forEach.call(dropDown.lastChild.childNodes,c => {c.onclick = update;});
  666. tab.lastChild.addEventListener('mouseover',update);
  667. if (setting.noRanking) {
  668. //document.getElementById('ranking-all').style = 'display: none';
  669. } else {
  670. update({target:[].find.call(dropDown.lastChild.childNodes,n=>n.dataset.day==day)});
  671. }
  672. }
  673.  
  674. //设置,包含设置变量以及设置窗口和对应的方法
  675. const setting = {
  676. dialog:undefined,
  677. historyData:JSON.parse(GM_getValue('historyRecommend','[]')),
  678. historyLimit:isNaN(+GM_getValue('historyLimit'))?10:+GM_getValue('historyLimit'),
  679. pageLimit:+GM_getValue('pageLimit')||0,
  680. autoFreshCount:isNaN(+GM_getValue('autoFreshCount'))?1:+GM_getValue('autoFreshCount'),
  681. hotkey: (()=>{
  682. let key = GM_getValue('hotkey');
  683. if (!key) return '';
  684. return key;
  685. })(),
  686. manualFreshCount:(()=>{
  687. var mfc = GM_getValue('manualFreshCount',1);
  688. if (isNaN(mfc)||mfc<1) mfc = 1;
  689. return mfc;
  690. })(),
  691. boxHeight:+GM_getValue('boxHeight')||2,
  692. noScrollBar:!!GM_getValue('noScrollBar'),
  693. reduceHeight:!!GM_getValue('reduceHeight',0),
  694. rankingDays:{1:'昨天',3:'三日',7:'一周'},
  695. rankingDay: (()=>{
  696. var rd = GM_getValue('rankingDay',3);
  697. if (rd!=1&&rd!=3&&rd!=7) rd = 3;
  698. return rd;
  699. })(),
  700. noRanking: GM_getValue('noRanking'),
  701. setNoRanking(value){
  702. GM_setValue('noRanking', this.noRanking = value);
  703. this.setStyle();
  704. if (!value) {
  705. document.querySelector(`#ranking-all .dropdown-item[data-day="${this.rankingDay}"]`).click();
  706. }
  707. },
  708. noRankingWidth: GM_getValue('noRankingWidth'),
  709. setNoRankingWidth(value){
  710. GM_setValue('noRankingWidth', this.noRankingWidth = value);
  711. this.setStyle();
  712. },
  713. forceWidth: GM_getValue('forceWidth'),
  714. setForceWidth(value){
  715. GM_setValue('forceWidth', this.forceWidth = value);
  716. this.setStyle();
  717. },
  718. accessKey:GM_getValue('biliAppHomeKey'),
  719. storageAccessKey(key){
  720. if(key) {
  721. GM_setValue('biliAppHomeKey',this.accessKey = key);
  722. }
  723. else {
  724. delete this.accessKey;
  725. GM_deleteValue('biliAppHomeKey');
  726. }
  727. },
  728. pushHistory(data){
  729. this.historyData.unshift(...data);
  730. },
  731. saveHistory(){
  732. while(this.historyData.length>this.historyLimit) this.historyData.pop();
  733. GM_setValue('historyRecommend',JSON.stringify(this.historyData));
  734. },
  735. setHistoryLimit(limit){
  736. GM_setValue('historyLimit',this.historyLimit = +limit);
  737. },
  738. setPageLimit(limit){
  739. GM_setValue('pageLimit',this.pageLimit = +limit);
  740. },
  741. setAutoFreshCount(count){
  742. GM_setValue('autoFreshCount',this.autoFreshCount = +count);
  743. },
  744. setManualFreshCount(target){
  745. var count = +target.value;
  746. if (count<1) count = target.value = 1;
  747. GM_setValue('manualFreshCount',this.manualFreshCount = +count);
  748. },
  749. setBoxHeight(line){
  750. GM_setValue('boxHeight',this.boxHeight=+line);
  751. this.setStyle();
  752. },
  753. setScrollBar(status){
  754. GM_setValue('noScrollBar',this.noScrollBar=+status);
  755. this.setStyle();
  756. },
  757. setReduceHeight(status){
  758. GM_setValue('reduceHeight',this.reduceHeight=+status);
  759. this.setStyle();
  760. },
  761. setHotkey(ev){
  762. ev.preventDefault();
  763. const key = ev.key;
  764. if(key=='Backspace' || key == 'Delete' || key == ' ') {
  765. this.hotkey = ev.target.value = '';
  766. if (this.freshHotkey) this.freshHotkey = document.removeEventListener('keydown', this.freshHotkey);
  767. }
  768. else {
  769. this.hotkey = ev.target.value = ev.key.toUpperCase();
  770. if (!this.freshHotkey) document.addEventListener('keydown', this.freshHotkey = ev=>this.freshHotkeyHandler(ev));
  771. }
  772. GM_setValue('hotkey', this.hotkey);
  773. },
  774. freshHotkeyHandler(ev) {
  775. if (ev.target instanceof HTMLInputElement) return;
  776. if (ev.key.toUpperCase() == this.hotkey) {
  777. document.querySelector('#bili_report_douga .btn-change > i').click();
  778. }
  779. },
  780. init(){
  781. this.setStyle();
  782. if (!!this.hotkey) document.addEventListener('keydown', this.freshHotkey = ev=>this.freshHotkeyHandler(ev));
  783. },
  784. setStyle(){
  785. if(!this.styleDiv) {
  786. this.styleDiv = element._c({
  787. nodeType:'style',
  788. parent:document.head
  789. });
  790. }
  791. let html = '';
  792. if(this.noScrollBar) {
  793. //不显示滚动条情况下,将内层容器宽度设置为比外层宽度多一个滚动条,则滚动条位置会溢出被遮挡
  794. html += '#ranking-all .rank-list-wrap{width:calc(200% + 40px)}'
  795. + '#ranking-all .rank-list-wrap.show-origin{margin-left:calc(-100% - 20px)}'
  796. ;
  797. //左侧推荐容器本同理,但因为新版弹性布局如果没有滚动条内容会伸展到超出可视范围,需针对设置
  798. }
  799. else {
  800. //显示滚动条情况下,排行榜容器维持原样式,内层容器自带滚动条。
  801. //左侧推荐容器将内层高度设置为弹性,则外层容器固定高度下如果内容超出会显示滚动条。
  802. html += '#recommend #recommend-list{height:unset!important;}';
  803. }
  804.  
  805. if (this.noRanking) {
  806. html += '#ranking-all{display: none !important}';
  807. }
  808.  
  809. if (this.noRanking && this.noRankingWidth) {
  810. html += '#recommend .card-list>.zone-list-box.storey-box {width: 100% !important;}';
  811. }
  812.  
  813. const reduceHeight = this.reduceHeight? ' - 12px' : '';
  814. //设置推荐容器宽高
  815. if (element.isNew) {
  816. html +=
  817. `#recommend .storey-box, #ranking-all ul.rank-list{height:calc(404px / 2 * ${this.boxHeight} ${reduceHeight})}`
  818. + `@media screen and (max-width: 1438px) { #recommend .storey-box, #ranking-all ul.rank-list{height:calc(364px / 2 * ${this.boxHeight} ${reduceHeight})} }`;
  819. if(this.setListWidth) {
  820. //新版的推荐容器宽度针对设置,该方法由初始化推荐容器的方法自行构造,真是深井冰的一团糟乱调用
  821. this.setListWidth();
  822. }
  823. }
  824. else {
  825. //旧版因为固定间隔布局的原因,无论滚动条在内还是在外是否显示均需要维持比外层多一个滚动条宽度
  826. html += `#recommend .storey-box {height:calc(336px / 2 * ${this.boxHeight}${reduceHeight})}`
  827. + `#ranking-all ul.rank-list{height:calc(336px / 2 * ${this.boxHeight}${reduceHeight} - 16px)}`
  828. + '#recommend #recommend-list{width:calc(100% + 20px)!important;}';
  829. }
  830. if (this.forceWidth) {
  831. //强制加宽低分辨率
  832. html += `
  833. @media screen and (max-width: 1654px) {
  834. .b-footer-wrap .zone-list-box .live-card:nth-child(n+9):nth-child(-n+10),
  835. .b-footer-wrap .zone-list-box .video-card-common:nth-child(n+9):nth-child(-n+10),
  836. .b-wrap .zone-list-box .live-card:nth-child(n+9):nth-child(-n+10),
  837. .b-wrap .zone-list-box .video-card-common:nth-child(n+9):nth-child(-n+10),
  838. .b-footer-wrap .manga-list-box .manga-card:nth-child(n+9):nth-child(-n+10),
  839. .b-wrap .manga-list-box .manga-card:nth-child(n+9):nth-child(-n+10),
  840. .b-footer-wrap .extension .video-card-common:nth-child(5),
  841. .b-wrap .extension .video-card-common:nth-child(5),
  842. .b-footer-wrap .rcmd-box-wrap>.rcmd-box .video-card-reco:nth-child(n+7):nth-child(-n+8),
  843. .b-wrap .rcmd-box-wrap>.rcmd-box .video-card-reco:nth-child(n+7):nth-child(-n+8),
  844. .b-footer-wrap .recommend-box .video-card-reco:nth-child(n+7):nth-child(-n+8),
  845. .b-wrap .recommend-box .video-card-reco:nth-child(n+7):nth-child(-n+8){
  846. display:block !important;
  847. }
  848. .b-wrap .rcmd-box-wrap>.rcmd-box {
  849. width: 690px !important;
  850. }
  851. .b-footer-wrap .recommend-box, .b-wrap .recommend-box {
  852. width: 717px !important;
  853. }
  854. .b-footer-wrap .extension, .b-wrap .extension,
  855. .b-footer-wrap .zone-list-box, .b-wrap .zone-list-box,
  856. .b-footer-wrap .manga-list-box, .b-wrap .manga-list-box {
  857. width: 880px !important;
  858. }
  859. .b-footer-wrap .elevator, .b-wrap .elevator {
  860. margin-left: 602px !important;
  861. }
  862. .b-footer-wrap .zone-list-box .time-line-card, .b-wrap .zone-list-box .time-line-card {
  863. width: 210px !important;
  864. margin: 0 5px 13px 0!important;
  865. }
  866. .b-wrap {
  867. width: 1150px !important;
  868. }
  869. .b-footer-wrap .zone-list-box .article-card, .b-wrap .zone-list-box .article-card {
  870. width: 440px;
  871. }
  872. .van-popper[x-placement^=top] {
  873. top: 208px !important;
  874. }
  875. .van-popper[x-placement^=top] .popper__arrow {
  876. top: -6px;
  877. border-width: 6px !important;
  878. border-color: transparent !important;
  879. border-bottom-color: #ebeef5 !important;
  880. border-top-width: 0 !important;
  881. }
  882. .van-popper[x-placement^=top] .popper__arrow:after {
  883. bottom: 3px;
  884. top: 1px;
  885. }
  886. .van-popper[x-placement^=top] .popper__arrow:after {
  887. border-top-color: transparent !important;
  888. border-bottom-color: #fff;
  889. border-top-width: 0 !important;
  890. border-bottom-width: 6px !important;
  891. }
  892. }
  893. `;
  894. }
  895. this.styleDiv.innerHTML = html;
  896. },
  897. show(){
  898. if(this.dialog) return document.body.appendChild(this.dialog);
  899. this.dialog = element._c({
  900. nodeType:'div',
  901. id:'biliAppHomeSetting',
  902. style:'position: fixed;top: 0;bottom: 0;left: 0;right: 0;background: rgba(0,0,0,0.4);z-index: 10000;',
  903. childs:[{
  904. nodeType:'div',
  905. style:'width:400px;right:0;left:0;position:absolute;padding:20px;background:#fff;border-radius:8px;margin:auto;transform:translate(0,50%);box-sizing:content-box',
  906. childs:[
  907. '<style>div#biliAppHomeSetting>div>div { display: inline-block; width: 200px; }</style>',
  908. {
  909. nodeType:'h2',innerText:'APP首页推荐设置',
  910. style:"font-size: 20px;color: #4fc1e9;font-weight: 400;",
  911. childs: [{
  912. nodeType:'span',innerText:'X',
  913. style:"float:right;cursor: pointer;",
  914. onclick:()=>document.body.removeChild(this.dialog)
  915. }]
  916. },
  917. /*
  918. {
  919. nodeType:'div',style:'margin: 10px 0;',
  920. childs: [
  921. '<label style="margin-right: 5px;">全站排行默认:</label>',
  922. `<span style="margin-right: 5px;color:#00f" title="${[
  923. '2020年10月左右起B站不再提供可选日期全站总排行数据',
  924. '脚本显示排行更改为近期全站投稿排行,因接口限制不再提供30日数据'
  925. ].join('\r\n')}">(?)</span>`,
  926. {
  927. nodeType:'select',
  928. style:'vertical-align: top',
  929. onchange:({target})=>GM_setValue('rankingDay',(this.rankingDay = target.value)),
  930. childs:Object.entries(this.rankingDays).map(([day,text])=>({
  931. nodeType:'option',value:day,innerText:text,
  932. })),
  933. value:this.rankingDay
  934. }
  935. ]
  936. },
  937. */
  938. {
  939. nodeType:'div',style:'margin: 10px 0;',
  940. childs: [
  941. '<label style="margin-right: 5px;">保存推荐数量:</label>',
  942. `<span style="margin-right: 5px;color:#00f" title="${[
  943. '页面关闭时会保存此数量的最新推荐,保存的推荐下次打开首页时会显示在新推荐的下方',
  944. '提交不喜欢的状态不会被保存在本地,但是已经提交给服务器所以没有必要再次提交',
  945. '每10条推荐占用空间约2KB,注意不要保存太多以免拖慢脚本管理器'
  946. ].join('\r\n')}">(?)</span>`,
  947. {
  948. nodeType:'input',type:'number',value:this.historyLimit,min:0,step:10,
  949. onchange:({target})=>this.setHistoryLimit(target.value),
  950. style:'width:50px'
  951. },
  952. ]
  953. },
  954. {
  955. nodeType:'div',style:'margin: 10px 0;',
  956. childs: [
  957. '<label style="margin-right: 5px;">页面显示限制:</label>',
  958. `<span style="margin-right: 5px;color:#00f" title="${[
  959. '页面中显示的推荐数量限制,超出的旧推荐会从推荐框中清除',
  960. '0表示无限制,更改设置后需要点击加载更多才会生效',
  961. '应当比保存推荐设置的数量大,否则保存的推荐不会全部被显示没有意义'
  962. ].join('\r\n')}">(?)</span>`,
  963. {
  964. nodeType:'input',type:'number',value:this.pageLimit,min:0,step:10,
  965. onchange:({target})=>this.setPageLimit(+target.value),
  966. style:'width:50px'
  967. }
  968. ]
  969. },
  970. {
  971. nodeType:'div',style:'margin: 10px 0;',
  972. childs: [
  973. '<label style="margin-right: 5px;">自动刷新页数:</label>',
  974. '<span style="margin-right: 5px;color:#00f" title="每次打开首页时自动加载的新推荐页数,每页10条">(?)</span>',
  975. {
  976. nodeType:'input',type:'number',value:this.autoFreshCount,min:0,step:1,
  977. onchange:({target})=>this.setAutoFreshCount(+target.value),
  978. style:'width:50px'
  979. }
  980. ]
  981. },
  982. {
  983. nodeType:'div',style:'margin: 10px 0;',
  984. childs: [
  985. '<label style="margin-right: 5px;">手动刷新页数:</label>',
  986. '<span style="margin-right: 5px;color:#00f" title="每次点击加载更多时加载的新推荐页数,每页10条">(?)</span>',
  987. {
  988. nodeType:'input',type:'number',value:this.manualFreshCount,min:1,step:1,
  989. onchange:({target})=>this.setManualFreshCount(target),
  990. style:'width:50px'
  991. }
  992. ]
  993. },
  994. {
  995. nodeType:'div',style:'margin: 10px 0;',
  996. childs: [
  997. '<label style="margin-right: 5px;">显示推荐高度:</label>',
  998. '<span style="margin-right: 5px;color:#00f" title="显示推荐框的行数,超出的推荐内容会产生滚动条来容纳">(?)</span>',
  999. {
  1000. nodeType:'input',type:'number',value:this.boxHeight,min:2,step:2,
  1001. onchange:({target})=>this.setBoxHeight(+target.value),
  1002. style:'width:50px'
  1003. }
  1004. ]
  1005. },
  1006. {
  1007. nodeType:'div',style:'margin: 10px 0;',
  1008. childs: [
  1009. '<label style="margin-right: 5px;">刷新快捷键:</label>',
  1010. '<span style="margin-right: 5px;color:#00f" title="设置一个加载更多的快捷键,如果为空则关闭">(?)</span>',
  1011. {
  1012. nodeType:'input',value:this.hotkey,
  1013. onkeydown:ev => this.setHotkey(ev),
  1014. style:'width:50px'
  1015. }
  1016. ]
  1017. },
  1018. {
  1019. nodeType:'div',style:'margin: 10px 0;',
  1020. childs: [
  1021. '<label style="margin-right: 5px;">高度减去间隔:</label>',
  1022. '<span style="margin-right: 5px;color:#00f" title="勾选此项推荐框的高度将减去一行间隔,如果你对浏览器进行缩放导致显示超出可以尝试勾选">(?)</span>',
  1023. {
  1024. nodeType:'input',type:'checkbox',checked:this.reduceHeight,
  1025. onchange:({target})=>this.setReduceHeight(target.checked),
  1026. style:'vertical-align: bottom',
  1027. },
  1028. ]
  1029. },
  1030. {
  1031. nodeType:'div',style:'margin: 10px 0;',
  1032. childs: [
  1033. '<label style="margin-right: 5px;">不显示排行榜:</label>',
  1034. '<span style="margin-right: 5px;color:#00f" title="勾选此项将不显示全站排行榜,但是右侧会存在空白">(?)</span>',
  1035. {
  1036. nodeType:'input',type:'checkbox',checked:this.noRanking,
  1037. onchange:({target})=>this.setNoRanking(target.checked),
  1038. style:'vertical-align: bottom',
  1039. },
  1040. ]
  1041. },
  1042. {
  1043. nodeType:'div',style:'margin: 10px 0;',
  1044. childs: [
  1045. '<label style="margin-right: 5px;">不显示滚动条:</label>',
  1046. '<span style="margin-right: 5px;color:#00f" title="勾选此项将不显示滚动条,但是列表仍然可以滚动">(?)</span>',
  1047. {
  1048. nodeType:'input',type:'checkbox',checked:this.noScrollBar,
  1049. onchange:({target})=>this.setScrollBar(target.checked),
  1050. style:'vertical-align: bottom',
  1051. },
  1052. ]
  1053. },
  1054. {
  1055. nodeType:'div',style:'margin: 10px 0;',
  1056. childs: [
  1057. '<label style="margin-right: 5px;">无排行榜加宽:</label>',
  1058. '<span style="margin-right: 5px;color:#00f" title="勾选此项将在不显示排行榜时加宽推荐框,但是推荐内容可能会对不齐">(?)</span>',
  1059. {
  1060. nodeType:'input',type:'checkbox',checked:this.noRankingWidth,
  1061. onchange:({target})=>this.setNoRankingWidth(target.checked),
  1062. style:'vertical-align: bottom',
  1063. },
  1064. ]
  1065. },
  1066. {
  1067. nodeType:'div',style:'margin: 10px 0;',
  1068. childs: [
  1069. '<label style="margin-right: 5px;">低分辨率加宽:</label>',
  1070. '<span style="margin-right: 5px;color:#00f" title="勾选此项将把1654px分辨率以下显示整体强制加宽到每行5个推荐,效果和副作用未知,自行尝试">(?)</span>',
  1071. {
  1072. nodeType:'input',type:'checkbox',checked:this.forceWidth,
  1073. onchange:({target})=>this.setForceWidth(target.checked),
  1074. style:'vertical-align: bottom',
  1075. },
  1076. ]
  1077. },
  1078. {
  1079. nodeType:'div',style:'margin: 10px 0;',
  1080. },
  1081. {
  1082. nodeType:'div',style:'margin: 10px 0;',
  1083. childs: [
  1084. '<label style="margin-right: 5px;">APP接口授权:</label>',
  1085. `<span style="margin-right: 5px;color:#00f" title="${[
  1086. '目前获取根据个人观看喜好的APP首页数据和提交定制不喜欢的视频需要获取授权key。',
  1087. '点击获取授权将从官方授权接口获取一个授权key,获取的key保存在脚本管理器内。',
  1088. '如果不想使用授权,脚本仍然能从官方接口获取随机推荐视频,但内容可能不再根据个人喜好且无法提交不喜欢内容。',
  1089. '点击删除授权可从脚本管理器中删除已获取授权key,脚本将按照没有获取授权的情况执行。',
  1090. '授权key有效期大约一个月,如果看到奇怪的推荐提交不喜欢时遇到奇怪的错误可以尝试删除授权重新获取。'
  1091. ].join('\r\n')}">(?)</span>`,
  1092. {
  1093. nodeType:'button',
  1094. style:'padding:0 15px;height:30px;background:#4fc1e9;color:white;border-radius:5px;border:none;cursor:pointer;',
  1095. innerText:this.accessKey?'删除授权':'获取授权',
  1096. onclick:({target})=>this.handleKey(target)
  1097. },
  1098. ]
  1099. },
  1100. {
  1101. nodeType:'div',
  1102. childs:[
  1103. '<a href="https://github.com/indefined/UserScripts/issues" target="_blank">github问题反馈</a>',
  1104. `<a href="https://gf.qytechs.cn/scripts/368446" target="_blank" style="padding-left:20px;">当前版本:${GM_info.script.version}</a>`
  1105. ]
  1106. }
  1107. ]
  1108. }],
  1109. parent:document.body
  1110. });
  1111. },
  1112. handleKey(target){
  1113. if (target.innerText === '删除授权') {
  1114. this.storageAccessKey(undefined);
  1115. target.innerText = '获取授权';
  1116. tools.toast('删除授权成功');
  1117. return;
  1118. }
  1119. else {
  1120. target.innerText = '获取中...';
  1121. target.style['pointer-events'] = 'none';
  1122. let tip = '请求授权接口错误';
  1123. fetch('https://passport.bilibili.com/login/app/third?appkey=27eb53fc9058f8c3'
  1124. +'&api=https%3A%2F%2Fwww.mcbbs.net%2Ftemplate%2Fmcbbs%2Fimage%2Fspecial_photo_bg.png&sign=04224646d1fea004e79606d3b038c84a',{
  1125. method:'GET',
  1126. credentials: 'include',
  1127. }).then(res=>{
  1128. return res.json().catch(e=>{
  1129. throw ({tip,msg:'返回数据异常:',data:res})
  1130. });
  1131. }).then(data=>{
  1132. if (data.code||!data.data) {
  1133. throw({tip,msg:data.msg||data.message||data.code,data});
  1134. }
  1135. else if (!data.data.has_login) {
  1136. throw({tip,msg:'你必须登录(不可用)B站之后才能使用授权',data});
  1137. }
  1138. else if (!data.data.confirm_uri) {
  1139. throw({tip,msg:'无法获得授权网址',data});
  1140. }
  1141. else {
  1142. return data.data.confirm_uri;
  1143. }
  1144. }).then(url=>new Promise((resolve,reject)=>{
  1145. let tip = '获取授权错误';
  1146. window.addEventListener('message', ev=>{
  1147. if (ev.origin!='https://www.mcbbs.net' || !ev.data) return;
  1148. const key = ev.data.match(/access_key=([0-9a-z]{32})/);
  1149. if (key) {
  1150. this.storageAccessKey(key[1]);
  1151. tools.toast('获取授权成功');
  1152. target.innerText = '删除授权';
  1153. clearTimeout(timeout);
  1154. document.body.contains(iframe) && document.body.removeChild(iframe);
  1155. resolve();
  1156. }
  1157. else {
  1158. reject({tip,msg:'没有获得匹配的密钥',data:ev});
  1159. }
  1160. });
  1161. let timeout = setTimeout(()=>{
  1162. document.body.contains(iframe) && document.body.removeChild(iframe);
  1163. reject({tip,msg:'请求超时'});
  1164. }, 5000)
  1165. let iframe = element._c({
  1166. nodeType:'iframe',
  1167. style:'display:none',
  1168. src: url,
  1169. parent: document.body
  1170. });
  1171. })).catch(error=> {
  1172. target.innerText = '获取授权';
  1173. tools.toast(`${error.tip}:${error.msg}`,error);
  1174. }).then(()=>{
  1175. target.style['pointer-events'] = 'unset';
  1176. });
  1177. }
  1178. }
  1179. };
  1180.  
  1181. //页面元素助手,包含克隆的一个未初始化版块和创建、设置页面元素的简单封装
  1182. const element = {
  1183. mainDiv:(()=>{
  1184. try{
  1185. return document.querySelector('#bili_douga').cloneNode(true);
  1186. }catch(e){
  1187. return undefined;
  1188. }
  1189. })(),
  1190. getLoadingDiv(target){
  1191. return this._c({
  1192. nodeType:'div',style:target=='recommend'?'padding:0;width:100%;height:unset;text-align: center;':'text-align: center;',
  1193. className:target=='recommend'?'load-state spread-module':'load-state',
  1194. innerHTML:'<span class="loading">正在加载...</span>'
  1195. });
  1196. },
  1197. _c(config){
  1198. if(config instanceof Array) return config.map(item=>this._c(item));
  1199. const item = document.createElement(config.nodeType);
  1200. return this._s(item,config);
  1201. },
  1202. _s(item,config){
  1203. for(const i in config){
  1204. if(i=='nodeType') continue;
  1205. if(i=='childs' && config.childs instanceof Array) {
  1206. config.childs.forEach(child=>{
  1207. if(child instanceof HTMLElement) item.appendChild(child);
  1208. else if (typeof(child)=='string') item.insertAdjacentHTML('beforeend',child);
  1209. else item.appendChild(this._c(child));
  1210. });
  1211. }
  1212. else if(i=='parent') {
  1213. config.parent.appendChild(item);
  1214. }
  1215. else if(config[i] instanceof Object && item[i]){
  1216. Object.entries(config[i]).forEach(([k,v])=>{
  1217. item[i][k] = v;
  1218. });
  1219. }
  1220. else{
  1221. item[i] = config[i];
  1222. }
  1223. }
  1224. return item;
  1225. }
  1226. };
  1227.  
  1228. //一些通用模块
  1229. const tools = {
  1230. token:(()=>{
  1231. try{
  1232. return document.cookie.match(/bili_jct=([0-9a-fA-F]{32})/)[1];
  1233. }catch(e){
  1234. console.error('添加APP首页推荐找不到token,请检查是否登录(不可用)');
  1235. return undefined;
  1236. }
  1237. })(),
  1238. imgType:(()=>{
  1239. try{
  1240. return 0==document.createElement('canvas').toDataURL("image/webp").indexOf("data:image/webp")?'webp':'jpg';
  1241. }catch(e){
  1242. return 'jpg';
  1243. }
  1244. })(),
  1245. toast(msg,error){
  1246. if(error) console.error(msg,error);
  1247. const div = element._c({
  1248. nodeType:'div',
  1249. style:'position: fixed;top: 50%;left: 50%;z-index: 999999;padding: 12px 24px;font-size: 14px;'
  1250. +'width: 240px; margin-left: -120px;background: #ffb243;color: #fff;border-radius: 6px;',
  1251. innerHTML:msg,
  1252. parent:document.body
  1253. });
  1254. setTimeout(()=>document.body.removeChild(div),2000);
  1255. return false;
  1256. },
  1257. formatNumber (input,format='number'){
  1258. if (format=='time'){
  1259. let second = input%60;
  1260. let minute = Math.floor(input/60);
  1261. let hour;
  1262. if (minute>60){
  1263. hour = Math.floor(minute/60);
  1264. minute = minute%60;
  1265. }
  1266. if (second<10) second='0'+second;
  1267. if (minute<10) minute='0'+minute;
  1268. return hour?`${hour}:${minute}:${second}`:`${minute}:${second}`;
  1269. }else{
  1270. return input>9999?`${(input/10000).toFixed(1)}万`:input||0;
  1271. }
  1272. },
  1273. watchLater (ev){
  1274. const target = ev.target;
  1275. const req = new XMLHttpRequest();
  1276. const action = target.classList.contains('added')?'del':'add';
  1277. req.open('POST','//api.bilibili.com/x/v2/history/toview/'+action);
  1278. req.withCredentials = true;
  1279. req.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8');
  1280. req.onload = res=>{
  1281. try{
  1282. var list = JSON.parse(res.target.response);
  1283. if (list.code!=0){
  1284. tools.toast(`请求稍后再看错误 code ${list.code}</br>msg:${list.message}`,{list,target});
  1285. return;
  1286. }
  1287. target.classList.toggle('added');
  1288. target.title = target.classList.contains('added')?'移除稍后再看':'稍后再看';
  1289. }catch(e){
  1290. tools.toast('请求稍后再看发生错误',e);
  1291. }
  1292. };
  1293. req.send(`aid=${target.dataset.aid}&csrf=${tools.token}`);
  1294. return false;
  1295. },
  1296. //视频预览……做得挺深井冰的……
  1297. previewImage (pv,target,width) {
  1298. if(!pv||!target||!target.cover) return;
  1299. let pWidth = target.parentNode.offsetWidth, data = target.cover,
  1300. percent = +width/pWidth,
  1301. index = Math.floor(percent*data.index.length),
  1302. url = data.image[Math.floor(index/data.img_x_len/data.img_y_len)],
  1303. size = pWidth * data.img_x_len,
  1304. y = Math.floor(index/data.img_x_len) * -pWidth/data.img_x_size * data.img_y_size,
  1305. x = (index % target.cover.img_x_len) * -pWidth;
  1306. if(pv.classList.contains('van-framepreview')) {
  1307. if(pv.classList.contains('ranking')) y += 10;
  1308. pv.style = `background-image: url(${url}); background-position: ${x}px ${y}px; background-size: ${size}px;opacity:1;`;
  1309. pv.innerHTML = `<div class="van-fpbar-box"><span style="width: ${percent*100}%;display:block;"></span></div>`;
  1310. }
  1311. else {
  1312. pv.innerHTML = `<div class="cover" style="background-image: url(${url}); background-position: ${x}px ${y}px; background-size: ${size}px;"></div>`
  1313. + `<div class="progress-bar van-fpbar-box"><span style="width: ${percent*100}%;display:block;"></span></div>`
  1314. }
  1315. },
  1316. previewDanmu (target,status) {
  1317. if(!target||!target.data||!target.data.length||!target.previewDanmu) return;
  1318. clearInterval(target.timmer);
  1319. if(status) {
  1320. target.previewDanmu();
  1321. target.timmer = setInterval(target.previewDanmu, 2.5*1000);
  1322. }
  1323. else {
  1324. target.style.opacity = 0;
  1325. }
  1326. },
  1327. preview (ev){
  1328. if(!ev.target) return;
  1329. let deep = 1,target = ev.target;
  1330. while(!target.dataset.id&&deep++<4){
  1331. target=target.parentNode;
  1332. }
  1333. const pv = target.querySelector('.cover-preview-module'),
  1334. danmu = target.querySelector('.danmu-module');
  1335. if(!pv||!danmu) return;
  1336. if(ev.type=='mouseenter') {
  1337. target.timmer = setTimeout(()=>{
  1338. if(!target.timmer) return;
  1339. pv.classList.add('show');
  1340. danmu.classList.add('show');
  1341. if(!target.cover) {
  1342. fetch(`//api.bilibili.com/pvideo?aid=${target.dataset.id}&_=${Date.now()}`)
  1343. .then(res=>res.json())
  1344. .then(d=>(target.cover = d.data))
  1345. .then(()=>fetch(`//api.bilibili.com/x/v2/dm/ajax?aid=${target.dataset.id}&_=${Date.now()}`))
  1346. .then(res=>res.json())
  1347. .then(d=>{
  1348. danmu.data = d.data;
  1349. danmu.count = 0;
  1350. danmu.previewDanmu = function (){
  1351. danmu.style.opacity = 1;
  1352. if(danmu.count%danmu.data.length==0) {
  1353. danmu.count = 0;
  1354. danmu.innerHTML = danmu.data.map((item,i)=>`<p class="dm van-danmu-item ${i%2?'':'row2'}">${item}</p>`).join('');
  1355. }
  1356. const item = danmu.children[danmu.count++];
  1357. if(!item) return;
  1358. item.style = `left: -${item.offsetWidth}px; transition: left 5s linear 0s;`;
  1359. };
  1360. if(!target.timmer) return;
  1361. tools.previewImage(pv,target,ev.offsetX);
  1362. tools.previewDanmu(danmu, true);
  1363. delete target.timmer;
  1364. });
  1365. }
  1366. else {
  1367. tools.previewImage(pv,target,ev.offsetX);
  1368. tools.previewDanmu(danmu, true);
  1369. delete target.timmer;
  1370. }
  1371. },100);
  1372. }
  1373. else if(ev.type=='mouseleave') {
  1374. clearTimeout(target.timmer);
  1375. delete target.timmer;
  1376. pv.classList.remove('show');
  1377. if(pv.classList.contains('van-framepreview')) {
  1378. pv.style.opacity = 0;
  1379. }
  1380. danmu.classList.remove('show');
  1381. tools.previewDanmu(danmu, false);
  1382. }
  1383. else {
  1384. if(!target.cover) return;
  1385. tools.previewImage(pv,target,ev.offsetX);
  1386. }
  1387. }
  1388. };
  1389.  
  1390. //初始化
  1391. function init() {
  1392. if (document.querySelector('.international-home')) element.isNew = true;;
  1393. try{
  1394. setting.init();
  1395. InitRecommend();
  1396. window.addEventListener("beforeunload", ()=>setting.saveHistory());
  1397. InitRanking();
  1398. }catch(e){
  1399. console.error(e);
  1400. }
  1401. }
  1402. if (element.mainDiv){
  1403. init();
  1404. }
  1405. else {
  1406. if(document.head.querySelector('link[href*=home]')) {
  1407. setting.setStyle();
  1408. //console.log('observe');
  1409. new MutationObserver((mutations,observer)=>{
  1410. //console.log(mutations)
  1411. for(const mutation of mutations){
  1412. for (const node of mutation.addedNodes) {
  1413. if(node.id=='bili_douga') {
  1414. observer.disconnect();
  1415. element.mainDiv = node.cloneNode(true);
  1416. init();
  1417. return;
  1418. }
  1419. }
  1420. }
  1421. }).observe(document.body,{
  1422. childList: true,
  1423. subtree: true,
  1424. });
  1425. }
  1426. }
  1427. })();
  1428. (function() {
  1429. 'use strict';
  1430. GM_addStyle('.storey-title { margin:90px 0 0px ; }');
  1431. GM_addStyle('#recommend .card-list>.zone-list-box.storey-box { width:80% !important ; margin-left:70px ; }');
  1432. GM_addStyle('#recommend .storey-box, #ranking-all ul.rank-list{ height: calc(404px / 2 * 4 ) ; }');
  1433. GM_addStyle('#recommend .video-card-common { width:25% ; height:33% ; padding:0.13333vw 0.13333vw ; margin-bottom:0px !important ; }');
  1434. GM_addStyle('.video-card-common .card-pic { height:100% ; }');
  1435. GM_addStyle('.video-card-common .title { font-size:16px ; height:60px ; margin:4px 0px 0px ; padding-right:0px ; }');
  1436. GM_addStyle('.video-card-common .up { display:none }');
  1437. GM_addStyle('.video-card-common .card-pic a .count { font-size:14px ; }');
  1438. GM_addStyle('.btn.btn-change { font-size:14px ; width:100px !important; height:30px ; }');
  1439. })();

QingJ © 2025

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