手机浏览器触摸手势

为手机浏览器添加触摸手势,即装即用,无需配置。除了通用手势外,还有针对文字、图片、视频的特殊手势。还嫌不够?支持添加属于你的个性化手势。推荐使用狐猴浏览器、Yandex浏览器和Kiwi浏览器。

Від 07.07.2024. Дивіться остання версія.

  1. // ==UserScript==
  2. // @name 手机浏览器触摸手势
  3. // @name:en Mobile browser touch gestures
  4. // @description 为手机浏览器添加触摸手势,即装即用,无需配置。除了通用手势外,还有针对文字、图片、视频的特殊手势。还嫌不够?支持添加属于你的个性化手势。推荐使用狐猴浏览器、Yandex浏览器和Kiwi浏览器。
  5. // @description:en Add touch gestures to your mobile browser, ready to use and no configuration required. In addition to general gestures, there are also special gestures for text, images, and videos. Do you still think it's not enough? Support adding personalized gestures that belong to you. We recommend using Lemur browser, Yandex browser, and Kiwi browser.
  6. // @version 9.4.4
  7. // @author L.Xavier
  8. // @namespace https://greasyfork.org/zh-CN/users/128493
  9. // @match *://*/*
  10. // @license MIT
  11. // @grant window.close
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_openInTab
  15. // @grant GM_setClipboard
  16. // @grant GM_addValueChangeListener
  17. // @run-at document-start
  18. // ==/UserScript==
  19. // v9.4.4 2024-07-07 - 为部分浏览器(如:狐猴浏览器)增加视频后台播放功能
  20. /*手势数据模块*/
  21. let gesture={
  22. '↑→↓←':'打开设置',
  23. '◆◆':'视频全屏',
  24. '●':'手势穿透',
  25. '→←':'后退',
  26. '←→':'前进',
  27. '↓↑':'回到顶部',
  28. '↑↓':'回到底部',
  29. '←↓':'刷新页面',
  30. '←↑':'新建页面',
  31. '→↓':'关闭页面',
  32. '→↑':'恢复页面',
  33. '↓↑●':'新页面打开',
  34. '↑↓●':'隐藏元素',
  35. '↓→':'复制页面',
  36. '→←→':'半屏模式',
  37. '→↓↑←':'视频解析',
  38. 'T→↑':'百度翻译',
  39. 'T←↑':'有道翻译',
  40. 'T◆◆':'双击搜索',
  41. 'I↓↑●':'打开图片',
  42. 'I→↑●':'百度搜图',
  43. 'V→':'前进10s',
  44. 'V←':'后退10s',
  45. 'V↑':'增加倍速',
  46. 'V↓':'减小倍速',
  47. 'V→●':'快进播放',
  48. 'V→○':'停止快进',
  49. 'V←●':'快退播放',
  50. 'V←○':'停止快退',
  51. 'V↑●':'增加音量',
  52. 'V↑○':'关闭增加音量',
  53. 'V↓●':'减少音量',
  54. 'V↓○':'关闭减少音量',
  55. 'V→▼':'右滑进度',
  56. 'V→▽':'关闭右滑进度',
  57. 'V←▼':'左滑进度',
  58. 'V←▽':'关闭左滑进度'
  59. },
  60. pathFn={
  61. '打开设置':'/*ONLY TOP*/openSet();',
  62. '视频全屏':'videoFullScreen();',
  63. '手势穿透':'setTimeout(()=>{if(/^[TIV]/.test(path)){path=(path.indexOf("I")>-1) ? "I" : "";return;}if(gestureData.touchEle.nodeName!=="IMG"){let imgs=gestureData.touchEle.parentNode.getElementsByTagName("img");for(let Ti=0,len=imgs.length;Ti<len;++Ti){let imgRect=imgs[Ti].getBoundingClientRect();if(gestureData.touchStart.clientX>imgRect.x && gestureData.touchStart.clientX<(imgRect.x+imgRect.width) && gestureData.touchStart.clientY>imgRect.y && gestureData.touchStart.clientY<(imgRect.y+imgRect.height)){gestureData.touchEle=imgs[Ti];break;}}}if(gestureData.touchEle.nodeName==="IMG" && settings["图片手势"]){path="I";}else if(gestureData.touchEle.style.backgroundImage && settings["图片手势"]){gestureData.touchEle.src=gestureData.touchEle.style.backgroundImage.split(\'"\')[1];path="I";}});',
  64. '后退':'/*ONLY TOP*/function pageBack(){if(gestureData.backTimer){history.go(-1);setTimeout(pageBack,20);}}gestureData.backTimer=setTimeout(()=>{gestureData.backTimer=0;window.close();},200);pageBack();',
  65. '前进':'/*ONLY TOP*/history.go(1);',
  66. '回到顶部':'/*WITH TOP*/let boxNode=gestureData.touchEle.parentNode;while(boxNode.nodeName!=="#document"){boxNode.scrollIntoView(true);if(boxNode.scrollTop){boxNode.scrollTo(0,0);}boxNode=boxNode.parentNode;}',
  67. '回到底部':'/*WITH TOP*/let boxNode=gestureData.touchEle.parentNode;while(boxNode.nodeName!=="#document"){if(getComputedStyle(boxNode).overflowY!=="hidden"){boxNode.scrollTo(0,boxNode.scrollHeight+999999);}boxNode=boxNode.parentNode;}',
  68. '刷新页面':'/*ONLY TOP*/document.documentElement.style.cssText="filter:grayscale(100%)";history.go(0);',
  69. '新建页面':'/*ONLY TOP*/GM_openInTab("//limestart.cn");',
  70. '关闭页面':'/*ONLY TOP*/window.close();',
  71. '恢复页面':'/*ONLY TOP*/GM_openInTab("chrome-native://recent-tabs");',
  72. '新页面打开':'let linkNode=gestureData.touchEle;while(true){if(linkNode.href){GM_openInTab(linkNode.href);break;}linkNode=linkNode.parentNode;if(linkNode.nodeName==="BODY"){gestureData.touchEle.click();break;}}',
  73. '隐藏元素':'let boxNode=gestureData.touchEle,area=boxNode.offsetWidth*boxNode.offsetHeight,area_p=boxNode.parentNode.offsetWidth*boxNode.parentNode.offsetHeight,area_s=screen.width*screen.height;while(boxNode.parentNode.nodeName!=="BODY" && area/area_p>0.2 && area_p/area_s<0.9){boxNode=boxNode.parentNode;area_p=boxNode.parentNode.offsetWidth*boxNode.parentNode.offsetHeight;}if(boxNode.nodeName!=="HTML"){boxNode.remove();}',
  74. '复制页面':'/*ONLY TOP*/GM_openInTab(location.href);',
  75. '半屏模式':'/*ONLY TOP*/if(gestureData.halfScreen){setTimeout(()=>{gestureData.halfScreen.remove();halfClose.remove();gestureData.halfScreen=null;document.documentElement.scrollTop=gestureData.scrollTop;},500);gestureData.scrollTop=document.body.scrollTop;let halfClose=addStyle("html{transform:translateY(0) !important;}");}else{gestureData.scrollTop=document.documentElement.scrollTop;gestureData.halfScreen=addStyle("html,body{height:43vh !important;overflow-y:auto !important;}html{transform:translateY(50vh) !important;transition:0.5s !important;overflow:hidden !important;}");document.body.scrollTop=gestureData.scrollTop;}',
  76. '视频解析':'/*ONLY TOP*/GM_openInTab("https://jx.xmflv.com/?url="+location.href);',
  77. '百度翻译':'GM_openInTab("//fanyi.baidu.com/#auto/auto/"+encodeURIComponent(gestureData.selectWords));',
  78. '有道翻译':'GM_openInTab("//dict.youdao.com/w/eng/"+encodeURIComponent(gestureData.selectWords));',
  79. '双击搜索':'copyText(gestureData.selectWords);if(!regURL.test(gestureData.selectWords.trim())){gestureData.selectWords="//bing.com/search?q="+encodeURIComponent(gestureData.selectWords);}else if(!/^(https?:)?\\/\\//.test(gestureData.selectWords.trim())){gestureData.selectWords="//"+gestureData.selectWords.trim();}GM_openInTab(gestureData.selectWords.trim());',
  80. '打开图片':'GM_openInTab(gestureData.touchEle.src);',
  81. '百度搜图':'GM_openInTab("//graph.baidu.com/details?isfromtusoupc=1&tn=pc&carousel=0&promotion_name=pc_image_shituindex&extUiData%5bisLogoShow%5d=1&image="+gestureData.touchEle.src);',
  82. '前进10s':'videoPlayer.currentTime+=10;gestureData.tipBox.textContent="+10s ";gestureData.tipBox.style.display="block";setTimeout(()=>{gestureData.tipBox.style.display="none";},500);',
  83. '后退10s':'videoPlayer.currentTime-=10;gestureData.tipBox.textContent="-10s ";gestureData.tipBox.style.display="block";setTimeout(()=>{gestureData.tipBox.style.display="none";},500);',
  84. '增加倍速':'if(document.fullscreen){let playSpeed=videoPlayer.playbackRate;playSpeed+=(playSpeed<1.5) ? 0.25 : 0.5;gestureData.tipBox.textContent="×"+playSpeed+" ∞ ";gestureData.tipBox.style.display="block";videoPlayer.playbackRate=playSpeed;setTimeout(()=>{gestureData.tipBox.style.display="none";},500)}',
  85. '减小倍速':'if(document.fullscreen){let playSpeed=videoPlayer.playbackRate;playSpeed-=(playSpeed>1.5) ? 0.5 : (playSpeed>0.25 && 0.25);gestureData.tipBox.textContent="×"+playSpeed+" ∞ ";gestureData.tipBox.style.display="block";videoPlayer.playbackRate=playSpeed;setTimeout(()=>{gestureData.tipBox.style.display="none";},500)}',
  86. '快进播放':'gestureData.playSpeed=videoPlayer.playbackRate;videoPlayer.playbackRate=10;gestureData.tipBox.textContent="×10 ";gestureData.tipBox.style.display="block";',
  87. '停止快进':'videoPlayer.playbackRate=gestureData.playSpeed;gestureData.tipBox.style.display="none";',
  88. '快退播放':'gestureData.videoTimer=setInterval(()=>{--videoPlayer.currentTime;},100);gestureData.tipBox.textContent="- ×10 ";gestureData.tipBox.style.display="block";',
  89. '停止快退':'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";',
  90. '增加音量':'if(document.fullscreen){videoPlayer.muted=false;gestureData.tipBox.textContent=(videoPlayer.volume*100|0)+"%";gestureData.tipBox.style.display="block";let lastY=gestureData.touchEnd.screenY;gestureData.videoTimer=setInterval(()=>{if(lastY-gestureData.touchEnd.screenY){let tempVolume=videoPlayer.volume+(lastY-gestureData.touchEnd.screenY)/100;videoPlayer.volume=+(tempVolume>1) || (+(tempVolume>0) && tempVolume);gestureData.tipBox.textContent=(videoPlayer.volume*100|0)+"%";lastY=gestureData.touchEnd.screenY;}},50);}',
  91. '关闭增加音量':'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";',
  92. '减少音量':'if(document.fullscreen){videoPlayer.muted=false;gestureData.tipBox.textContent=(videoPlayer.volume*100|0)+"%";gestureData.tipBox.style.display="block";let lastY=gestureData.touchEnd.screenY;gestureData.videoTimer=setInterval(()=>{if(lastY-gestureData.touchEnd.screenY){let tempVolume=videoPlayer.volume+(lastY-gestureData.touchEnd.screenY)/100;videoPlayer.volume=+(tempVolume>1) || (+(tempVolume>0) && tempVolume);gestureData.tipBox.textContent=(videoPlayer.volume*100|0)+"%";lastY=gestureData.touchEnd.screenY;}},50);}',
  93. '关闭减少音量':'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";',
  94. '右滑进度':'let lastX=gestureData.touchEnd.screenX,hour,minu,sec;function showTip(){minu=videoPlayer.currentTime/60;sec=videoPlayer.currentTime%60;hour=minu/60;minu%=60;gestureData.tipBox.textContent=(hour|0)+":"+((minu<10) ? "0":"")+(minu|0)+":"+((sec<10) ? "0" : "")+(sec|0);}showTip();gestureData.tipBox.style.display="block";gestureData.videoTimer=setInterval(()=>{let len=gestureData.touchEnd.screenX-lastX;if(len){videoPlayer.currentTime+=len*(1+Math.abs(len)/5);lastX=gestureData.touchEnd.screenX;}showTip();},50);',
  95. '关闭右滑进度':'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";',
  96. '左滑进度':'let lastX=gestureData.touchEnd.screenX,hour,minu,sec;function showTip(){minu=videoPlayer.currentTime/60;sec=videoPlayer.currentTime%60;hour=minu/60;minu%=60;gestureData.tipBox.textContent=(hour|0)+":"+((minu<10) ? "0":"")+(minu|0)+":"+((sec<10) ? "0" : "")+(sec|0);}showTip();gestureData.tipBox.style.display="block";gestureData.videoTimer=setInterval(()=>{let len=gestureData.touchEnd.screenX-lastX;if(len){videoPlayer.currentTime+=len*(1+Math.abs(len)/5);lastX=gestureData.touchEnd.screenX;}showTip();},50);',
  97. '关闭左滑进度':'clearInterval(gestureData.videoTimer);gestureData.tipBox.style.display="none";'
  98. },
  99. settings={
  100. '滑动系数':[0.2,0,0.5,2],//[当前值,最小值,最大值,取值精度]
  101. '文字手势':true,
  102. '图片手势':true,
  103. '视频手势':true,
  104. '链接加速':false,
  105. '视频下载':false,
  106. '避免断触':false
  107. };
  108. //存储数据读取
  109. gesture=GM_getValue('gesture',gesture);
  110. pathFn=GM_getValue('pathFn',pathFn);
  111. settings=GM_getValue('settings',settings);
  112. //脚本常量
  113. const gestureData={},minSide=(screen.width>screen.height) ? screen.height : screen.width,limit=(minSide*settings['滑动系数'][0])**2,attachShadow=Element.prototype.attachShadow,regURL=/^((https?:)?\/\/)?([\w\-]+\.)+\w{2,4}(:\d{1,5})?(\/\S*)?$/,mObserver=new MutationObserver(()=>{if(!checkTimer){checkTimer=setTimeout(loadCheck,200);}});
  114.  
  115. /*手势功能模块*/
  116. //手指功能变量
  117. let path='',startPoint=null,timeSpan=0,raiseTime=0,slideTime=0,slideLimit=0,fingersNum=0,gestureTimer=0,clickTimer=0,isAllow=0,isClick=0;
  118. //手势执行
  119. function runCode(code){
  120. try{eval(code);}catch(error){
  121. if((error+'').indexOf('unsafe-eval')>-1){
  122. if(!window._eval_){
  123. window._eval_=(()=>{
  124. let script=document.createElement('script');
  125. function thisParams(){
  126. this.window.close=window.close;
  127. this.GM_setValue=GM_setValue;
  128. this.GM_getValue=GM_getValue;
  129. this.GM_openInTab=GM_openInTab;
  130. this.GM_setClipboard=GM_setClipboard;
  131. this.copyText=copyText;
  132. this.runCode=runCode;
  133. this.runFrame=runFrame;
  134. this.runGesture=runGesture;
  135. this.videoFullScreen=videoFullScreen;
  136. this.findVideoBox=findVideoBox;
  137. this.addStyle=addStyle;
  138. this.openSet=openSet;
  139. this.gestureData=gestureData;
  140. this.regURL=regURL;
  141. this.path=path;
  142. this.videoPlayer=videoPlayer;
  143. }
  144. return (js)=>{
  145. thisParams();script.remove();
  146. script.textContent='try{'+js+'}catch(error){alert("“"+path+"” 手势执行脚本错误:\\n"+error+" !");}';
  147. document.body.insertAdjacentElement('beforeend',script);
  148. }
  149. })();
  150. if(top===self){window._eval_('window.addEventListener("popstate",()=>{clearTimeout(gestureData.backTimer);gestureData.backTimer=0;},true);window.addEventListener("beforeunload",()=>{clearTimeout(gestureData.backTimer);gestureData.backTimer=0;},true);');}
  151. }
  152. window._eval_(code);
  153. }
  154. else{alert('“'+path+'” 手势执行脚本错误:\n'+error+' !');}
  155. }
  156. }
  157. function runFrame(runPath){
  158. let code=pathFn[gesture[runPath]];
  159. if(top===self || /^[TIV]/.test(runPath)){runCode(code);}
  160. else{
  161. if(code.indexOf('/*ONLY TOP*/')<0){runCode(code);}
  162. if(/\/\*(ONLY|WITH) TOP\*\//.test(code)){
  163. if(/[●▼]$/.test(runPath)){window._isPushing_=()=>{let _gestureData={};_gestureData.touchEnd=copyTouch(gestureData.touchEnd);top.postMessage({'type':'pushTouch','gestureData':_gestureData},'*');}}
  164. let _gestureData={};
  165. _gestureData.touchStart=copyTouch(gestureData.touchStart);
  166. _gestureData.touchEnd=copyTouch(gestureData.touchEnd);
  167. top.postMessage({'type':'runPath','runPath':path,'gestureData':_gestureData},'*');
  168. }
  169. }
  170. }
  171. function runGesture(newPath){
  172. if(gesture[path]){
  173. runFrame(path);
  174. if(gesture[newPath]){path=newPath;}
  175. }else if(gesture[path.slice(1)] && /^[TIV]/.test(path)){
  176. runFrame(path.slice(1));
  177. if(gesture[newPath?.slice(1)]){path=newPath;}
  178. } raiseTime=0;
  179. }
  180. //长按执行
  181. function longPress(){
  182. if(!/[●○▽]$/.test(path)){
  183. isAllow=isClick=0;
  184. startPoint=gestureData.touchEnd;
  185. let newPath=path+'○';path+='●';
  186. runGesture(newPath);
  187. }
  188. }
  189. //持续滑动执行
  190. function slidingRun(){
  191. slideTime=0;
  192. let newPath=path+'▽';path+='▼';
  193. runGesture(newPath);
  194. path=path.replace('▼','');
  195. }
  196. //手指按下
  197. function touchStart(e){
  198. clearTimeout(gestureTimer);
  199. if((fingersNum=e.touches.length)>1){return;}
  200. timeSpan=Date.now()-raiseTime;
  201. let lineLen=raiseTime && (e.changedTouches[0].screenX-gestureData.touchEnd.screenX)**2+(e.changedTouches[0].screenY-gestureData.touchEnd.screenY)**2;
  202. if(timeSpan>50 || lineLen>limit){//断触判断
  203. startPoint=e.changedTouches[0];
  204. if(timeSpan>200 || lineLen>limit){
  205. path='';slideLimit=limit;
  206. gestureData.touchEle=e.target;
  207. gestureData.touchEnd=gestureData.touchStart=startPoint;
  208. gestureData.selectWords=window.getSelection()+'';
  209. if(gestureData.selectWords && settings['文字手势']){path='T';}
  210. else if(document.contains(videoPlayer) && settings['视频手势']){
  211. let videoRect=findVideoBox().getBoundingClientRect();
  212. if(fullsState>0 && gestureData.touchStart.clientY<(videoRect.y+videoRect.height/8)){path='!';}
  213. else if(gestureData.touchStart.clientX>videoRect.x && gestureData.touchStart.clientX<(videoRect.x+videoRect.width) && gestureData.touchStart.clientY>videoRect.y && gestureData.touchStart.clientY<(videoRect.y+videoRect.height)){path='V';}
  214. }
  215. }else if(isClick){e.preventDefault();} isClick=1;
  216. }else if(isClick){clearTimeout(clickTimer);path=path.slice(0,-1);}
  217. gestureTimer=setTimeout(longPress,300);
  218. }
  219. //手指滑动
  220. function touchMove(e){
  221. clearTimeout(gestureTimer);
  222. gestureData.touchEnd=e.changedTouches[0];
  223. if(window._isPushing_){setTimeout(window._isPushing_);}
  224. if(/[○▽]$/.test(path) || fingersNum>1){return;}
  225. let xLen=(gestureData.touchEnd.screenX-startPoint.screenX)**2,yLen=(gestureData.touchEnd.screenY-startPoint.screenY)**2,
  226. direction=(xLen>yLen*1.42) ? ((gestureData.touchEnd.screenX>startPoint.screenX) ? '→' : '←') : ((gestureData.touchEnd.screenY>startPoint.screenY) ? '↓' : '↑'),
  227. nowTime=Date.now(),pathLen=xLen+yLen,lastIcon=path.slice(-1);
  228. if((lastIcon===direction && pathLen>limit/576) || pathLen>slideLimit){
  229. if(lastIcon!==direction && (timeSpan<50 || 'TIV◆'.indexOf(lastIcon)>-1)){path+=direction;slideLimit*=(slideLimit<limit/2) || 0.64;slideTime=nowTime;isAllow=1;timeSpan=0;}
  230. startPoint=gestureData.touchEnd;
  231. if(slideTime && nowTime-slideTime>300){setTimeout(slidingRun);}
  232. }else if(pathLen>limit/100){isClick=slideTime=isAllow=0;}
  233. gestureTimer=setTimeout(longPress,300);
  234. }
  235. //手指抬起
  236. function touchEnd(e){
  237. clearTimeout(gestureTimer);
  238. if(--fingersNum>0){if(!/[○▽]$/.test(path)){path='!';}return;}
  239. if(window._isPushing_){window._isPushing_=null;}
  240. gestureData.touchEnd=e.changedTouches[0];
  241. raiseTime=Date.now();setTimeout(iframeLock);
  242. if(/[○▽]$/.test(path)){setTimeout(runGesture);return;}
  243. if(isClick){isAllow=1;path+='◆';if(/^V◆◆$|^T/.test(path)){e.stopPropagation();e.preventDefault();window.getSelection().empty();}}
  244. if(isAllow){gestureTimer=setTimeout(runGesture,199);}
  245. }
  246. //延迟点击,避免断触触发点击
  247. function delayClick(e){
  248. if(e.isTrusted){
  249. e.stopImmediatePropagation();e.stopPropagation();e.preventDefault();
  250. if(timeSpan<50){return;}
  251. let ev=new PointerEvent('click',{bubbles:true,cancelable:true,clientX:e.clientX,clientY:e.clientY,composed:true,detail:1,layerX:e.layerX,layerY:e.layerY,offsetX:e.offsetX,offsetY:e.offsetY,pageX:e.pageX,pageY:e.pageY,pointerId:e.pointerId,pointerType:e.pointerType,screenX:e.screenX,screenY:e.screenY,sourceCapabilities:e.sourceCapabilities,view:e.view,x:e.x,y:e.y});
  252. clickTimer=setTimeout(()=>{e.target.dispatchEvent(ev);},50);
  253. }
  254. }
  255.  
  256. /*视频功能模块*/
  257. //视频功能变量
  258. let videoPlayer=null,oriLock=0,resizeTimer=0,fullsState=0;
  259. //videoPlayer赋值
  260. async function setVideo(player){
  261. let _videoPlayer=player.target || player;
  262. if(videoPlayer?.paused===false && _videoPlayer.muted===true){return;}
  263. videoPlayer=_videoPlayer;
  264. videoOriLock();
  265. videoPlayer.parentNode.insertAdjacentElement('beforeend',gestureData.tipBox);
  266. if(settings['视频下载']){
  267. await findVideoBox()?.insertAdjacentElement('beforeend',videoPlayer._downloadTip_);
  268. if(window._urlObjects_[videoPlayer.src]){
  269. videoPlayer._downloadTip_.textContent='正在捕获';
  270. videoPlayer._downloadTip_.buffers=window._urlObjects_[videoPlayer.src].sourceBuffers;
  271. window._urlObjects_[videoPlayer.src]._downloadTip_=videoPlayer._downloadTip_;
  272. delete window._urlObjects_[videoPlayer.src];
  273. }else if(videoPlayer._downloadTip_.textContent==='未加载'){
  274. if(!videoPlayer.src && videoPlayer.children.length){videoPlayer.src=videoPlayer.firstChild.src;}
  275. if(videoPlayer.src.indexOf('blob:') && videoPlayer.src){videoPlayer._downloadTip_.textContent='可下载';}
  276. }
  277. }
  278. }
  279. //video方向锁定
  280. function videoOriLock(){
  281. if(!videoPlayer.videoWidth){if(!videoPlayer.error && document.contains(videoPlayer)){setTimeout(videoOriLock,100);}oriLock=0;return;}
  282. oriLock=+(videoPlayer.videoWidth>videoPlayer.videoHeight);
  283. if(fullsState>0 && oriLock){top.postMessage({'type':'GYRO'},'*');}
  284. else{screen.orientation.unlock();}
  285. }
  286. //video框架锁定
  287. function iframeLock(){
  288. if(top!==self && !window._isShow_){GM_setValue('isShow',Date.now());}
  289. }
  290. //video全屏/退出全屏
  291. async function videoFullScreen(){
  292. if(resizeTimer){return;}
  293. if(document.fullscreen){await document.exitFullscreen()?.catch(Date);}
  294. else if(videoPlayer){await findVideoBox()?.requestFullscreen()?.catch(Date);}
  295. else if(iframeEles.length){GM_setValue('fullscreen',Date.now());}
  296. }
  297. //获取video全屏样式容器
  298. function findVideoBox(player=videoPlayer){
  299. if(!document.contains(player)){return null;}
  300. if(player._videoBox_?.contains(player) && (document.fullscreen || player._boxHeight_>=player._videoBox_.clientHeight)){return player._videoBox_;}
  301. player._videoBox_=player.parentNode;player.setAttribute('_videobox_','');
  302. let parentEle=player._videoBox_.parentNode,videoStyle=getComputedStyle(player),childStyle=getComputedStyle(player._videoBox_),childWidth=0,childHeight=0,_childWidth=0,_childHeight=0;
  303. if(player._videoBox_.offsetParent===parentEle){
  304. childWidth=Math.round(player.offsetWidth+(+videoStyle.marginLeft.slice(0,-2))+(+videoStyle.marginRight.slice(0,-2)));
  305. childHeight=Math.round(player.offsetHeight+(+videoStyle.marginTop.slice(0,-2))+(+videoStyle.marginBottom.slice(0,-2)));
  306. _childWidth=Math.round(player._videoBox_.offsetWidth+(+childStyle.marginLeft.slice(0,-2))+(+childStyle.marginRight.slice(0,-2)));
  307. _childHeight=Math.round(player._videoBox_.offsetHeight+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2)));
  308. }else{
  309. childWidth=Math.round(player.offsetWidth+(+videoStyle.left.slice(0,-2) || 0)+(+videoStyle.marginLeft.slice(0,-2))+(+videoStyle.marginRight.slice(0,-2))+(+videoStyle.right.slice(0,-2) || 0));
  310. childHeight=Math.round(player.offsetHeight+(+videoStyle.top.slice(0,-2) || 0)+(+videoStyle.marginTop.slice(0,-2))+(+videoStyle.marginBottom.slice(0,-2))+(+videoStyle.bottom.slice(0,-2) || 0));
  311. _childWidth=Math.round(player._videoBox_.offsetWidth+(+childStyle.left.slice(0,-2) || 0)+(+childStyle.marginLeft.slice(0,-2))+(+childStyle.marginRight.slice(0,-2))+(+childStyle.right.slice(0,-2) || 0));
  312. _childHeight=Math.round(player._videoBox_.offsetHeight+(+childStyle.top.slice(0,-2) || 0)+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2))+(+childStyle.bottom.slice(0,-2) || 0));
  313. }
  314. childWidth=(childWidth>_childWidth) ? childWidth : _childWidth;
  315. childHeight=(childHeight>_childHeight) ? childHeight : _childHeight;
  316. while(childWidth>=parentEle.clientWidth && childHeight>=parentEle.clientHeight && parentEle.nodeName!='BODY'){
  317. if(parentEle.clientHeight){
  318. player._videoBox_.setAttribute('_videobox_','');
  319. player._videoBox_=parentEle;player._boxHeight_=childHeight;
  320. }
  321. childStyle=getComputedStyle(parentEle);
  322. if(parentEle.offsetParent===parentEle.parentNode){
  323. _childWidth=Math.round(parentEle.offsetWidth+(+childStyle.marginLeft.slice(0,-2))+(+childStyle.marginRight.slice(0,-2)));
  324. _childHeight=Math.round(parentEle.offsetHeight+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2)));
  325. }else{
  326. _childWidth=Math.round(parentEle.offsetWidth+(+childStyle.left.slice(0,-2) || 0)+(+childStyle.marginLeft.slice(0,-2))+(+childStyle.marginRight.slice(0,-2))+(+childStyle.right.slice(0,-2) || 0));
  327. _childHeight=Math.round(parentEle.offsetHeight+(+childStyle.top.slice(0,-2) || 0)+(+childStyle.marginTop.slice(0,-2))+(+childStyle.marginBottom.slice(0,-2))+(+childStyle.bottom.slice(0,-2) || 0));
  328. }
  329. childWidth=(childWidth>_childWidth) ? childWidth : _childWidth;
  330. childHeight=(childHeight>_childHeight) ? childHeight : _childHeight;
  331. parentEle=parentEle.parentNode;
  332. }
  333. player._videoBox_.setAttribute('_videobox_','outer');
  334. return player._videoBox_;
  335. }
  336. //全屏检测事件
  337. function regRESIZE(){
  338. let videoCss=addStyle(''),stopResize=()=>{resizeTimer=0;};
  339. window.addEventListener('resize',()=>{
  340. clearTimeout(resizeTimer);resizeTimer=setTimeout(stopResize,200);
  341. if(document.fullscreen && !fullsState){
  342. fullsState=document.fullscreenElement;
  343. if(fullsState.nodeName==='IFRAME'){fullsState=-1;return;}
  344. let srcFindVideo=fullsState.getElementsByTagName('video'),srcVideo=(fullsState.nodeName==='VIDEO') ? fullsState : srcFindVideo[0];
  345. if(!fullsState.hasAttribute('_videobox_') && (!srcVideo || srcFindVideo.length>1 || (srcVideo._videoBox_.offsetWidth*srcVideo._videoBox_.offsetHeight/fullsState.offsetWidth/fullsState.offsetHeight)<0.9)){fullsState=-1;videoCss.textContent='';return;}
  346. if(srcVideo!==videoPlayer){videoPlayer?.pause();setVideo(srcVideo);}
  347. fullsState=1;if(oriLock){top.postMessage({'type':'GYRO'},'*');}
  348. videoCss.textContent='*[_videobox_]{inset:0 !important;margin:0 !important;padding:0 !important;transform:none !important;}*[_videobox_=""]{width:100% !important;height:100% !important;max-width:100% !important;max-height:100% !important;}video{position:fixed !important;object-fit:contain !important;}';
  349. }else if(fullsState && !document.fullscreen){fullsState=0;videoCss.textContent='';}
  350. },true);
  351. }
  352.  
  353. /*视频下载模块*/
  354. if(settings['视频下载']){
  355. //原始方法存储
  356. const createObjectURL=URL.createObjectURL,addSourceBuffer=MediaSource.prototype.addSourceBuffer,appendBuffer=SourceBuffer.prototype.appendBuffer,endOfStream=MediaSource.prototype.endOfStream;
  357. //初始化视频下载
  358. window._initDownload_=(player)=>{
  359. player._downloadTip_=document.createElement('div');
  360. player._downloadTip_.style.cssText='position:absolute;right:0;top:20px;background:#3498db;border-radius:20px 0 0 20px;text-align:center;padding:20px;line-height:0px;color:#fff;min-width:60px;font-size:16px;font-family:system-ui;z-index:2147483647;';
  361. player._downloadTip_.target=player;
  362. player._downloadTip_.textContent='未加载';
  363. if(window._urlObjects_[player.src]){
  364. player._downloadTip_.textContent='正在捕获';
  365. player._downloadTip_.buffers=window._urlObjects_[player.src].sourceBuffers;
  366. window._urlObjects_[player.src]._downloadTip_=player._downloadTip_;
  367. delete window._urlObjects_[player.src];
  368. }else{
  369. if(!player.src && player.children.length){player.src=player.firstChild.src;}
  370. if(player.src.indexOf('blob:') && player.src){player._downloadTip_.textContent='可下载';}
  371. }
  372. player._downloadTip_.onclick=window._downloadVideo_;
  373. player._videoBox_.insertAdjacentElement('beforeend',player._downloadTip_);
  374. }
  375. //下载视频
  376. window._downloadVideo_=function(data){
  377. if(this.textContent==='未加载'){return;}
  378. if(data.target){data=this;data.src=this.target.src;}
  379. let buffers=data.buffers;
  380. if(top!==self){
  381. let _buffers=[];
  382. for(let Ti=0,len=buffers.length;Ti<len;++Ti){
  383. _buffers.push({'mime':buffers[Ti]._mime_,'bufferList':buffers[Ti]._bufferList_});
  384. }
  385. top.postMessage({'type':'download','buffers':_buffers,'src':data.src},'*');
  386. return;
  387. }
  388. let a=document.createElement('a');a.download=document.title;a.style.display='none';document.body.insertAdjacentElement('beforeend',a);
  389. if(data.src.indexOf('blob:') && data.src){a.href=data.src;a.click();}
  390. else if(buffers.length){
  391. for(let Ti=0,len=buffers.length;Ti<len;++Ti){
  392. a.href=URL.createObjectURL(new Blob(buffers[Ti]._bufferList_,{'type':buffers[Ti]._mime_}));
  393. a.click();
  394. URL.revokeObjectURL(a.href);
  395. }
  396. }
  397. a.remove();
  398. }
  399. //存储MediaSource
  400. window._urlObjects_={};
  401. URL.createObjectURL=(obj)=>{
  402. let url=createObjectURL(obj);
  403. if(obj.sourceBuffers){window._urlObjects_[url]=obj;}
  404. return url;
  405. }
  406. //添加捕获
  407. MediaSource.prototype.addSourceBuffer=function(mime){
  408. let sourceBuffer=addSourceBuffer.call(this,mime);
  409. sourceBuffer._bufferList_=[];
  410. sourceBuffer._mime_=mime;
  411. sourceBuffer._mediaSource_=this;
  412. return sourceBuffer;
  413. }
  414. //捕获片段
  415. SourceBuffer.prototype.appendBuffer=function(buffer){
  416. this._bufferList_.push(buffer);
  417. if(this._mime_.indexOf('video')>-1 && this._mediaSource_._downloadTip_){this._mediaSource_._downloadTip_.textContent='已捕获'+this._bufferList_.length+'个片段';}
  418. appendBuffer.call(this,buffer);
  419. }
  420. //捕获完成
  421. MediaSource.prototype.endOfStream=function(){
  422. if(this._downloadTip_){this._downloadTip_.textContent='可下载';}
  423. endOfStream.call(this);
  424. }
  425. }
  426.  
  427. /*功能补充模块*/
  428. //功能补充变量
  429. let videoEles=document.getElementsByTagName('video'),linkEles=document.getElementsByTagName('a'),iframeEles=document.getElementsByTagName('iframe'),checkTimer=0,linkNum=0,allowCopy=0;
  430. //修改Trusted-Types策略
  431. window.trustedTypes?.createPolicy('default',{createHTML:string=>string,createScript:string=>string,createScriptURL:string=>string});
  432. //设置shadow-root (open)
  433. Element.prototype.attachShadow=function(){
  434. if(!window._shadowList_){window._shadowList_=[];}
  435. let shadowRoot=attachShadow.call(this,{mode:'open'});
  436. window._shadowList_.push(shadowRoot);
  437. return shadowRoot;
  438. }
  439. //页面加载检测
  440. async function loadCheck(){
  441. //检测shadowRoot中的视频
  442. if(window._shadowList_){
  443. videoEles=[...document.getElementsByTagName('video')];
  444. for(let Ti=0,len=window._shadowList_.length;Ti<len;++Ti){
  445. videoEles.push(...window._shadowList_[Ti].querySelectorAll('video'));
  446. }
  447. }
  448. //video播放事件绑定
  449. if(videoEles.length){
  450. if(!gestureData.tipBox){
  451. //启动全屏检测
  452. regRESIZE();
  453. if(top!==self){top.postMessage({'type':'forceFullScreen'},'*');}
  454. //tip操作提示
  455. gestureData.tipBox=document.createElement('div');
  456. gestureData.tipBox.style.cssText='width:100px;height:50px;position:absolute;text-align:center;top:calc(50% - 25px);left:calc(50% - 50px);display:none;color:#1e87f0;font-size:22px;line-height:50px;background-color:rgba(0,0,0,0.6);border-radius:20px;font-family:system-ui;z-index:2147483647;';
  457. }
  458. for(let Ti=0,len=videoEles.length;Ti<len;++Ti){
  459. if(!videoEles[Ti]._videoBox_ && videoEles[Ti].offsetHeight){
  460. await findVideoBox(videoEles[Ti]);
  461. if(settings['视频下载']){await window._initDownload_(videoEles[Ti]);}
  462. if(!videoEles[Ti].paused){setVideo(videoEles[Ti]);}
  463. videoEles[Ti].addEventListener('playing',setVideo,true);
  464. }
  465. }
  466. }
  467. //链接加速事件绑定
  468. if(linkEles.length>linkNum && settings['链接加速']){
  469. if(!window._prefetch_){
  470. window._prefetch_=document.createElement('div');
  471. window._prefetch_.style.display='none';
  472. }
  473. for(let Ti=0,len=linkEles.length;Ti<len;++Ti){
  474. if(!linkEles[Ti]._prefetch_){
  475. linkEles[Ti]._prefetch_=1;if(!regURL.test(linkEles[Ti].href)){continue;}
  476. linkEles[Ti].addEventListener('touchstart',linkQuicken,true);
  477. }
  478. }
  479. }
  480. linkNum=linkEles.length;checkTimer=0;
  481. }
  482. //链接加速
  483. function linkQuicken(){
  484. window._prefetch_.innerHTML='<iframe style="width:0px;height:0px;display:none;" src="'+this.href.replace(/^https?:/,'')+'" loading="eager" importance="high" onload="this.remove();" onerror="this.remove();" sandbox></iframe>';
  485. document.body.insertAdjacentElement('beforeend',window._prefetch_);
  486. }
  487. //复制文本
  488. function copyText(text){
  489. allowCopy=1;GM_setClipboard(text);
  490. }
  491. //添加样式表
  492. function addStyle(css){
  493. let style=document.createElement('style');
  494. style.textContent=css;
  495. document.head.insertAdjacentElement('beforeend',style);
  496. return style;
  497. }
  498. //复制坐标对象
  499. function copyTouch(oldObj){
  500. let newObj={};
  501. for(let Ti in oldObj){
  502. if(Ti==='target'){continue;}
  503. newObj[Ti]=oldObj[Ti];
  504. }
  505. return newObj;
  506. }
  507. //手势功能设置UI
  508. function openSet(){
  509. let gestureName='',gesturePath='',gestureBox=document.createElement('div'),pathEle=null,_clickTimer=0;
  510. //页面生成
  511. addStyle('*{overflow:hidden !important;}'+
  512. '#_gestureBox_{background-color:#fff;width:100%;height:100%;position:fixed;padding:0;margin:0;inset:0;overflow-y:auto !important;z-index:2147483647;}'+
  513. '#_gestureBox_ *{font-family:system-ui;margin:0;padding:0;text-align:center;font-size:5vmin;line-height:12vmin;user-select:none !important;transform:none;text-indent:0;}'+
  514. '#_gestureBox_ ::placeholder{color:#999;font-size:2.5vmin;line-height:6vmin;}'+
  515. '#_gestureBox_ h1{width:60%;height:12vmin;color:#0074d9;background-color:#dee6ef;margin:3vmin auto;border-radius:12vmin;box-shadow:0.9vmin 0.9vmin 3vmin #dfdfdf;}'+
  516. '#_gestureBox_ #_addGesture_{width:14vmin;height:14vmin;margin:3vmin auto;line-height:14vmin;background-color:#dee6ef;color:#032e58;font-size:7.5vmin;border-radius:15vmin;box-shadow:0.3vmin 0.3vmin 1.5vmin #dfdfdf;}'+
  517. '#_gestureBox_ ._gestureLi_{height:18vmin;width:100%;border-bottom:0.3vmin solid #dfdfdf;}'+
  518. '#_gestureBox_ ._gestureLi_ p{margin:3vmin 0 0 1%;width:38%;height:12vmin;border-left:1.8vmin solid;color:#ffb400;background-color:#fff1cf;float:left;white-space:nowrap;text-overflow:ellipsis;text-shadow:0.3vmin 0.3vmin 3vmin #ffcb56;}'+
  519. '#_gestureBox_ ._gestureLi_ ._gesturePath_{margin:3vmin 0 0 3%;float:left;width:38%;height:12vmin;background-color:#f3f3f3;color:#000;box-shadow:0.3vmin 0.3vmin 1.5vmin #ccc9c9;border-radius:3vmin;white-space:nowrap;text-overflow:ellipsis;}'+
  520. '#_gestureBox_ ._gestureLi_ ._delGesture_{margin:3vmin 2% 0 0;width:15vmin;height:12vmin;float:right;color:#f00;text-decoration:line-through;}'+
  521. '#_gestureBox_ #_revisePath_{background-color:rgba(0,0,0,0.7);width:100%;height:100%;position:fixed;inset:0;display:none;color:#000;}'+
  522. '#_gestureBox_ #_revisePath_ span{width:15vmin;height:15vmin;font-size:12.5vmin;line-height:15vmin;position:absolute;}'+
  523. '#_gestureBox_ #_revisePath_ div{color:#3339f9;position:absolute;width:30%;height:12vmin;font-size:10vmin;bottom:15%;}'+
  524. '#_gestureBox_ #_revisePath_ p{color:#3ba5d8;position:absolute;top:15%;font-size:10vmin;height:12vmin;width:100%;}'+
  525. '#_gestureBox_ #_revisePath_ #_path_{top:40%;color:#ffee03;height:100%;word-wrap:break-word;font-size:15vmin;line-height:18vmin;}'+
  526. '#_gestureBox_ #_editGesture_{overflow-y:auto !important;background-color:#fff;width:100%;height:100%;position:fixed;inset:0;display:none;color:#000;}'+
  527. '#_gestureBox_ #_editGesture_ p{color:#3339f9;font-size:7.5vmin;text-align:left;margin:6vmin 0 0 9vmin;width:100%;height:9vmin;line-height:9vmin;}'+
  528. '#_gestureBox_ #_editGesture_ #_gestureName_{margin-top:6vmin;width:80%;height:12vmin;color:#000;border:0.3vmin solid #dadada;border-radius:3vmin;text-align:left;padding:0 3vmin;}'+
  529. '#_gestureBox_ #_editGesture_ ._label_box_>label{display:inline-block;margin-top:6vmin;position:relative;}'+
  530. '#_gestureBox_ #_editGesture_ ._label_box_>label>input{position:absolute;top:0;left:-6vmin;}'+
  531. '#_gestureBox_ #_editGesture_ ._label_box_>label>div{width:20vw;border:#ddd solid 0.3vmin;height:12vmin;color:#666;position:relative;}'+
  532. '#_gestureBox_ #_editGesture_ ._label_box_>label>input:checked + div{border:#d51917 solid 0.3vmin;color:#d51917;}'+
  533. '#_gestureBox_ #_editGesture_ ._label_box_>label>input + div:after{top:auto;left:auto;bottom:-3vmin;right:0;transition:none;}'+
  534. '#_gestureBox_ #_editGesture_ ._label_box_>label>input:checked + div:after{content:"";display:block;border:none;width:6vmin;height:6vmin;background-color:#d51917;transform:skewY(-45deg);position:absolute;}'+
  535. '#_gestureBox_ #_editGesture_ ._label_box_>label>input:checked + div:before{content:"";display:block;width:0.9vmin;height:2.4vmin;border-right:#fff solid 0.6vmin;border-bottom:#fff solid 0.6vmin;transform:rotate(35deg);position:absolute;bottom:0.6vmin;right:1.2vmin;z-index:1;}'+
  536. '#_gestureBox_ #_editGesture_ #_pathFn_{overflow-y:auto !important;width:80%;margin-top:6vmin;height:40%;text-align:left;line-height:6vmin;padding:3vmin;border:0.3vmin solid #dadada;border-radius:3vmin;}'+
  537. '#_gestureBox_ #_editGesture_ button{width:30vmin;height:15vmin;font-size:7.5vmin;line-height:15vmin;display:inline-block;color:#fff;background-color:#2866bd;margin:6vmin 3vmin 0 3vmin;border:none;}'+
  538. '#_gestureBox_ #_settingsBox_{overflow-y:auto !important;background-color:#fff;width:100%;height:100%;position:fixed;inset:0;display:none;color:#000;}'+
  539. '#_gestureBox_ #_settingsBox_ p{color:#3339f9;text-align:left;margin:9vmin 0 0 9vmin;float:left;height:6vmin;line-height:6vmin;clear:both;}'+
  540. '#_gestureBox_ #_settingsBox_ ._slideRail_{overflow:initial !important;width:55%;background-color:#a8a8a8;float:left;margin:12vmin 0 0 3vmin;height:0.6vmin;position:relative;}'+
  541. '#_gestureBox_ #_settingsBox_ ._slideRail_ ._slideButton_{line-height:9vmin;color:#fff;background-color:#2196f3;min-width:9vmin;height:9vmin;border-radius:9vmin;font-size:4vmin;position:absolute;top:-4.5vmin;box-shadow:0.3vmin 0.3vmin 1.8vmin #5e8aee;padding:0 1vmin;}'+
  542. '#_gestureBox_ #_settingsBox_ ._switch_{position:relative;display:inline-block;width:18vmin;height:9vmin;float:left;margin:7.5vmin 42% 0 3vmin;}'+
  543. '#_gestureBox_ #_settingsBox_ ._switch_ input{display:none;}'+
  544. '#_gestureBox_ #_settingsBox_ ._slider_{border-radius:9vmin;position:absolute;cursor:pointer;inset:0;background-color:#ccc;transition:0.4s;}'+
  545. '#_gestureBox_ #_settingsBox_ ._slider_:before{border-radius:50%;position:absolute;content:"";height:7.5vmin;width:7.5vmin;left:0.6vmin;bottom:0.6vmin;background-color:white;transition:0.4s;}'+
  546. '#_gestureBox_ #_settingsBox_ input:checked + ._slider_{background-color:#2196F3;}'+
  547. '#_gestureBox_ #_settingsBox_ input:checked + ._slider_:before{transform:translateX(9vmin);}'+
  548. '#_gestureBox_ #_settingsBox_ #_saveSettings_{display:block;clear:both;width:30vmin;height:15vmin;font-size:7.5vmin;line-height:15vmin;color:#fff;background-color:#2866bd;border:none;margin:12vmin 0 0 calc(50% - 15vmin);float:left;}');
  549. gestureBox.id='_gestureBox_';
  550. document.body.insertAdjacentElement('beforeend',gestureBox);
  551. gestureBox.innerHTML='<h1 id="_openSettings_">手势轨迹设置</h1><div id="_addGesture_">+</div><div id="_gestureUL_"></div>'+
  552. '<div id="_revisePath_"><span style="top:0;left:0;text-align:left;">┌</span><span style="top:0;right:0;text-align:right;">┐</span><span style="bottom:0;left:0;text-align:left;">└</span><span style="bottom:0;right:0;text-align:right;">┘</span>'+
  553. '<p>请滑动手指</p><p id="_path_"></p><div id="_clearPath_" style="left:10%;">清除</div><div id="_cancleRevise_" style="right:10%;">保存</div></div>'+
  554. '<div id="_editGesture_"><p>手势名称:</p><input type="text" id="_gestureName_" maxlength="12" placeholder="最大输入12个字符">'+
  555. '<p>手势类型:</p><div class="_label_box_"><label><input type="radio" id="_G_" name="_gestureType_" value=""><div>一般</div></label><label><input type="radio" id="_T_" name="_gestureType_" value="T"><div>文字</div></label><label><input type="radio" id="_I_" name="_gestureType_" value="I"><div>图片</div></label><label><input type="radio" id="_V_" name="_gestureType_" value="V"><div>视频</div></label></div>'+
  556. '<p>手势执行脚本:</p><textarea id="_pathFn_" placeholder="可用变量说明↓\n gestureData:手势数据常量,如果你需要在不同手势间传递变量,你可以赋值gestureData.变量名=变量值;\n gestureData.touchEle:手指触摸的源元素;\n gestureData.selectWords:选中的文字;\n gestureData.touchStart:触摸开始坐标对象;\n gestureData.touchEnd:触摸最新坐标对象;\n path:滑动的路径;\n videoPlayer:正在播放的视频元素。'+
  557. '\n\n可用方法说明↓\n addStyle(CSS样式):将CSS样式添加到网页上;\n runGesture():以path为路径执行手势,你可以修改path后执行此方法;\n GM_openInTab(链接):打开链接;\n copyText(文本):复制文本到剪切板;\n GM_setValue(变量名,变量值):在油猴中存储数据;\n GM_getValue(变量名,默认值):从油猴中取出数据,没有则使用默认值。'+
  558. '\n\n可识别代码注释说明(仅对一般手势生效)↓\n 默认情况:存在iframe时,所有手势只会在触发手势的页面对象执行!\n 添加/*ONLY TOP*/:手势只在顶级页面对象执行;\n 添加/*WITH TOP*/:手势同时在当前页面对象和顶级页面对象执行。"></textarea>'+
  559. '<div style="width:100%;height:0.3vmin;"></div><button id="_saveGesture_">保存</button><button id="_closeEdit_">关闭</button></div>'+
  560. '<div id="_settingsBox_"><h1>功能开关设置</h1><span id="_settingList_"></span><button id="_saveSettings_">保存</button></div>';
  561. pathEle=document.getElementById('_path_');
  562.  
  563. //编辑手势
  564. function editGesture(){
  565. gestureName=this.parentNode.getAttribute('name');
  566. if(['打开设置','视频全屏','手势穿透'].indexOf(gestureName)>-1){alert('该手势脚本无法修改!');return;}
  567. gesturePath=this.parentNode.getAttribute('path');
  568. let selectType=(/^[TIV]/.test(gesturePath)) ? '_'+gesturePath.slice(0,1)+'_' : '_G_';
  569. document.getElementById(selectType).click();
  570. document.getElementById('_gestureName_').value=gestureName;
  571. document.getElementById('_pathFn_').value=pathFn[gestureName];
  572. document.getElementById('_editGesture_').style.display='block';
  573. }
  574. //修改路径
  575. function revisePath(){
  576. gestureName=this.parentNode.getAttribute('name');
  577. gesturePath=this.parentNode.getAttribute('path');
  578. pathEle.textContent='';
  579. window.removeEventListener('touchmove',touchMove,true);
  580. document.getElementById('_revisePath_').style.display='block';
  581. }
  582. //删除手势
  583. function delGesture(){
  584. gestureName=this.parentNode.getAttribute('name');
  585. if(['打开设置','视频全屏','手势穿透'].indexOf(gestureName)>-1){alert('该手势无法删除!');return;}
  586. if(!confirm('确定删除"'+gestureName+'"手势')){return;}
  587. gesturePath=this.parentNode.getAttribute('path');
  588. delete pathFn[gestureName];
  589. delete gesture[gesturePath];
  590. GM_setValue('pathFn',pathFn);
  591. GM_setValue('gesture',gesture);
  592. init();
  593. }
  594. //滑动条
  595. function silideBar(e){
  596. e.preventDefault();fingersNum=2;
  597. let diffX=e.changedTouches[0].clientX-gestureData.touchStart.clientX,
  598. leftPX=(+this.style.left.slice(0,-2))+diffX,vmin=this.offsetWidth/2,setArr=settings[this.id];
  599. leftPX=(leftPX<-vmin) ? -vmin : ((leftPX>(diffX=this.parentNode.offsetWidth-vmin)) ? diffX : leftPX);
  600. this.style.left=leftPX+'px';
  601. this.textContent=((leftPX+vmin)/this.parentNode.offsetWidth*(setArr[2]-setArr[1])+setArr[1]).toFixed(setArr[3]);
  602. gestureData.touchStart=e.changedTouches[0];
  603. }
  604. //长按执行
  605. function _longPress(){if(!/[●○▼▽]$/.test(pathEle.textContent)){isClick=0;startPoint=gestureData.touchEnd;pathEle.textContent+='●';}}
  606. //持续滑动执行
  607. function _slidingRun(){slideTime=0;pathEle.textContent+='▼';}
  608. //点击执行
  609. function _clickRun(){if(!/[○▼▽]$/.test(pathEle.textContent)){pathEle.textContent+='◆';}}
  610. //界面初始化
  611. function init(){
  612. document.getElementById('_gestureUL_').textContent='';
  613. for(gestureName in pathFn){
  614. gesturePath='';
  615. for(let Ti in gesture){
  616. if(gesture[Ti]===gestureName){gesturePath=Ti;break;}
  617. }
  618. document.getElementById('_gestureUL_').innerHTML+='<div class="_gestureLi_" name="'+gestureName+'" path="'+gesturePath+'"><p>'+gestureName+'</p><div class="_gesturePath_">'+gesturePath+'</div><div class="_delGesture_">删除</div></div>';
  619. }
  620. //操作绑定
  621. let gestureEle=document.querySelectorAll('#_gestureBox_ ._gestureLi_ p');
  622. for(let Ti=0,len=gestureEle.length;Ti<len;++Ti){
  623. gestureEle[Ti].addEventListener('click',editGesture,true);
  624. }
  625. gestureEle=document.querySelectorAll('#_gestureBox_ ._gestureLi_ ._gesturePath_');
  626. for(let Ti=0,len=gestureEle.length;Ti<len;++Ti){
  627. gestureEle[Ti].addEventListener('click',revisePath,true);
  628. }
  629. gestureEle=document.querySelectorAll('#_gestureBox_ ._gestureLi_ ._delGesture_');
  630. for(let Ti=0,len=gestureEle.length;Ti<len;++Ti){
  631. gestureEle[Ti].addEventListener('click',delGesture,true);
  632. }
  633. }
  634. init();
  635.  
  636. //.新建手势
  637. document.getElementById('_addGesture_').addEventListener('click',()=>{
  638. gestureName=gesturePath='';
  639. document.getElementById('_G_').click();
  640. document.getElementById('_gestureName_').value='';
  641. document.getElementById('_pathFn_').value='';
  642. document.getElementById('_editGesture_').style.display='block';
  643. },true);
  644. //保存手势
  645. document.getElementById('_saveGesture_').addEventListener('click',()=>{
  646. if(!document.getElementById('_gestureName_').value){alert('请输入手势名称!');return;}
  647. if(pathFn[document.getElementById('_gestureName_').value] && gestureName!==document.getElementById('_gestureName_').value){alert('该手势名称已被占用!');return;}
  648. delete pathFn[gestureName];
  649. delete gesture[gesturePath];
  650. let typeEle=document.getElementsByName('_gestureType_');
  651. for(let Ti=0,len=typeEle.length;Ti<len;++Ti){
  652. if(typeEle[Ti].checked){
  653. gesturePath=typeEle[Ti].value+((gestureName && gesturePath.indexOf('[')<0) ? ((/^[TIV]/.test(gesturePath)) ? gesturePath.slice(1) : gesturePath) : ('['+document.getElementById('_gestureName_').value+']'));
  654. break;
  655. }
  656. }
  657. gesture[gesturePath]=document.getElementById('_gestureName_').value;
  658. pathFn[document.getElementById('_gestureName_').value]=document.getElementById('_pathFn_').value;
  659. GM_setValue('pathFn',pathFn);
  660. GM_setValue('gesture',gesture);
  661. init();
  662. document.getElementById('_editGesture_').style.display='none';
  663. },true);
  664. //关闭编辑
  665. document.getElementById('_closeEdit_').addEventListener('click',()=>{
  666. document.getElementById('_editGesture_').style.display='none';
  667. },true);
  668. //路径修改事件
  669. document.getElementById('_revisePath_').addEventListener('touchstart',()=>{
  670. if(fingersNum>1){return;}
  671. clearTimeout(gestureTimer);clearTimeout(_clickTimer);
  672. gestureTimer=setTimeout(_longPress,300);
  673. },true);
  674. document.getElementById('_revisePath_').addEventListener('touchmove',(e)=>{
  675. e.preventDefault();clearTimeout(gestureTimer);
  676. gestureData.touchEnd=e.changedTouches[0];
  677. if(/[○▼▽]$/.test(pathEle.textContent) || fingersNum>1){return;}
  678. let xLen=(gestureData.touchEnd.screenX-startPoint.screenX)**2,yLen=(gestureData.touchEnd.screenY-startPoint.screenY)**2,
  679. direction=(xLen>yLen) ? ((gestureData.touchEnd.screenX>startPoint.screenX) ? '→' : '←') : ((gestureData.touchEnd.screenY>startPoint.screenY) ? '↓' : '↑'),
  680. nowTime=Date.now(),pathLen=xLen+yLen,lastIcon=pathEle.textContent.slice(-1);
  681. if((lastIcon===direction && pathLen>limit/576) || pathLen>limit){
  682. if(lastIcon!==direction){pathEle.textContent+=direction;slideTime=nowTime;}
  683. startPoint=gestureData.touchEnd;
  684. if(slideTime && nowTime-slideTime>300){_slidingRun();}
  685. }else if(pathLen>limit/100){isClick=slideTime=0;}
  686. gestureTimer=setTimeout(_longPress,300);
  687. },true);
  688. document.getElementById('_revisePath_').addEventListener('touchend',(e)=>{
  689. if(!isClick || fingersNum>0){return;}
  690. if(path.indexOf('◆◆')>-1){path='';
  691. switch(pathEle.textContent.slice(-1)){
  692. case '●':{pathEle.textContent=pathEle.textContent.slice(0,-1)+'○';break;}
  693. case '○':{pathEle.textContent=pathEle.textContent.slice(0,-1)+'●';break;}
  694. case '▼':{pathEle.textContent=pathEle.textContent.slice(0,-1)+'▽';break;}
  695. case '▽':{pathEle.textContent=pathEle.textContent.slice(0,-1)+'▼';break;}
  696. default:{pathEle.textContent+='◆';setTimeout(_clickRun,100);break;}
  697. }
  698. }else{_clickTimer=setTimeout(_clickRun,200);}
  699. });
  700. //清除路径
  701. document.getElementById('_clearPath_').addEventListener('touchend',(e)=>{
  702. e.stopPropagation();
  703. if(!isClick || fingersNum>0){return;}
  704. if(path.indexOf('◆◆')>-1){path='';pathEle.textContent='';}
  705. else{pathEle.textContent=pathEle.textContent.slice(0,-1);}
  706. });
  707. //保存修改路径
  708. document.getElementById('_cancleRevise_').addEventListener('touchend',(e)=>{
  709. e.stopPropagation();e.preventDefault();
  710. if(!isClick || fingersNum>0){return;}
  711. if(pathEle.textContent){
  712. if(gestureName==='视频全屏' && pathEle.textContent.slice(-1)!=='◆'){alert('视频全屏需要以◆结尾!');return;}
  713. if(gesture[pathEle.textContent]==='手势穿透'){alert('路径与"手势穿透"功能冲突!');return;}
  714. if(/^[TIV]/.test(gesturePath)){pathEle.textContent=gesturePath.slice(0,1)+pathEle.textContent;}
  715. delete gesture[gesturePath];
  716. if(gesture[pathEle.textContent]){
  717. let pathTXT=((/^[TIV]/.test(gesturePath)) ? gesturePath.slice(0,1) : '')+'['+gesture[pathEle.textContent]+']';
  718. gesture[pathTXT]=gesture[pathEle.textContent];
  719. }
  720. gesture[pathEle.textContent]=gestureName;
  721. GM_setValue('gesture',gesture);
  722. init();
  723. }
  724. window.addEventListener('touchmove',touchMove,{capture:true,passive:true});
  725. document.getElementById('_revisePath_').style.display='none';
  726. });
  727. //打开功能开关设置
  728. document.getElementById('_openSettings_').addEventListener('click',()=>{
  729. gestureBox.style.cssText='overflow-y:hidden !important';
  730. document.getElementById('_settingsBox_').style.display='block';
  731. let settingList=document.getElementById('_settingList_');
  732. settingList.textContent='';
  733. for(let Ti in settings){
  734. settingList.innerHTML+='<p>'+Ti+':</p>';
  735. if(typeof(settings[Ti])==='boolean'){
  736. settingList.innerHTML+='<label class="_switch_"><input type="checkbox" id="'+Ti+'" '+((settings[Ti]) ? 'checked' : '')+'><div class="_slider_"></div></label>';
  737. }else if(typeof(settings[Ti])==='object'){
  738. settingList.innerHTML+='<div class="_slideRail_"><div class="_slideButton_" id="'+Ti+'"></div></div>';
  739. let slideButton=document.getElementById(Ti),
  740. leftPX=slideButton.parentNode.offsetWidth*(settings[Ti][0]-settings[Ti][1])/(settings[Ti][2]-settings[Ti][1])-slideButton.offsetWidth/2;
  741. slideButton.style.left=leftPX+'px';
  742. slideButton.textContent=settings[Ti][0].toFixed(settings[Ti][3]);
  743. }
  744. }
  745. let slideList=document.getElementsByClassName('_slideButton_');
  746. for(let Ti=0,len=slideList.length;Ti<len;++Ti){
  747. slideList[Ti].addEventListener('touchmove',silideBar,true);
  748. }
  749. },true);
  750. //保存功能开关设置
  751. document.getElementById('_saveSettings_').addEventListener('click',()=>{
  752. gestureBox.style.cssText='';
  753. for(let Ti in settings){
  754. if(typeof(settings[Ti])==='boolean'){
  755. settings[Ti]=document.getElementById(Ti).checked;
  756. }else if(typeof(settings[Ti])==='object'){
  757. settings[Ti][0]=+document.getElementById(Ti).textContent;
  758. }
  759. }
  760. GM_setValue('settings',settings);
  761. document.getElementById('_settingsBox_').style.display='none';
  762. },true);
  763. }
  764.  
  765. /*事件注册模块*/
  766. (function(){
  767. if(top===self){
  768. //清除后退定时器
  769. window.addEventListener('popstate',()=>{clearTimeout(gestureData.backTimer);gestureData.backTimer=0;},true);
  770. window.addEventListener('beforeunload',()=>{clearTimeout(gestureData.backTimer);gestureData.backTimer=0;},true);
  771. //接收iframe数据
  772. window.addEventListener('message',async (e)=>{
  773. let data=e.data;
  774. switch(data.type){
  775. case 'GYRO':{//锁定横屏模式
  776. await screen.orientation.lock('landscape')?.catch(Date);
  777. break;}
  778. case 'forceFullScreen':{//iframe强制可全屏
  779. for(let Ti=0,len=iframeEles.length;Ti<len;++Ti){
  780. if(iframeEles[Ti].contentWindow===e.source){
  781. if(!iframeEles[Ti].allowFullscreen){
  782. iframeEles[Ti].allowFullscreen=true;
  783. if(iframeEles[Ti].getAttribute('src') && regURL.test(iframeEles[Ti].src)){
  784. iframeEles[Ti].src=iframeEles[Ti].src;
  785. }
  786. }
  787. break;
  788. }
  789. }
  790. break;}
  791. case 'runPath':{//iframe手势在顶级页面执行
  792. for(let Ti=0,len=iframeEles.length;Ti<len;++Ti){
  793. if(iframeEles[Ti].contentWindow===e.source){
  794. let ifrRect=iframeEles[Ti].getBoundingClientRect();
  795. gestureData.touchStart=data.gestureData.touchStart;gestureData.touchEnd=data.gestureData.touchEnd;
  796. gestureData.touchStart.target=gestureData.touchEnd.target=gestureData.touchEle=iframeEles[Ti];
  797. gestureData.touchStart.pageX=gestureData.touchStart.clientX+=ifrRect.x;
  798. gestureData.touchStart.pageY=gestureData.touchStart.clientY+=ifrRect.y;
  799. gestureData.touchEnd.pageX=gestureData.touchEnd.clientX+=ifrRect.x;
  800. gestureData.touchEnd.pageY=gestureData.touchEnd.clientY+=ifrRect.y;
  801. break;
  802. }
  803. }
  804. path=data.runPath;setTimeout(runGesture);
  805. break;}
  806. case 'pushTouch':{//iframe手势坐标传递
  807. let ifrRect=gestureData.touchEle.getBoundingClientRect();
  808. gestureData.touchEnd=data.gestureData.touchEnd;
  809. gestureData.touchEnd.target=gestureData.touchEle;
  810. gestureData.touchEnd.pageX=gestureData.touchEnd.clientX+=ifrRect.x;
  811. gestureData.touchEnd.pageY=gestureData.touchEnd.clientY+=ifrRect.y;
  812. break;}
  813. case 'download':{//iframe视频下载
  814. window._downloadVideo_(data);
  815. break;}
  816. }
  817. },true);
  818. }else{
  819. //iframe视频全屏
  820. GM_addValueChangeListener('fullscreen',async (name,old_value,new_value,remote)=>{
  821. if(!document.hidden && window._isShow_){
  822. await findVideoBox()?.requestFullscreen()?.catch(Date);
  823. }
  824. });
  825. //iframe锁定
  826. GM_addValueChangeListener('isShow',(name,old_value,new_value,remote)=>{
  827. if(!document.hidden){window._isShow_=!remote;}
  828. });
  829. }
  830. //加载检测
  831. checkTimer=setTimeout(loadCheck,200);
  832. mObserver.observe(document,{childList:true,subtree:true});
  833. //手势事件注册
  834. window.addEventListener('touchstart',touchStart,{capture:true,passive:false});
  835. window.addEventListener('touchmove',touchMove,{capture:true,passive:true});
  836. window.addEventListener('touchend',touchEnd,{capture:true,passive:false});
  837. window.addEventListener('touchcancel',touchEnd,{capture:true,passive:false});
  838. window.addEventListener('contextmenu',(e)=>{if(path.indexOf("I")>-1 && gestureData.touchEle.src!==location.href){e.preventDefault();}},true);//长按图片时禁止弹出菜单
  839. if(settings['避免断触']){window.addEventListener('click',delayClick,true);}
  840. //禁止网页检测焦点
  841. window.addEventListener('visibilitychange',(e)=>{e.stopImmediatePropagation();//页面切换检测
  842. if(document.hidden){//视频后台播放
  843. let playState=videoPlayer?.paused,playTime=videoPlayer?.currentTime+0.3,playSpeed=videoPlayer?.playbackRate,playVolume=videoPlayer?.volume;
  844. setTimeout(()=>{if(playState!==videoPlayer?.paused){
  845. videoPlayer.src=videoPlayer.src;videoPlayer.currentTime=playTime;
  846. videoPlayer.oncanplay=()=>{videoPlayer.play();videoPlayer.playbackRate=playSpeed;videoPlayer.volume=playVolume;}
  847. }});
  848. }
  849. },true);
  850. window.addEventListener('pagehide',(e)=>{e.stopImmediatePropagation();},true);
  851. window.addEventListener('blur',(e)=>{e.stopImmediatePropagation();});
  852. //禁止网页修改复制内容
  853. window.addEventListener('copy',(e)=>{if(allowCopy){allowCopy=0;return;}e.stopImmediatePropagation();e.stopPropagation();},true);
  854. //禁止网页写入剪切板
  855. document.execCommand=()=>{};
  856. if(navigator.clipboard){
  857. navigator.clipboard.writeText=()=>{};
  858. navigator.clipboard.write=()=>{};
  859. }
  860. //解除选中限制
  861. addStyle('*{user-select:text !important;touch-action:manipulation;overscroll-behavior-x:none;}');
  862. })();