bilibili网页端添加APP首页推荐

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

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