视频兼容修复。查看用户的永久链接。
// ==UserScript== // @name 我的推特工具箱 // @namespace https://greasyfork.org/users/159546 // @version 1.0.5 // @description 视频兼容修复。查看用户的永久链接。 // @author LEORChn // @include *://twitter.com/* // @run-at document-start // @grant GM_xmlhttpRequest // @connect download-twitter-videos.com // ==/UserScript== var IntervalTime = 2000; function onIntervalFunction(){ //doAddEntry(); // 隐藏转推。不稳定,如有需要请自行取消注释 //doHideRetweet(); // 隐藏转推。不稳定,如有需要请自行取消注释 doAddVideoEntry(); doAddUidViewer(); // 用户资料:永久链接 adaptDoublePhotoHeight(); doFixImageView(); } var ID_HIDE_RETWEET_ENTRY = 'leorchn_action_hide_retweet', HIDE_RETWEET_ENABLED = false, HIDE_RETWEET_ENABLED_V2019 = false; function doAddEntry(){ if(fv(ID_HIDE_RETWEET_ENTRY)) return; var bar = $('.ProfileHeading-toggle'); var li = ct('li'), a = ct('a', '隐藏转推'); if(bar){ // 新布局,登录后强制启用的那种(不知道怎么返回旧布局,新布局真的用着太难过了) li.className='ProfileHeading-toggleItem u-textUserColor'; a.className='ProfileHeading-toggleLink js-nav'; a.onclick=function(){ this.parentNode.className='ProfileHeading-toggleItem is-active'; this.outerHTML=this.innerText; HIDE_RETWEET_ENABLED = true; }; li.appendChild(a); }else{ // 旧布局 bar = $('[aria-hidden]+div[role=tablist]'); if(!bar) return; // 真的不知道是什么布局了,return li = bar.lastElementChild.cloneNode(true); a = li.lastElementChild; a.removeAttribute('href'); a.onclick=function(){ this.lastElementChild.style.color = 'rgb(29, 161, 242)'; HIDE_RETWEET_ENABLED = HIDE_RETWEET_ENABLED_V2019 = true; }; var span = li; while(span.lastElementChild != null) span = span.lastElementChild; span.innerText = '隐藏转推'; } li.id = ID_HIDE_RETWEET_ENTRY; bar.appendChild(li); } function doHideRetweet(){ if( ! HIDE_RETWEET_ENABLED ) return; try{ var retweets = fc('js-retweet-text'); while(retweets.length > 0)retweets[0].parentNode.parentNode.parentNode.parentNode.remove(); }catch(e){ pl(e); } if( ! HIDE_RETWEET_ENABLED_V2019) return; var tweetRoot, tweetRoot2, retweets19 = $$('article:not([isHideRetweet])'), alignH, retweetMarkRoot; try{ for(var i=0; i<retweets19.length; i++){ alignH = retweets19[i].firstElementChild; retweetMarkRoot = alignH.firstElementChild; if(retweetMarkRoot.innerText.includes('转推了')){ tweetRoot2 = retweets19[i].parentNode; tweetRoot = tweetRoot2.parentNode; retweets19[i].setAttribute('isHideRetweet', '1'); var hidehint = ct('div', '隐藏了一条转推。'); hidehint.style.height = tweetRoot2.offsetHeight + 'px'; hidehint.className = 'LEORChn_HIDE_RETWEET_HINT'; tweetRoot2.style.display = 'none'; tweetRoot.appendChild(hidehint); } pl('hide retweet is running'); } }catch(e){ pl(e); pl(retweets19); } } //----- function doAddVideoEntry(){ var r=$('.PlayableMedia--video:only-child'), pageType = 0; if(!r){ r = $('video:not([blur]):not([hasvideoproxy])'); pageType++; } if(!r) return; var v=ct('video'), a=ct('a', '视频无法播放?点此解决'); if(pageType == 0){ r=r.parentNode; }else if(pageType == 1){ var rootPadding = r.parentElement, videoSlot, videoSlotPadding, videoHolderRoot; videoSlot = videoSlotPadding = videoHolderRoot = null; while(rootPadding.children.length <= 3){ videoHolderRoot = videoSlotPadding; videoSlotPadding = videoSlot; videoSlot = rootPadding; rootPadding = rootPadding.parentElement; } v.setAttribute('blur', 'v2'); var tweetsURL = getTweetsURL(videoHolderRoot); a.setAttribute('data-permalink-path', tweetsURL);// 适配旧版代码 a.href = '/'+tweetsURL; // 鼠标放在文字上时,浏览器左下角显示原帖地址(但是并不是用于点击后跳转,所以在下文的onclick里return false) r.setAttribute('hasvideoproxy', '1'); r=videoSlotPadding;//.appendChild(a); // 适配旧版代码 } a.style.color = 'rgb(27, 149, 224)'; a.style.cursor = 'help'; v.style.cssText='position:absolute; width:100%; height:calc(100% - 20px); top:0; display:none'; r.appendChild(v); r.appendChild(a); a.onclick=function(){ if(v.getAttribute('blur') == 'v2') v.parentElement.children[0].style.cssText = '-webkit-filter:blur(20px); filter:blur(20px)'; v.style.display=''; var curTweet, curNode = this; while(!curNode.hasAttribute('data-permalink-path') && curNode != document.body) curNode = curNode.parentNode; if(curNode == document.body) return; curTweet = encodeURIComponent('https://twitter.com/'+curNode.getAttribute('data-permalink-path')); /* - more tools: https://download-twitter-videos.com/ https://mydowndown.com/twitter */ http2('post', 'https://download-twitter-videos.com/zh/core/ajax.php', 'host=twitter&url='+curTweet, function(){ var res=this.responseText, reg=new RegExp('http[^>]*\\.mp4[0-9a-zA-Z\?=%]{0,}','g'), rsl=reg.exec(res); pl(res); pl(rsl); v.autoplay = v.loop = v.controls = true; v.src = rsl[0]; v.style.height = '100%'; v.volume = 0.6; }); return false; // 鼠标放在文字上时,浏览器左下角显示原帖地址(但是并不是用于点击后跳转,所以return false) } v.onplay = function(){ if(a) a.remove(); } v.onclick = function(e){ if(v.duration == 0) return; if(v.paused) v.play(); else v.pause(); e.stopPropagation(); } } function getTweetsURL(element){ // 获取当前视频元素所在的帖子的真实URL var url = null, testcase = /[^\/]*?\/status\/[0-9]*/; // 一个用于测试是否是具体帖子URL的用例。【[用户ID]/status/[帖子ID_数字]】 var testReplyRootUrl = (function(e){ /* 已测试: 进入一个回复帖页面,原帖中包含的视频 https://twitter.com/LEORChn/status/1230184640842829825 进入一个主题帖页面,原帖和回复帖均包含视频 https://twitter.com/Chisen_Lupus/status/1229804782589706240 进入一个帐号的空间页面,切换到“媒体”选项卡之后看到的每一个视频 */ var links = e.parentNode.parentNode.parentNode.firstElementChild.querySelectorAll('a'); for(var i=0; i<links.length; i++) if(testcase.test(links[i].href)) return testcase.exec(links[i])[0]; })(element); if(testReplyRootUrl) return testReplyRootUrl; return testcase.exec(location.pathname)[0]; } //----- 在用户资料页面添加一个视图以查看他的永久链接 var ID_USER_FOREVER_UID = 'leorchn_user_id'; function doAddUidViewer(){ if(fv(ID_USER_FOREVER_UID)) return; var followbtn = $('main a+div a+div [data-testid*=follow]'); // 定位到资料页面本人的关注按钮,而不是其他的关注按钮 if(followbtn == null){ // 自己的资料页面 followbtn = $('a[href^="/settings/profile"]'); if(followbtn == null){ pl('永久链接显示失败,可能是功能已失效。'); return; } var my_uid_testcase = /\"user_id\".*?(\d+)/, my_uid_source = $('script[nonce]').innerText, my_uid = my_uid_testcase.exec(my_uid_source)[1]; followbtn.setAttribute('data-testid', my_uid); followbtn.parentNode.style.display = 'block'; // 不加这个会导致气泡与“编辑个人资料”按钮重叠在一起 } var a = ct('a'), coret = ct('span'), text = ct('span', '用户的永久链接'); a.style.cssText = 'position:absolute; right:0; width:120px; padding:5px; margin-top:10px; border:#1da1f2 solid 1px; border-radius:10px; text-align:center; color:#1da1f2; text-decoration:none'; coret.style.cssText = 'position:absolute; right:25px; width:10px; height:10px; top:-6px; border:#1da1f2 solid 1px; border-bottom:none; border-right:none; transform:rotate(45deg); background-color:#fff;'; a.href = '/i/user/' + /\d*/.exec(followbtn.getAttribute('data-testid'))[0]; a.target = '_blank'; a.id = ID_USER_FOREVER_UID; a.appendChild(coret); a.appendChild(text); followbtn.parentNode.appendChild(a); var tmpView = followbtn, tmpView2; while(true){ // 把名称 margin 一下以免挡到链接点击 tmpView2 = tmpView; tmpView = tmpView.parentNode; if(document.body == tmpView) break; var prev = tmpView.previousElementSibling if(prev == null) continue; //if(prev.tagName.toLowerCase() != 'a') continue; // 这个在自己的资料页面会测试失败 if(tmpView.children.length < 3) continue; if( ! tmpView.innerText.includes('@')) continue; tmpView2.nextElementSibling.style.marginRight = '130px'; break; } } //----- function adaptDoublePhotoHeight(){ var a = $('.permalink-container .AdaptiveMedia'); if(a) a.style.maxHeight = '500%'; // 重置设置,突破界限 a = $$('.permalink-container .AdaptiveMedia-doublePhoto'); for(var i=0;i<a.length;i++){ a[i].style.lineHeight = 0; // 修改后的双图底部会多出一点点边框很难看 a[i].style.height = '100%'; // 重置掉原先固定的大小 } a = $$('.permalink-container .AdaptiveMedia-doublePhoto img'); for(var b=0;b<a.length;b++){ a[b].style.top = a[b].style.left = 0; // 重置 a[b].style.height = ''; // 重置固定高度 a[b].style.position = 'static'; // 确保图片保持在边框里面 a[b].style.maxWidth = '100%'; // 限制双图宽度(横向扩展度) } } function doFixImageView(){ var t = $$('div[aria-label=上一个]'); if(t.lengh == 0) return; for(var i=0; i<t.length; i++){ t[i].parentElement.style.position = 'static'; } } //----- (function(){ setInterval(onIntervalFunction, IntervalTime); })(); //----- my ezjs lib function fv(id){return document.getElementById(id);} function ft(tag){return document.getElementsByTagName(tag);} function fc(cname){return document.getElementsByClassName(cname);} function ct(tag, t){var d=document.createElement(tag); if(t)d.innerText=t; return d;} function $(s){return document.querySelector(s);} function $$(s){return document.querySelectorAll(s);} function msgbox(msg){alert(msg);} function inputbox(title,defalt){return prompt(title,defalt);} function pl(s){console.log(s);} function http(method,url,formed,dofun,dofail){ var x = new XMLHttpRequest(); if(location.protocol.includes('https')) url=url.replace('^http:','https:'); x.open(method.toUpperCase(),url,true); x.timeout=60000; x.responseType="text"; // IE要求先open才能设置timeout和responseType x.onload=dofun; x.ontimeout=x.onerror= dofail? dofail: null; x.send(formed?formed:''); } function getHeaders(){ return { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', // 如果没有这个,就会默认是 text/plain 并导致错误 cookie: '__cfduid=d507d0eb58ae7839d11def0ca83e943631553697169', referer: 'https://download-twitter-videos.com/' }; } function http2(_method,_url,formdata,dofun,dofail){ pl('request cross-site http:\n\n'+_method+' '+_url+'\nform: '+formdata+'\n\n.'); GM_xmlhttpRequest({ method: _method.toUpperCase(), url: _url, data: formdata, headers: getHeaders(), onload: dofun, onerror: dofail }); } function mkformdata(arr){ var f = new FormData(); for(var i in arr){ var s = arr[i].split('='); pl(s); f.append(s[0], decodeURIComponent(s[1])); } return f; }