Greasy Fork is available in English.

我的推特工具箱

视频兼容修复。查看用户的永久链接。

// ==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;
}