diff --git a/.vscode/settings.json b/.vscode/settings.json index 5d2e6e15b..61945c677 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "Swiper", "Violentmonkey", "Vuex", + "WASD", "Watchlater", "afterbegin", "afterend", diff --git a/min/custom-navbar.min.js b/min/custom-navbar.min.js index 94a2e5db0..59b999d7e 100644 --- a/min/custom-navbar.min.js +++ b/min/custom-navbar.min.js @@ -1 +1 @@ -(()=>(t,i)=>{const e=["//www.bilibili.com","//t.bilibili.com","//search.bilibili.com","//space.bilibili.com","//account.bilibili.com","//pay.bilibili.com","//member.bilibili.com","//big.bilibili.com","//message.bilibili.com","//app.bilibili.com","//passport.bilibili.com","//game.bilibili.com","//live.bilibili.com/blackboard/"];const a=["//t.bilibili.com/vote/h5/index/#/result","//t.bilibili.com/lottery/h5/index/#/result","//member.bilibili.com/video/upload","//space.bilibili.com/ajax/","//www.bilibili.com/h5/comment/","//member.bilibili.com/v2"];const n=()=>{document.documentElement.style.setProperty("--navbar-bounds-padding",`0 ${t.customNavbarBoundsPadding}%`);document.documentElement.style.setProperty("--navbar-blur-opacity",(t.customNavbarBlurOpacity||.7).toString());addSettingsListener("customNavbarBlurOpacity",(t=>{document.documentElement.style.setProperty("--navbar-blur-opacity",t)}));if(!document.URL.startsWith("https://space.bilibili.com")){addSettingsListener("customNavbarGlobalFixed",(t=>{document.body.classList.toggle("fixed-navbar",t)}),true)}};const s=(t,i,e)=>{e.classList.toggle(t,i)};const o=t=>{dq(".custom-navbar").classList.toggle("dark",t);dq(".custom-navbar-settings").classList.toggle("dark",t)};return(()=>{const c=document.URL.replace(location.search,"");const r=c==="https://www.bilibili.com/"||c==="https://www.bilibili.com/index.html";if(isIframe()||t.bilibiliSimpleNewHomeCompatible&&r||document.contentType!=="text/html"){i.removeStyle("customNavbarStyle");return}n();const l=!(!e.some((t=>document.URL.includes(t)))||a.some((t=>document.URL.includes(t))));if(l){document.body.classList.add("custom-navbar-loading");(async()=>{const e=await i.importAsync("customNavbarHtml");document.body.insertAdjacentHTML("beforeend",e);addSettingsListener("useDarkStyle",o,true);const a=()=>dq(".custom-navbar");["Fill","Shadow","Compact","Blur"].forEach((t=>{const i="customNavbar"+t;addSettingsListener(i,(i=>s(t.toLowerCase(),i,a())),true)}));SpinQuery.select("#banner_link,.international-header .bili-banner").then((i=>{if(i===null||!(i instanceof HTMLElement)){return}if(i.style.backgroundImage||dq(i,".animated-banner")){addSettingsListener("customNavbarTransparent",(i=>{if(!t.hideBanner){a().classList.toggle("transparent",i)}}),true);addSettingsListener("hideBanner",(i=>{if(t.customNavbarTransparent){a().classList.toggle("transparent",!i)}}))}}));SpinQuery.condition((()=>dq("#banner_link,.international-header .bili-banner")),(t=>t===null?false:Boolean(t.style.backgroundImage)),(t=>{Observer.attributes(t,(()=>{const i=dqa(".custom-navbar .blur-layer");i.forEach((i=>{i.style.backgroundImage=t.style.backgroundImage;i.setAttribute("data-image",t.style.backgroundImage)}))}))}));const{Blank:n}=await i.importAsync("custom-navbar-blank");const{Logo:c}=await i.importAsync("custom-navbar-logo");const{Category:r}=await i.importAsync("custom-navbar-category");const{SimpleLink:l}=await i.importAsync("custom-navbar-simple-link");const{UserInfo:m}=await i.importAsync("custom-navbar-user-info");const{SearchBox:b}=await i.importAsync("custom-navbar-search-box");const{Iframe:u}=await i.importAsync("custom-navbar-iframe");const d=[new n(1),new c,new r,new l("排行","https://www.bilibili.com/v/popular/rank/all","ranking"),new l("相簿","https://h.bilibili.com","drawing"),new l("音频","https://www.bilibili.com/audio/home/","music"),new u("游戏中心","https://game.bilibili.com/",{src:`https://www.bilibili.com/page-proxy/game-nav.html`,width:`680px`,height:`260px`,lazy:true,iframeName:"games"}),new u("直播","https://live.bilibili.com",{src:`https://live.bilibili.com/blackboard/dropdown-menu.html`,width:`528px`,height:`266px`,lazy:true,iframeName:"lives"}),new l("赛事","https://www.bilibili.com/v/game/match/","match"),new l("会员购","https://show.bilibili.com","shop"),new l("漫画","https://manga.bilibili.com","manga"),new n(2),new b,new m];if(getUID()){const{WatchlaterList:t}=await i.importAsync("custom-navbar-watchlater-list");const{Messages:e}=await i.importAsync("custom-navbar-messages");const{Activities:a}=await i.importAsync("custom-navbar-activities");const{Subscriptions:n}=await i.importAsync("custom-navbar-subscriptions");const{FavoritesList:s}=await i.importAsync("custom-navbar-favorites-list");const{HistoryList:o}=await i.importAsync("custom-navbar-history-list");d.push(new e,new n,new a,new t,new s,new o)}const{Upload:p}=await i.importAsync("custom-navbar-upload");const{DarkMode:w}=await i.importAsync("custom-navbar-dark-mode");d.push(new p,new n(3),new w);new Vue({el:".custom-navbar",data:{components:d},methods:{async requestPopup(t){if(!t.requestedPopup&&!t.disabled){this.$set(t,`requestedPopup`,true);if(t.initialPopup){t.initialPopup()}return}if(t.onPopup){t.onPopup()}}},mounted(){document.body.classList.remove("custom-navbar-loading");const t=[...d].sort(ascendingSort((t=>t.order)));const i=()=>{const i=()=>{let i=0;let e=true;let a=t.length-1;let n=true;while(i\n\n顶栏布局\n`,condition:()=>l,success:async()=>{const{initSettingsPanel:t}=await i.importAsync("custom-navbar-settings");await t()}},unload:()=>{const t=dqa(".custom-navbar,.custom-navbar-settings");t.forEach((t=>t.style.display="none"));i.removeStyle("customNavbarStyle")},reload:()=>{const t=dqa(".custom-navbar,.custom-navbar-settings");t.forEach((t=>t.style.display="flex"));i.applyImportantStyle("customNavbarStyle")}}})()})(); \ No newline at end of file +(()=>(t,i)=>{const e=["//www.bilibili.com","//t.bilibili.com","//search.bilibili.com","//space.bilibili.com","//account.bilibili.com","//pay.bilibili.com","//member.bilibili.com","//big.bilibili.com","//message.bilibili.com","//app.bilibili.com","//passport.bilibili.com","//game.bilibili.com","//live.bilibili.com/blackboard/"];const a=["//t.bilibili.com/vote/h5/index/#/result","//t.bilibili.com/lottery/h5/index/#/result","//member.bilibili.com/video/upload","//space.bilibili.com/ajax/","//www.bilibili.com/h5/comment/","//member.bilibili.com/v2"];const n=()=>{document.documentElement.style.setProperty("--navbar-bounds-padding",`0 ${t.customNavbarBoundsPadding}%`);document.documentElement.style.setProperty("--navbar-blur-opacity",(t.customNavbarBlurOpacity||.7).toString());addSettingsListener("customNavbarBlurOpacity",(t=>{document.documentElement.style.setProperty("--navbar-blur-opacity",t)}));if(!document.URL.startsWith("https://space.bilibili.com")){addSettingsListener("customNavbarGlobalFixed",(t=>{document.body.classList.toggle("fixed-navbar",t)}),true)}};const s=(t,i,e)=>{e.classList.toggle(t,i)};const o=t=>{dq(".custom-navbar").classList.toggle("dark",t);dq(".custom-navbar-settings").classList.toggle("dark",t)};return(()=>{const c=document.URL.replace(location.search,"");const r=c==="https://www.bilibili.com/"||c==="https://www.bilibili.com/index.html";if(isIframe()||t.bilibiliSimpleNewHomeCompatible&&r||document.contentType!=="text/html"){i.removeStyle("customNavbarStyle");return}n();const l=!(!e.some((t=>document.URL.includes(t)))||a.some((t=>document.URL.includes(t))));if(l){document.body.classList.add("custom-navbar-loading");(async()=>{const e=await i.importAsync((()=>"customNavbarHtml")());document.body.insertAdjacentHTML("beforeend",e);addSettingsListener("useDarkStyle",o,true);const a=()=>dq(".custom-navbar");["Fill","Shadow","Compact","Blur"].forEach((t=>{const i="customNavbar"+t;addSettingsListener(i,(i=>s(t.toLowerCase(),i,a())),true)}));SpinQuery.select("#banner_link,.international-header .bili-banner").then((i=>{if(i===null||!(i instanceof HTMLElement)){return}if(i.style.backgroundImage||dq(i,".animated-banner")){addSettingsListener("customNavbarTransparent",(i=>{if(!t.hideBanner){a().classList.toggle("transparent",i)}}),true);addSettingsListener("hideBanner",(i=>{if(t.customNavbarTransparent){a().classList.toggle("transparent",!i)}}))}}));SpinQuery.condition((()=>dq("#banner_link,.international-header .bili-banner")),(t=>t===null?false:Boolean(t.style.backgroundImage)),(t=>{Observer.attributes(t,(()=>{const i=dqa(".custom-navbar .blur-layer");i.forEach((i=>{i.style.backgroundImage=t.style.backgroundImage;i.setAttribute("data-image",t.style.backgroundImage)}))}))}));const{Blank:n}=await i.importAsync("custom-navbar-blank");const{Logo:c}=await i.importAsync("custom-navbar-logo");const{Category:r}=await i.importAsync("custom-navbar-category");const{SimpleLink:l}=await i.importAsync("custom-navbar-simple-link");const{UserInfo:m}=await i.importAsync("custom-navbar-user-info");const{SearchBox:b}=await i.importAsync("custom-navbar-search-box");const{Iframe:u}=await i.importAsync("custom-navbar-iframe");const d=[new n(1),new c,new r,new l("排行","https://www.bilibili.com/v/popular/rank/all","ranking"),new l("相簿","https://h.bilibili.com","drawing"),new l("音频","https://www.bilibili.com/audio/home/","music"),new u("游戏中心","https://game.bilibili.com/",{src:`https://www.bilibili.com/page-proxy/game-nav.html`,width:`680px`,height:`260px`,lazy:true,iframeName:"games"}),new u("直播","https://live.bilibili.com",{src:`https://live.bilibili.com/blackboard/dropdown-menu.html`,width:`528px`,height:`266px`,lazy:true,iframeName:"lives"}),new l("赛事","https://www.bilibili.com/v/game/match/","match"),new l("会员购","https://show.bilibili.com","shop"),new l("漫画","https://manga.bilibili.com","manga"),new n(2),new b,new m];if(getUID()){const{WatchlaterList:t}=await i.importAsync("custom-navbar-watchlater-list");const{Messages:e}=await i.importAsync("custom-navbar-messages");const{Activities:a}=await i.importAsync("custom-navbar-activities");const{Subscriptions:n}=await i.importAsync("custom-navbar-subscriptions");const{FavoritesList:s}=await i.importAsync("custom-navbar-favorites-list");const{HistoryList:o}=await i.importAsync("custom-navbar-history-list");d.push(new e,new n,new a,new t,new s,new o)}const{Upload:p}=await i.importAsync("custom-navbar-upload");const{DarkMode:w}=await i.importAsync("custom-navbar-dark-mode");d.push(new p,new n(3),new w);new Vue({el:".custom-navbar",data:{components:d},methods:{async requestPopup(t){if(!t.requestedPopup&&!t.disabled){this.$set(t,`requestedPopup`,true);if(t.initialPopup){t.initialPopup()}return}if(t.onPopup){t.onPopup()}}},mounted(){document.body.classList.remove("custom-navbar-loading");const t=[...d].sort(ascendingSort((t=>t.order)));const i=()=>{const i=()=>{let i=0;let e=true;let a=t.length-1;let n=true;while(i\n\n顶栏布局\n`,condition:()=>l,success:async()=>{const{initSettingsPanel:t}=await i.importAsync("custom-navbar-settings");await t()}},unload:()=>{const t=dqa(".custom-navbar,.custom-navbar-settings");t.forEach((t=>t.style.display="none"));i.removeStyle("customNavbarStyle")},reload:()=>{const t=dqa(".custom-navbar,.custom-navbar-settings");t.forEach((t=>t.style.display="flex"));i.applyImportantStyle("customNavbarStyle")}}})()})(); \ No newline at end of file diff --git a/min/key-bindings.min.js b/min/key-bindings.min.js new file mode 100644 index 000000000..c0f7568a5 --- /dev/null +++ b/min/key-bindings.min.js @@ -0,0 +1 @@ +(()=>(e,t)=>{const n=["shift","alt","ctrl","meta"];const i=_.once((e=>{const t=document.URL.startsWith("https://www.bilibili.com/watchlater/");const i=document.URL.startsWith("https://www.bilibili.com/medialist/play/");const o={enable:true};document.body.addEventListener("keydown",(s=>{if(!o.enable){return}if(t&&document.URL.endsWith("list")){return}if(document.activeElement&&isTyping()){return}const r=s.key.toLowerCase();const l=dq(".bilibili-player-sphere-control");if(l!==null&&l.style.display!=="none"&&["w","a","s","d"].includes(r)){return}e.forEach((e=>{var o;const r=n.some((t=>{const n=e.keys.includes(t);const i=s[t+"Key"];return n!==i}));if(r){return}const l=e.keys.filter((e=>!n.includes(e.toLowerCase()))).map((e=>e.toLowerCase()));const c=l.includes(s.key.toLowerCase())||l.includes(s.code.toLowerCase());if(!c){return}if((o=e===null||e===void 0?void 0:e.prevent)!==null&&o!==void 0?o:true){s.stopPropagation();s.preventDefault()}e.action({binding:e,isWatchlater:t,isMediaList:i,event:s})}))}));return o}));return{export:{loadKeyBindings:i}}})(); \ No newline at end of file diff --git a/min/keymap.min.js b/min/keymap.min.js index e40b829a7..584007379 100644 --- a/min/keymap.min.js +++ b/min/keymap.min.js @@ -1 +1 @@ -(()=>(e,i)=>{const t=["https://www.bilibili.com/bangumi/","https://www.bilibili.com/video/","https://www.bilibili.com/watchlater/","https://www.bilibili.com/medialist/play/"];let n=true;(async()=>{const{VideoSpeedController:a}=await i.importAsync("default-video-speed");if(t.some((e=>document.URL.startsWith(e)))){const t={f:".bilibili-player-video-fullscreen",w:".bilibili-player-video-web-fullscreen",t:".bilibili-player-video-btn-widescreen",m:".bilibili-player-video-btn-volume .bilibili-player-iconfont-volume",p:".bilibili-player-video-btn-pip",c:".video-toolbar .coin,.tool-bar .coin-info, .video-toolbar-module .coin-box, .play-options-ul > li:nth-child(2)",s:".video-toolbar .collect, .video-toolbar-module .fav-box, .play-options-ul > li:nth-child(3)"," ":".bilibili-player-video-btn-start"};let o=true;let l=false;const s=document.URL.startsWith("https://www.bilibili.com/watchlater/");const c=document.URL.startsWith("https://www.bilibili.com/medialist/play/");if(s){Observer.videoChange((()=>{Ajax.getJsonWithCredentials(`https://api.bilibili.com/x/web-interface/archive/has/like?aid=${unsafeWindow.aid}`).then((e=>{l=Boolean(e.data)}))}))}let r;const p=e=>{let t=dq(".keymap-playback-tip");if(!t){const e=dq(".bilibili-player-video-wrap");if(!e){return}e.insertAdjacentHTML("afterbegin",`\n
\n\n
x\n
\n`);i.applyStyleFromText(`\n .keymap-playback-tip-container {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n padding: 8px 16px;\n background-color: #000A;\n color: white;\n pointer-events: none;\n opacity: 0;\n z-index: 100;\n display: flex;\n align-items: center;\n font-size: 14pt;\n border-radius: 4px;\n transition: .2s ease-out;\n }\n .keymap-playback-tip-container.show {\n opacity: 1;\n }\n .keymap-playback-tip-container i {\n line-height: 1;\n margin-right: 8px;\n font-size: 18pt;\n }\n`,"keymapStyle");t=dq(".keymap-playback-tip")}t.innerHTML=e.toString();if(r){clearTimeout(r)}dq(".keymap-playback-tip-container").classList.add("show");r=window.setTimeout((()=>{dq(".keymap-playback-tip-container").classList.remove("show")}),2e3)};document.body.addEventListener("keydown",(async i=>{if(!n){return}if(s&&document.URL.endsWith("list")){return}if(document.activeElement&&isTyping()){return}const r=i.key.toLowerCase();const d=dq(".bilibili-player-sphere-control");if(d!==null&&d.style.display!=="none"&&["w","a","s","d"].includes(r)){return}const b=!i.shiftKey&&!i.altKey&&!i.ctrlKey&&!i.metaKey;if(r in t&&b){const e=dq(t[r]);if(!e){return}i.stopPropagation();i.preventDefault();e.click()}else if(r==="d"&&b){const e=dq(".bilibili-player-video-danmaku-switch input");if(!e){return}i.stopPropagation();i.preventDefault();e.checked=!e.checked;raiseEvent(e,"change")}else if(r==="j"&&b){const t=dq(".bilibili-player-video video");t.currentTime+=e.keymapJumpSeconds;i.stopPropagation();i.preventDefault()}else if(r==="l"&&b){if(s){const e={aid:unsafeWindow.aid,like:l?2:1,csrf:getCsrf()};Ajax.postTextWithCredentials(`https://api.bilibili.com/x/web-interface/archive/like`,Object.entries(e).map((([e,i])=>`${e}=${i}`)).join("&")).then((()=>{l=!l;if(l){Toast.success(`已点赞`,`快捷键扩展`,1e3)}else{Toast.success(`已取消点赞`,`快捷键扩展`,1e3)}}))}else if(c){const e=dq(".play-options-ul > li:first-child");if(e){e.click()}}else{const e=dq(".video-toolbar .like");i.preventDefault();const t=(i,t)=>{const n=new CustomEvent(i,t);e.dispatchEvent(n)};o=true;setTimeout((()=>o=false),200);t("mousedown",i);document.body.addEventListener("keyup",(e=>{e.preventDefault();t("mouseup",e);if(o){t("click",e)}}),{once:true})}}else if(r==="`"&&b){const e=dq(".bilibili-player-video-wrap");const i=e.getBoundingClientRect();e.dispatchEvent(new MouseEvent("contextmenu",{bubbles:true,cancelable:false,view:unsafeWindow,button:2,buttons:0,clientX:i.x+i.width/2-176.65/2,clientY:i.y+i.height/2-194/2}))}else if(i.shiftKey){const t=await SpinQuery.select(`.${a.classNameMap.speedContainer}`);const n=await SpinQuery.select(`.${a.classNameMap.video} video`);const o=new a(t,n,1);const l=a.supportedRates;let s=true;if(r===">"||r==="ArrowUp".toLowerCase()){o.setVideoSpeed(l.find((e=>e>o.playbackRate))||l[l.length-1]);p(o.playbackRate)}else if(r==="<"||r==="ArrowDown".toLowerCase()){o.setVideoSpeed([...l].reverse().find((e=>en=true,unload:()=>n=false}})(); \ No newline at end of file +(()=>(e,i)=>{const t=["https://www.bilibili.com/bangumi/","https://www.bilibili.com/video/","https://www.bilibili.com/watchlater/","https://www.bilibili.com/medialist/play/"];let n;if(t.some((e=>document.URL.startsWith(e)))){const t=e=>()=>{if(typeof e==="string"){var i;(i=dq(e))===null||i===void 0?void 0:i.click()}else{e.click()}};let a;const o=e=>{let t=dq(".keymap-playback-tip");if(!t){const e=dq(".bilibili-player-video-wrap");if(!e){return}e.insertAdjacentHTML("afterbegin",`\n
\n\n
x\n
\n`);i.applyStyleFromText(`\n .keymap-playback-tip-container {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n padding: 8px 16px;\n background-color: #000A;\n color: white;\n pointer-events: none;\n opacity: 0;\n z-index: 100;\n display: flex;\n align-items: center;\n font-size: 14pt;\n border-radius: 4px;\n transition: .2s ease-out;\n }\n .keymap-playback-tip-container.show {\n opacity: 1;\n }\n .keymap-playback-tip-container i {\n line-height: 1;\n margin-right: 8px;\n font-size: 18pt;\n }\n`,"keymapStyle");t=dq(".keymap-playback-tip")}t.innerHTML=e.toString();if(a){clearTimeout(a)}dq(".keymap-playback-tip-container").classList.add("show");a=window.setTimeout((()=>{dq(".keymap-playback-tip-container").classList.remove("show")}),2e3)};const l=e=>async()=>{const t=await i.importAsync("default-video-speed");console.log(t);const{VideoSpeedController:n}=await i.importAsync("default-video-speed");const a=dq(`.${n.classNameMap.speedContainer}`);const l=dq(`.${n.classNameMap.video} video`);if(!a||!l){return}const s=new n(a,l,1);e(s,n.supportedRates);o(s.playbackRate)};const s={fullscreen:t(".bilibili-player-video-btn-fullscreen"),webFullscreen:t(".bilibili-player-video-web-fullscreen"),wideScreen:t(".bilibili-player-video-btn-widescreen"),mute:t(".bilibili-player-video-btn-volume .bilibili-player-iconfont-volume"),pictureInPicture:t(".bilibili-player-video-btn-pip"),coin:t(".video-toolbar .coin,.tool-bar .coin-info, .video-toolbar-module .coin-box, .play-options-ul > li:nth-child(2)"),favorite:t(".video-toolbar .collect, .video-toolbar-module .fav-box, .play-options-ul > li:nth-child(3)"),pause:t(".bilibili-player-video-btn-start"),like:(()=>{let e=true;let i=false;const t=_.once((()=>{Observer.videoChange((()=>{Ajax.getJsonWithCredentials(`https://api.bilibili.com/x/web-interface/archive/has/like?aid=${unsafeWindow.aid}`).then((e=>{i=Boolean(e.data)}))}))}));return({isWatchlater:n,isMediaList:a,event:o})=>{if(n){t();const e={aid:unsafeWindow.aid,like:i?2:1,csrf:getCsrf()};Ajax.postTextWithCredentials(`https://api.bilibili.com/x/web-interface/archive/like`,Object.entries(e).map((([e,i])=>`${e}=${i}`)).join("&")).then((()=>{i=!i;if(i){Toast.success(`已点赞`,`快捷键扩展`,1e3)}else{Toast.success(`已取消点赞`,`快捷键扩展`,1e3)}}))}else if(a){const e=dq(".play-options-ul > li:first-child");if(e){e.click()}}else{const i=dq(".video-toolbar .like");o.preventDefault();const t=(e,t)=>{const n=new CustomEvent(e,t);i.dispatchEvent(n)};e=true;setTimeout((()=>e=false),200);t("mousedown",o);document.body.addEventListener("keyup",(i=>{i.preventDefault();t("mouseup",i);if(e){t("click",i)}}),{once:true})}}})(),danmaku:()=>{const e=dq(".bilibili-player-video-danmaku-switch input");if(!e){return}e.checked=!e.checked;raiseEvent(e,"change")},longJumpBackward:()=>{const i=dq(".bilibili-player-video video");i.currentTime-=e.keymapJumpSeconds},longJumpForward:()=>{const i=dq(".bilibili-player-video video");i.currentTime+=e.keymapJumpSeconds},playerMenu:()=>{const e=dq(".bilibili-player-video-wrap");if(!e){return}const i=e.getBoundingClientRect();e.dispatchEvent(new MouseEvent("contextmenu",{bubbles:true,cancelable:false,view:unsafeWindow,button:2,buttons:0,clientX:i.x+i.width/2-176.65/2,clientY:i.y+i.height/2-194/2}))},watchlater:t(".video-toolbar .ops .watchlater, .more-ops-list .ops-watch-later, .video-toolbar-module .see-later-box"),quickFavorite:t(".quick-favorite"),videoSpeedIncrease:l(((e,i)=>{e.setVideoSpeed(i.find((i=>i>e.playbackRate))||i[i.length-1])})),videoSpeedDecrease:l(((e,i)=>{e.setVideoSpeed([...i].reverse().find((i=>i{e.reset()}))};const c={fullscreen:"f",webFullscreen:"w",wideScreen:"t",mute:"m",pictureInPicture:"p",coin:"c",favorite:"s",pause:"space",like:"l",playerMenu:"`",longJumpForward:"j",longJumpBackward:"shift j",watchlater:"shift w",quickFavorite:"shift s",danmaku:"d",videoSpeedIncrease:"shift > ArrowUp",videoSpeedDecrease:"shift < ArrowDown",videoSpeedReset:"shift ?"};const r=e=>Object.entries(e).map((([e,i])=>{const t=i.split(" ");return{keys:t,action:s[e]||(()=>{})}}));(async()=>{const{loadKeyBindings:t}=await i.importAsync("key-bindings");n=t(r({...c,...e.customKeyBindings}))})()}return{reload:()=>n&&(n.enable=true),unload:()=>n&&(n.enable=false)}})(); \ No newline at end of file diff --git a/src/client/bilibili-evolved.js b/src/client/bilibili-evolved.js index 97a399ac3..041307a55 100644 --- a/src/client/bilibili-evolved.js +++ b/src/client/bilibili-evolved.js @@ -1,6 +1,6 @@ // ==UserScript== // @name Bilibili Evolved (Preview) -// @version 1.11.8 +// @version 1.11.9 // @description Bilibili Evolved 的预览版, 可以抢先体验新功能. // @author Grant Howard, Coulomb-G // @copyright 2020, Grant Howard (https://github.com/the1812) & Coulomb-G (https://github.com/Coulomb-G) diff --git a/src/client/settings.js b/src/client/settings.js index 79f605797..f31c021ec 100644 --- a/src/client/settings.js +++ b/src/client/settings.js @@ -250,6 +250,7 @@ export const settings = { rememberVideoSpeedList: {}, rememberVideoSpeed: false, extendVideoSpeed: true, + customKeyBindings: {}, cache: {}, } const fixedSettings = { diff --git a/src/global.d.ts b/src/global.d.ts index 4691e417d..64437908e 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -525,6 +525,7 @@ declare global { }, rememberVideoSpeed: boolean, extendVideoSpeed: boolean, + customKeyBindings: { [actionName: string]: string } } const GM_info: MonkeyInfo function GM_xmlhttpRequest(details: MonkeyXhrDetails): { abort: () => void } diff --git a/src/video/keymap.ts b/src/video/keymap.ts deleted file mode 100644 index 7c4335918..000000000 --- a/src/video/keymap.ts +++ /dev/null @@ -1,232 +0,0 @@ -const supportedUrls = [ - 'https://www.bilibili.com/bangumi/', - 'https://www.bilibili.com/video/', - 'https://www.bilibili.com/watchlater/', - 'https://www.bilibili.com/medialist/play/', -] -let enabled = true; - -(async () => { - const { VideoSpeedController } = await import("../video/default-video-speed") - - if (supportedUrls.some(url => document.URL.startsWith(url))) { - const clickableKeymap = { - f: '.bilibili-player-video-fullscreen', // 全屏 - w: '.bilibili-player-video-web-fullscreen', // 网页全屏 - t: '.bilibili-player-video-btn-widescreen', // 宽屏 - // r: '.bilibili-player-video-btn-repeat', // 切换循环模式 - m: '.bilibili-player-video-btn-volume .bilibili-player-iconfont-volume', // 切换静音 - p: '.bilibili-player-video-btn-pip', // 画中画 - // l: '.video-toolbar .like', // 点赞 - c: '.video-toolbar .coin,.tool-bar .coin-info, .video-toolbar-module .coin-box, .play-options-ul > li:nth-child(2)', // 投币 - s: '.video-toolbar .collect, .video-toolbar-module .fav-box, .play-options-ul > li:nth-child(3)', // 收藏 - ' ': '.bilibili-player-video-btn-start', // 砸瓦撸多 - } as { [key: string]: string } - /** 长按`L`三连使用的记忆变量 */ - let likeClick = true - /** 在稍后再看页面里, 记录当前视频是否赞过 */ - let liked = false - const isWatchlater = document.URL.startsWith('https://www.bilibili.com/watchlater/') - const isMediaList = document.URL.startsWith('https://www.bilibili.com/medialist/play/') - if (isWatchlater) { - Observer.videoChange(() => { - Ajax.getJsonWithCredentials(`https://api.bilibili.com/x/web-interface/archive/has/like?aid=${unsafeWindow.aid}`).then(json => { - liked = Boolean(json.data) - }) - }) - } - /** 播放速度提示框用的`setTimeout`句柄 */ - let showPlaybackTipOldTimeout: number - /** - * 显示播放速度提示框 - * @param speed 播放速度 - */ - const showPlaybackTip = (speed: number) => { - let tip = dq('.keymap-playback-tip') as HTMLDivElement - if (!tip) { - const player = dq('.bilibili-player-video-wrap') - if (!player) { - return - } - player.insertAdjacentHTML('afterbegin', /*html*/` -
- -
x -
- `) - resources.applyStyleFromText(` - .keymap-playback-tip-container { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - padding: 8px 16px; - background-color: #000A; - color: white; - pointer-events: none; - opacity: 0; - z-index: 100; - display: flex; - align-items: center; - font-size: 14pt; - border-radius: 4px; - transition: .2s ease-out; - } - .keymap-playback-tip-container.show { - opacity: 1; - } - .keymap-playback-tip-container i { - line-height: 1; - margin-right: 8px; - font-size: 18pt; - } - `, 'keymapStyle') - tip = dq('.keymap-playback-tip') as HTMLDivElement - } - tip.innerHTML = speed.toString() - if (showPlaybackTipOldTimeout) { - clearTimeout(showPlaybackTipOldTimeout) - } - (dq('.keymap-playback-tip-container') as HTMLDivElement).classList.add('show') - showPlaybackTipOldTimeout = window.setTimeout(() => { - (dq('.keymap-playback-tip-container') as HTMLDivElement).classList.remove('show') - }, 2000) - } - document.body.addEventListener('keydown', async e => { - if (!enabled) { - return - } - if (isWatchlater && document.URL.endsWith('list')) { - return - } - if (document.activeElement && isTyping()) { - return - } - const key = e.key.toLowerCase() - const panoramaControl = dq('.bilibili-player-sphere-control') as HTMLElement - if (panoramaControl !== null && panoramaControl.style.display !== 'none' && ['w', 'a', 's', 'd'].includes(key)) { - return - } - const noModifyKeys = !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey - if (key in clickableKeymap && noModifyKeys) { - const element = dq(clickableKeymap[key]) as HTMLElement - if (!element) { - return - } - e.stopPropagation() - e.preventDefault() - element.click() - } else if (key === 'd' && noModifyKeys) { // 切换弹幕开关 - const checkbox = dq('.bilibili-player-video-danmaku-switch input') as HTMLInputElement - if (!checkbox) { - return - } - e.stopPropagation() - e.preventDefault() - checkbox.checked = !checkbox.checked - raiseEvent(checkbox, 'change') - } else if (key === 'j' && noModifyKeys) { // 绯红之王 - const video = dq('.bilibili-player-video video') as HTMLVideoElement - video.currentTime += settings.keymapJumpSeconds - e.stopPropagation() - e.preventDefault() - } else if (key === 'l' && noModifyKeys) { - if (isWatchlater) { - const formData = { - aid: unsafeWindow.aid, - /** `1`点赞; `2`取消赞 */ - like: liked ? 2 : 1, - csrf: getCsrf(), - } - Ajax.postTextWithCredentials(`https://api.bilibili.com/x/web-interface/archive/like`, Object.entries(formData).map(([k, v]) => `${k}=${v}`).join('&')).then(() => { - liked = !liked - if (liked) { - Toast.success(`已点赞`, `快捷键扩展`, 1000) - } else { - Toast.success(`已取消点赞`, `快捷键扩展`, 1000) - } - }) - } else if (isMediaList) { - const likeButton = dq('.play-options-ul > li:first-child') as HTMLLIElement - if (likeButton) { - likeButton.click() - } - } else { - const likeButton = dq('.video-toolbar .like') as HTMLSpanElement - e.preventDefault() - const fireEvent = (name: string, args: Event) => { - const event = new CustomEvent(name, args) - likeButton.dispatchEvent(event) - } - likeClick = true - setTimeout(() => likeClick = false, 200) - fireEvent('mousedown', e) - document.body.addEventListener('keyup', e => { - e.preventDefault() - fireEvent('mouseup', e) - if (likeClick) { - fireEvent('click', e) - } - }, { once: true }) - } - } else if (key === '`' && noModifyKeys) { - // menu size: 386.6 x 311 (2020-03-29) - // menu size: 176.65 x 194 (2020-06-09) - const player = dq('.bilibili-player-video-wrap') as HTMLElement - const rect = player.getBoundingClientRect() - player.dispatchEvent(new MouseEvent('contextmenu', { - bubbles: true, - cancelable: false, - view: unsafeWindow, - button: 2, - buttons: 0, - clientX: rect.x + rect.width / 2 - 176.65 / 2, - clientY: rect.y + rect.height / 2 - 194 / 2 - })) - } else if (e.shiftKey) { - const containerElement = await SpinQuery.select(`.${VideoSpeedController.classNameMap.speedContainer}`) as HTMLElement - const videoElement = await SpinQuery.select(`.${VideoSpeedController.classNameMap.video} video`) as HTMLVideoElement - - const controller = new VideoSpeedController(containerElement, videoElement, 1) - - const playbackRates = VideoSpeedController.supportedRates - - let preventDefault = true - if (key === '>' || key === 'ArrowUp'.toLowerCase()) { // 天堂制造 - controller.setVideoSpeed(playbackRates.find(it => it > controller.playbackRate) || playbackRates[playbackRates.length - 1]) - showPlaybackTip(controller.playbackRate) - } else if (key === '<' || key === 'ArrowDown'.toLowerCase()) { // 时间减速 - controller.setVideoSpeed([...playbackRates].reverse().find(it => it < controller.playbackRate) || playbackRates[0]) - showPlaybackTip(controller.playbackRate) - } else if (key === '?') { // 重置速度 - controller.reset() - showPlaybackTip(controller.playbackRate) - } else if (key === 'w') { // 稍后再看 - const watchlater = dq('.video-toolbar .ops .watchlater, .more-ops-list .ops-watch-later, .video-toolbar-module .see-later-box') as HTMLElement - if (watchlater !== null) { - watchlater.click() - } - } else if (key === 'j') { // 败者食尘 - videoElement.currentTime -= settings.keymapJumpSeconds - } else if (key === 's') { // 快速收藏 - const quickFavorite = dq('.quick-favorite') as HTMLElement - if (quickFavorite) { - quickFavorite.click() - } - } else { - preventDefault = false - } - - if (preventDefault) { - e.stopPropagation() - e.preventDefault() - } - } - }) - } -})() - -export default { - reload: () => enabled = true, - unload: () => enabled = false, -} \ No newline at end of file diff --git a/src/video/keymap/key-bindings.ts b/src/video/keymap/key-bindings.ts new file mode 100644 index 000000000..488a824c4 --- /dev/null +++ b/src/video/keymap/key-bindings.ts @@ -0,0 +1,72 @@ +export type KeyBindingAction = (context: { + binding: KeyBinding + event: KeyboardEvent + isWatchlater: boolean + isMediaList: boolean +}) => void +export interface KeyBinding { + keys: string[] + action: KeyBindingAction + prevent?: boolean +} +const modifyKeys = [ + 'shift', + 'alt', + 'ctrl', + 'meta', +] +export const loadKeyBindings = _.once((bindings: KeyBinding[]) => { + const isWatchlater = document.URL.startsWith('https://www.bilibili.com/watchlater/') + const isMediaList = document.URL.startsWith('https://www.bilibili.com/medialist/play/') + const config = { + enable: true + } + document.body.addEventListener('keydown', (e: KeyboardEvent & { [key: string]: boolean }) => { + if (!config.enable) { + return + } + // 稍后再看页面无视快捷键 + if (isWatchlater && document.URL.endsWith('list')) { + return + } + // 打字时无视快捷键 + if (document.activeElement && isTyping()) { + return + } + const key = e.key.toLowerCase() + + // 全景视频禁用 WASD 无视快捷键 + const panoramaControl = dq('.bilibili-player-sphere-control') as HTMLElement + if (panoramaControl !== null && panoramaControl.style.display !== 'none' && ['w', 'a', 's', 'd'].includes(key)) { + return + } + + bindings.forEach(binding => { + const modifyKeyNotMatch = modifyKeys.some(m => { + const needModifyKey = binding.keys.includes(m) + const isModifyKeyPressed = e[m + 'Key'] + return needModifyKey !== isModifyKeyPressed + }) + if (modifyKeyNotMatch) { + return + } + const restKeys = binding.keys.filter(k => !modifyKeys.includes(k.toLowerCase())).map(k => k.toLowerCase()) + const keyMatch = restKeys.includes(e.key.toLowerCase()) || restKeys.includes(e.code.toLowerCase()) + if (!keyMatch) { + return + } + + if (binding?.prevent ?? true) { + e.stopPropagation() + e.preventDefault() + } + binding.action({ + binding, + isWatchlater, + isMediaList, + event: e, + }) + }) + }) + return config +}) diff --git a/src/video/keymap/keymap.ts b/src/video/keymap/keymap.ts new file mode 100644 index 000000000..cc2d07113 --- /dev/null +++ b/src/video/keymap/keymap.ts @@ -0,0 +1,245 @@ +import { VideoSpeedController } from '../default-video-speed' +import { KeyBinding, KeyBindingAction } from './key-bindings' + +const supportedUrls = [ + 'https://www.bilibili.com/bangumi/', + 'https://www.bilibili.com/video/', + 'https://www.bilibili.com/watchlater/', + 'https://www.bilibili.com/medialist/play/', +] + +let config: { enable: boolean } +if (supportedUrls.some(url => document.URL.startsWith(url))) { + const clickElement = (target: string | HTMLElement) => { + return () => { + if (typeof target === 'string') { + (dq(target) as HTMLElement)?.click() + } else { + target.click() + } + } + } + /** 播放速度提示框用的`setTimeout`句柄 */ + let showPlaybackTipOldTimeout: number + /** + * 显示播放速度提示框 + * @param speed 播放速度 + */ + const showPlaybackTip = (speed: number) => { + let tip = dq('.keymap-playback-tip') as HTMLDivElement + if (!tip) { + const player = dq('.bilibili-player-video-wrap') + if (!player) { + return + } + player.insertAdjacentHTML('afterbegin', /*html*/` +
+ +
x +
+ `) + resources.applyStyleFromText(` + .keymap-playback-tip-container { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + padding: 8px 16px; + background-color: #000A; + color: white; + pointer-events: none; + opacity: 0; + z-index: 100; + display: flex; + align-items: center; + font-size: 14pt; + border-radius: 4px; + transition: .2s ease-out; + } + .keymap-playback-tip-container.show { + opacity: 1; + } + .keymap-playback-tip-container i { + line-height: 1; + margin-right: 8px; + font-size: 18pt; + } + `, 'keymapStyle') + tip = dq('.keymap-playback-tip') as HTMLDivElement + } + tip.innerHTML = speed.toString() + if (showPlaybackTipOldTimeout) { + clearTimeout(showPlaybackTipOldTimeout) + } + (dq('.keymap-playback-tip-container') as HTMLDivElement).classList.add('show') + showPlaybackTipOldTimeout = window.setTimeout(() => { + (dq('.keymap-playback-tip-container') as HTMLDivElement).classList.remove('show') + }, 2000) + } + const videoSpeed = (controllerAction: (controller: VideoSpeedController, rates: number[]) => void) => { + return async () => { + const attr = await import('../default-video-speed') + console.log(attr) + const { VideoSpeedController } = await import('../default-video-speed') + const containerElement = dq(`.${VideoSpeedController.classNameMap.speedContainer}`) as HTMLElement + const videoElement = dq(`.${VideoSpeedController.classNameMap.video} video`) as HTMLVideoElement + if (!containerElement || !videoElement) { + return + } + const controller = new VideoSpeedController(containerElement, videoElement, 1) + controllerAction(controller, VideoSpeedController.supportedRates) + showPlaybackTip(controller.playbackRate) + } + } + const actions = { + fullscreen: clickElement('.bilibili-player-video-btn-fullscreen'), + webFullscreen: clickElement('.bilibili-player-video-web-fullscreen'), + wideScreen: clickElement('.bilibili-player-video-btn-widescreen'), + mute: clickElement('.bilibili-player-video-btn-volume .bilibili-player-iconfont-volume'), + pictureInPicture: clickElement('.bilibili-player-video-btn-pip'), + coin: clickElement('.video-toolbar .coin,.tool-bar .coin-info, .video-toolbar-module .coin-box, .play-options-ul > li:nth-child(2)'), + favorite: clickElement('.video-toolbar .collect, .video-toolbar-module .fav-box, .play-options-ul > li:nth-child(3)'), + pause: clickElement('.bilibili-player-video-btn-start'), + like: (() => { + /** 长按`L`三连使用的记忆变量 */ + let likeClick = true + /** 在稍后再看页面里, 记录当前视频是否赞过 */ + let liked = false + + const listenWatchlaterVideoChange = _.once(() => { + Observer.videoChange(() => { + Ajax.getJsonWithCredentials(`https://api.bilibili.com/x/web-interface/archive/has/like?aid=${unsafeWindow.aid}`).then(json => { + liked = Boolean(json.data) + }) + }) + }) + return (({ isWatchlater, isMediaList, event }) => { + if (isWatchlater) { + listenWatchlaterVideoChange() + const formData = { + aid: unsafeWindow.aid, + /** `1`点赞; `2`取消赞 */ + like: liked ? 2 : 1, + csrf: getCsrf(), + } + Ajax.postTextWithCredentials(`https://api.bilibili.com/x/web-interface/archive/like`, Object.entries(formData).map(([k, v]) => `${k}=${v}`).join('&')).then(() => { + liked = !liked + if (liked) { + Toast.success(`已点赞`, `快捷键扩展`, 1000) + } else { + Toast.success(`已取消点赞`, `快捷键扩展`, 1000) + } + }) + } else if (isMediaList) { + const likeButton = dq('.play-options-ul > li:first-child') as HTMLLIElement + if (likeButton) { + likeButton.click() + } + } else { + const likeButton = dq('.video-toolbar .like') as HTMLSpanElement + event.preventDefault() + const fireEvent = (name: string, args: Event) => { + const event = new CustomEvent(name, args) + likeButton.dispatchEvent(event) + } + likeClick = true + setTimeout(() => likeClick = false, 200) + fireEvent('mousedown', event) + document.body.addEventListener('keyup', e => { + e.preventDefault() + fireEvent('mouseup', e) + if (likeClick) { + fireEvent('click', e) + } + }, { once: true }) + } + }) as KeyBindingAction + })(), + danmaku: () => { + const checkbox = dq('.bilibili-player-video-danmaku-switch input') as HTMLInputElement + if (!checkbox) { + return + } + checkbox.checked = !checkbox.checked + raiseEvent(checkbox, 'change') + }, + longJumpBackward: () => { + const video = dq('.bilibili-player-video video') as HTMLVideoElement + video.currentTime -= settings.keymapJumpSeconds + }, + longJumpForward: () => { + const video = dq('.bilibili-player-video video') as HTMLVideoElement + video.currentTime += settings.keymapJumpSeconds + }, + playerMenu: () => { + // menu size: 386.6 x 311 (2020-03-29) + // menu size: 176.65 x 194 (2020-06-09) + const player = dq('.bilibili-player-video-wrap') as HTMLElement + if (!player) { + return + } + const rect = player.getBoundingClientRect() + player.dispatchEvent(new MouseEvent('contextmenu', { + bubbles: true, + cancelable: false, + view: unsafeWindow, + button: 2, + buttons: 0, + clientX: rect.x + rect.width / 2 - 176.65 / 2, + clientY: rect.y + rect.height / 2 - 194 / 2 + })) + }, + watchlater: clickElement('.video-toolbar .ops .watchlater, .more-ops-list .ops-watch-later, .video-toolbar-module .see-later-box'), + quickFavorite: clickElement('.quick-favorite'), + videoSpeedIncrease: videoSpeed((controller, rates) => { + controller.setVideoSpeed(rates.find(it => it > controller.playbackRate) || rates[rates.length - 1]) + }), + videoSpeedDecrease: videoSpeed((controller, rates) => { + controller.setVideoSpeed([...rates].reverse().find(it => it < controller.playbackRate) || rates[0]) + }), + videoSpeedReset: videoSpeed((controller, rates) => { + controller.reset() + }), + } + const defaultBindings: { [action in keyof typeof actions]: string } = { + fullscreen: 'f', + webFullscreen: 'w', + wideScreen: 't', + mute: 'm', + pictureInPicture: 'p', + coin: 'c', + favorite: 's', + pause: 'space', + like: 'l', + playerMenu: '`', + longJumpForward: 'j', + longJumpBackward: 'shift j', + watchlater: 'shift w', + quickFavorite: 'shift s', + danmaku: 'd', + videoSpeedIncrease: 'shift > ArrowUp', + videoSpeedDecrease: 'shift < ArrowDown', + videoSpeedReset: 'shift ?', + } + const parseBindings = (bindings: { [action: string]: string }) => { + return Object.entries(bindings).map(([actionName, keyString]) => { + const keys = keyString.split(' ') + return { + keys, + action: (actions as any)[actionName] || (() => {}), + } as KeyBinding + }) + } + + ;(async () => { + const { loadKeyBindings } = await import('./key-bindings') + config = loadKeyBindings(parseBindings( + { ...defaultBindings, ...settings.customKeyBindings } + )) + })() +} + +export default { + reload: () => config && (config.enable = true), + unload: () => config && (config.enable = false), +} diff --git a/version.txt b/version.txt index cd97a5e32..c8cc62303 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.11.8 \ No newline at end of file +1.11.9 \ No newline at end of file