// ==UserScript== // @name gh-proxy-buttons // @name:zh-CN github加速按钮 // @namespace https://github.com/du33169/gh-proxy-buttons // @version 0.7 // @license MPL-2.0 // @require https://cdn.bootcdn.net/ajax/libs/clipboard.js/2.0.6/clipboard.min.js // @description add a button beside github link(releases,files and repository url), click to get alternative url according to previously specified proxy. // @description:zh-CN 为github中的特定链接(releases、文件、项目地址)添加一个悬浮按钮,提供代理后的加速链接 // @author du33169 // @match *://github.com/* // @grant none // @run-at document-end // ==/UserScript== (function() { 'use strict'; //--------------------------代理设置------------------------------- //用于在线代理的workers地址 const proxy_url= 'https://gh-proxy.du33169.workers.dev/'; /* 备用: 'https://gh.api.99988866.xyz/'; (来自gh-proxy项目作者) 代理服务器地址可自行修改,末尾斜杠不可省略! */ //--------------------------其他设置------------------------------ //是否打开调试输出 const open_log=true; //鼠标离开链接或按钮后消失前等待的时间 const fade_timeout=100;//ms //--------------------------功能代码------------------------------ function isRepoFile(ourTarget){ return (ourTarget.tagName=='A' &&ourTarget.getAttribute('aria-label')!=null//利用&&短路特性防止没有属性的元素导致脚本终止 &&ourTarget.classList.contains("Link--primary") &&ourTarget.getAttribute('aria-label').endsWith('(File)') &&ourTarget.closest('#js-repo-pjax-container')!=null ); } function isReleaseAsset(ourTarget){ return (ourTarget.tagName=='A' &&ourTarget.getAttribute('rel')!=null &&ourTarget.rel=="nofollow" //&&/github.com/.test(ourTarget.href)==true &&(ourTarget.closest('#repo-content-pjax-container')!=null)||(ourTarget.closest('.js-truncated-assets-fragment')!=null) ); } function isDownloadZip(e){ return (e.tagName=='A' &&e.classList.contains("kHKEGN") &&e.getAttribute('rel')!=null &&e.rel=="nofollow" &&e.getAttribute('role')!=null &&e.role=="menuitem" ); } function isHttpCopyGit(e){ return ( e.tagName=='BUTTON' &&e.classList.contains('hUTZcL') &&e.previousElementSibling!=null &&e.previousElementSibling.tagName=='INPUT' &&e.previousElementSibling.value.startsWith('https') ); } function getBtn(originLink,filename,copy=false) { var ghBtn=document.getElementById("gh-proxy-button"); //existed if(!ghBtn){ //not existed, create instance ghBtn=document.createElement('a'); ghBtn.setAttribute('class','btn'); ghBtn.id="gh-proxy-button"; ghBtn.title="[gh-proxy-buttons] get proxy link"; ghBtn.style.position="absolute"; ghBtn.role="button"; ghBtn.style.zIndex=9999; //ghBtn.style.top=0;//must be set for transition //ghBtn.style.left=0; //ghBtn.style.transition="transform 0.5s ease-in-out"; ghBtn.addEventListener('mouseenter',function(){ if(open_log)console.debug('[gh-proxy-buttons] onbtn'); if(ghBtn.timerId !=undefined && ghBtn.timerId!=-1){ clearTimeout(ghBtn.timerId); ghBtn.timerId=-1; } }); ghBtn.addEventListener('mouseleave',function(){ if(open_log)console.debug('[gh-proxy-buttons] mouseleave-btn'); if(ghBtn.timerId !=undefined && ghBtn.timerId!=-1)return; ghBtn.timerId=setTimeout(function(){ ghBtn.style.visibility="hidden"; ghBtn.timerId=-1; if(open_log)console.debug('[gh-proxy-buttons] timeout fade mouseleave-btn'); },fade_timeout); if(open_log)console.debug('[gh-proxy-buttons] btn leave timerid:',ghBtn.timerId); }); document.documentElement.appendChild(ghBtn); } //now gh-proxy-button exists if(copy)//仓库地址input标签特殊处理,使用ClipboardJS实现点击复制 { ghBtn.removeAttribute('target'); ghBtn.removeAttribute('download'); ghBtn.href="javascript:void(0)"; ghBtn.innerText="🚀📄"; ghBtn.clip=new ClipboardJS(ghBtn); ghBtn.clip.on('success',function(){ ghBtn.innerText="🚀📄✔"; }); ghBtn.setAttribute('data-clipboard-text',proxy_url+originLink); //console.log('[gh-proxy-buttons] input url processed'); } else{ ghBtn.innerText="🚀"; ghBtn.target="_blank"; if(ghBtn.clip)ghBtn.clip.destroy(); ghBtn.clip=undefined; ghBtn.download=filename; ghBtn.removeAttribute('data-clipboard-text'); ghBtn.href=proxy_url+originLink; } return ghBtn; } console.log('[gh-proxy-buttons] processing...'); function moveHere(e,originLink,copy=false)//用于注册mouseenter事件,e为当前元素 { //创建按钮对象,github中使用.btn的class可以为标签加上按钮外观 var btn=getBtn(originLink,e.title,copy); if(open_log)console.debug("[gh-proxy-buttons]",btn); //e.parentElement.insertBefore(btn,e); if(btn.timerId !=undefined && btn.timerId!=-1){ clearTimeout(btn.timerId); btn.timerId=-1; } const rect = e.getBoundingClientRect(); const btnRect=btn.getBoundingClientRect(); const x = rect.left + window.scrollX - btnRect.width; const y = rect.top + window.scrollY; console.log(`Element coordinates (relative to document): x: ${x}, y: ${y}`); btn.style.left=`${x}px`; btn.style.top =`${y}px`; if(btn.style.visibility=="visible"){ //btn.style.transform = `translate(${event.x}px, ${event.y}px)`; } else{ //btn.style.transform=""; btn.style.visibility="visible"; } if(open_log)console.debug(`[gh-proxy-buttons] mousein, move btn to ${event.x},${event.y}`); e.addEventListener('mouseleave',function(){ if(open_log)console.debug('[gh-proxy-buttons] mouseleave-target'); if(btn.timerId !=undefined && btn.timerId!=-1)return; btn.timerId=setTimeout(function(){ btn.style.visibility="hidden"; btn.timerId=-1; if(open_log)console.debug('[gh-proxy-buttons] timeout fade mouseleave-target'); },fade_timeout); if(open_log)console.debug('[gh-proxy-buttons] target leave timerid:',btn.timerId); }); } function eventDelegation(e) { // e.target 是事件触发的元素 if(e.target!=null) { var ourTarget=e.target; while(ourTarget!=e.currentTarget&&ourTarget.tagName!='A'&&ourTarget.tagName!='BUTTON')//releases页面触发元素为内的span,需要上浮寻找 { ourTarget=ourTarget.parentNode; } if(ourTarget==e.currentTarget)return; //if(open_log)console.debug('[gh-proxy-buttons]','found A',ourTarget); if(isRepoFile(ourTarget)||isReleaseAsset(ourTarget)||isDownloadZip(ourTarget)) { console.log('[gh-proxy-buttons] ','found target',ourTarget); moveHere(ourTarget,ourTarget.href); }else if(isHttpCopyGit(ourTarget)){ console.log('[gh-proxy-buttons] ','found target copy button',ourTarget); moveHere(ourTarget,ourTarget.previousElementSibling.value,true); } } } document.body.addEventListener("mouseover", eventDelegation); })();