PincongFilter (20190506完成基本功能)

不正经的简介:
你有说话放屁的自由,我就有掩耳捂鼻的权力。



正经的简介:
Depend on Firefox at least 52esr and Greasemonkey.
过滤来自特定id的问题、文章、回答及评论,对匿名用户无效。
同时对于未被过滤的非匿名用户,在其用户名或头像旁显示该用户的id。
目前只能通过修改代码中特定变量的方法添加所需过滤的id。

本来打算放在pastebin的,想想还是算了……
不接受来自浏览器兼容性的bug report。

20190430更新:
过滤文章的评论。
修复额外的显示用户id的行为。

20190506更新:
显示过滤计数。
过滤列表修改/导入/导出。


P.S. 但是无法过滤文章的评论,因为这些评论的用户信息都没有data-id。这个要看网站后台的心情了。 已修复
P.S. 当然网站后台想要封杀这个脚本也非常容易。这个也看网站后台的心情了。


// ==UserScript==
// @name        PincongFilter
// @namespace   PincongFilter
// @match       https://pincong.rocks/*
// @version     1
// @grant       none
// @run-at      document-start
// ==/UserScript==

(function(){
    let doc=document;
    let html=doc.documentElement;
    let map=function(f,i){for(let n of i)f(n);}
    let newtag=function(t){return doc.createElement(t);}
    let newtext=function(s){return doc.createTextNode(s);}
    let addev=function(n,e,f){return n.addEventListener(e,f);}
    let selid=function(i){return doc.getElementById(i);}
    let seltag=function(t){return doc.getElementsByTagName(t);}
    let selcss=function(c,r){return (r?r:doc).querySelectorAll(c);}
    let selcss1=function(c,r){return (r?r:doc).querySelector(c);}
    let hasattr=function(n,a){return n.hasAttribute(a);}
    let getattr=function(n,a){return n.getAttribute(a);}
    let setattr=function(n,a,v){n.setAttribute(a,v);}
    let delattr=function(n,a){if(hasattr(n,a))n.removeAttribute(a);}
    let appendnode=function(r,n){n?r.appendChild(n):null;}
    let delnode=function(n){n?n.remove():null;}

    let watch=function(n,p,f){
        let config={};
        let leastprops=['childList','attributes','characterData'];
        let validconfig=false;
        let watcher=new MutationObserver(f);
        map(n=>config[n]=true,p);
        for(let prop of leastprops)
            validconfig=validconfig||config[prop];
        if(!validconfig)config[leastprops[0]]=true;
        watcher.observe(n,config);
        addev(window,'beforeunload',()=>watcher.disconnect());
    }

    let hiddenstart=function(r,f){
        r.hidden=true;
        addev(doc,'DOMContentLoaded',()=>{f();r.hidden=false;});
    }

    let clicknode=function(n){
        let e=document.createEvent('MouseEvents');
        e.initEvent('click',true,true);
        n.dispatchEvent(e);
    };

    let cmpstr=function(a,b){
        let lencmp=a.length-b.length;
        if(lencmp===0)
            return a<b?-1:1;
        return lencmp;
    }

    let synclist=function(src,dst){
        dst.length=0;
        for(let item of src){
            if(dst.includes(item))continue;
            if(item.length===0)continue;
            dst.push(item)
        }
        dst.sort(cmpstr);
    }



    // question or article
    let question_selector='div.aw-common-list>div.aw-item';
    // answer for question or comment for article
    let answer_selector='div.aw-item[id^="answer_list_"]';
    // comment for answer or comment
    let comment_selector='div.aw-comment-box>div.aw-comment-list>ul>li';
    // prefix of user path
    let user_prefix='/people/';
    // blacklist of userid as string
    let blacklist=[];
    // static blacklist of userid, not visiable in UI
    let staticlist=[];

    let counter=0;

    let filter_answer=function(node){
        let usernode=selcss1('div.mod-head a.aw-user-name[data-id]',node);
        // only do on non-anonymous user
        if(usernode){
            let userid=getattr(usernode,'data-id');
            if(staticlist.includes(userid)||blacklist.includes(userid)){
                console.debug('filter answer:',usernode.textContent);
                delnode(node);
                counter+=1;
                showcounter();
                return;
            }
            usernode.textContent+=' ('+userid+')';
        }
        watch(node,['subtree'],commentfilter);
    }

    let filter_question=function(node){
        let usernode=selcss1('a.aw-user-name[data-id]:first-child',node);
        // anonymous user, do nothing
        if(!usernode)return;
        let userid=getattr(usernode,'data-id');
        if(staticlist.includes(userid)||blacklist.includes(userid)){
            console.debug('filter question:',userid);
            delnode(node);
            counter+=1;
            showcounter();
            return;
        }
        // anonymous user, do nothing
        if(userid.length===0)return;
        let p=newtag('p');
        appendnode(p,newtext(userid));
        appendnode(usernode,p);
    }

    let filter_comment=function(node){
        let usernode=selcss1('a.aw-user-name.author[data-id]:not([data--])',node);
        // anonymous user, do nothing
        if(!usernode)return;
        let userid=getattr(usernode,'data-id');
        if(staticlist.includes(userid)||blacklist.includes(userid)){
            console.debug('filter comment:',usernode.textContent);
            delnode(node);
            counter+=1;
            showcounter();
            return;
        }
        usernode.textContent+=' ('+userid+')';
        setattr(usernode,'data--','');
    }

    let commentfilter=function(){
        map(filter_comment,selcss(comment_selector))
    }

    let loadblacklist=function(list){
        try{
            let _str=localStorage.getItem('PincongFilter');
            if(_str===null)_str=JSON.stringify({'filter':[]});
            let _data=JSON.parse(_str);
            synclist(_data['filter'],list);
        }catch(e){
            console.error('error:',e);
        }
    }

    let saveblacklist=function(list){
        try{
            let _data={'filter':[]}
            let _str=selid('PincongFilter-blacklist').value;
            synclist(_str.split('\n'),_data['filter']);
            localStorage.setItem('PincongFilter',JSON.stringify(_data));
            loadblacklist(list);
        }catch(e){
            console.error('error:',e);
        }
    }

    let impblacklist=function(ev){
        let finput=newtag('input');
        finput.type='file';
        finput.accept='.json';
        finput.multiple=false;
        finput.oninput=function(iev){
            if(iev.target.files.length<1)return;
            let reader=new FileReader();
            reader.onload=function(rev){
                try{
                    let _data={'filter':[]};
                    let json=JSON.parse(rev.target.result);
                    let area=selid('PincongFilter-blacklist');
                    synclist(json['filter'],_data['filter']);
                    area.value=_data['filter'].join('\n');
                    saveblacklist(blacklist);
                }catch(e){
                    console.error('error:',e)
                }
            };
            for(let file of iev.target.files)
                reader.readAsText(file);
        }
        clicknode(finput);
    }

    let expblacklist=function(ev){
        let data=new Blob([localStorage.getItem('PincongFilter')],
                          {type:'application/json'})
        let url=URL.createObjectURL(data);
        let a=newtag('a');
        a.href=url;
        a.download='PincongFilter.json';
        clicknode(a);
    }

    let changeblacklist=function(ev){
        saveblacklist(blacklist);
    }

    let createui=function(){
        let parent=selcss1('nav>ul.nav');
        if(!parent)return;

        let root=newtag('li');
        let a=newtag('a');
        let con=newtag('span');
        let div=newtag('div');
        let area=newtag('textarea');
        let btndiv=newtag('div')
        let impbtn=newtag('button');
        let expbtn=newtag('button');
        appendnode(parent,root);
        appendnode(root,a);
        appendnode(root,div);
        appendnode(a,newtext('PincongFilter'));
        appendnode(a,con);
        appendnode(div,area);
        appendnode(div,btndiv);
        appendnode(btndiv,impbtn);
        appendnode(btndiv,expbtn);
        appendnode(impbtn,newtext('Import'));
        appendnode(expbtn,newtext('Export'));

        a.href='javascript:;';
        con.id='PincongFilter-counter';
        div.classList.add('aw-dropdown','pull-right','hidden-xs');

        area.id='PincongFilter-blacklist';
        area.placeholder='One user id per line.';
        area.rows=Math.max(blacklist.length+1,1);
        if(blacklist.length)
            area.cols=blacklist[blacklist.length-1].length;
        area.value=blacklist.join('\n');
        addev(area,'input',changeblacklist);

        btndiv.style.cssFloat='right';

        addev(impbtn,'click',impblacklist);
        addev(expbtn,'click',expblacklist);
    }

    let showcounter=function(){
        if(counter){
            let span=selid('PincongFilter-counter');
            span.textContent=[' (',counter,')'].join('');
        }
    }

    let mainfilter=function(){
        loadblacklist(blacklist);
        createui();
        map(filter_answer,selcss(answer_selector));
        map(filter_question,selcss(question_selector));
    }

    hiddenstart(html,mainfilter);
})()
8
分享 2019-04-27

25 个评论

不错不错,有前途
我已经成功大脑升级成NLP魔人力,,,你们这些垃圾JS脚本不过是我玩剩下的,已经没有利用价值了
在这里的这个身份不会直接出现在其他地方。
大手子炫技大赛开始了?
一直没搞懂怎么在TorBrowser里加脚本,能否提供一个简易教程?
试了一下,加到bookmark了,但点了毫无反应啊,能否在telegra.ph写个教程?
匿名用户 回复 looose 新注册用户
你这个也没用啊,是不是打开姿势不对?
测试了一下,题主的脚本能显示用户的数字id,但屏蔽功能不好用呢。
亲测可用,比@looose 的略方便
就爱你们这样顽皮的小孩 @komorebi @looose
因催斯听
原来大手子的脚本就是靠看用户ID来判断是否是连续注册的小号
以前还有点踩来判断匿名,可惜现在这个漏洞被堵上了
这样看来还是转世党比较安全呢

大手子要不画一个品葱ID活跃的时间线?
我们品葱大手子的思路和编程能力比国营的@looose 还是要强很多滴
大手子我为你骄傲!!
@looose 站长 你的@情报员101 们有没有查出结果来啊?@BE4 这么无耻下流道德败坏注册无数小号到处引战的人渣败类,不但诬陷了全体中国人,还诬陷小站是共青团的蜜罐,现在Google搜索‘开放区小站’头几条全是骂小站的黑屁文呀,这岂有此理!这不共戴天之仇,这还没查出@BE4 的大号岂可堰气息股、三八干修呢?
主要是不能移动平台,pc平台脚本有,但主力一直是用手机。。
Greasemonkey对android的支持要求firefox 57.0以上。
品葱官方可以考虑加入这个功能 @admin
可以保存在<script>直接加到<head>,但是不适合。
这个脚本保存过滤列表的方案有两个:
1,作为JSON保存在localStorage;
2,直接修改脚本内部的staticlist。

方案2是为了使脚本在浏览器拒绝网站数据时依然能够工作。
如果这个脚本被官方嵌入,那么用户当然无法更改脚本内容,方案2就无法工作,那么这个脚本就要求用户必须接受网站数据。
这是网站官方无法解决的问题,因为浏览器的隐私设置就是为此设计。
配置文件在本地的确不太合适放到官方功能
白名单是否方便?比如只看威望30以上发文或回复?
1:不管是什么方式的过滤,只要是需要访问者配置启用,都会遇到浏览器隐私设置的问题;
2:目前官方已经有折叠、封禁等功能来隐藏特定内容;
3:官方默认启用大规模非特定过滤违背了网站的建设初衷。
官方不启用过滤,用户自己用类似脚本在本地设置白名单。
理论上是可行的,但实作效果不好。
能够在页面上直接获取的用户属性只有用户名和id,以这两个属性设置白名单显然不适合。
而其他属性则需要产生额外的网络访问,并且是每一个id发送一次,那么极端的情况是每页100次,对访问者的代价太大。
品葱官方如果允许用户导出自己的关注列表或者条件过滤器筛选出的列表到本地,然后就可以用白名单脚本看了。其实有点类似于‘动态’功能。
关注列表的API是有的。
https://pincong.rocks/people/ajax/follows/?type=follows&uid={uid}

这个请求返回的是html的<li>,只有用户的用户名所以还要再请求一次来获取id,不过这个列表并不需要频繁更新所以问题不大。

不管是黑名单还是白名单,关键在于过滤规则是不是特定的。如果是非特定的,比如以用户的威望,那么在用户端这个规则就需要在每次执行时对数据进行更新。用户名也是一样,因为用户名是可更改的。
所以我在这个脚本中采用了对id进行判断的策略。

非特定过滤规则在服务器端的确很容易实现,但个人认为网站方不应这么做。
网站本身对于其托管的内容应当保持一定的中立。可以对于违反规则的用户或内容进行关闭,但除此之外的关闭行为就不合适了。
威望倒不用更新得很实时,一周/月一次都可以,导出id列表作为本地白名单,方便浏览关注对象的内容,而且这个白名单被关注者是不知道的,这样反而可以一定程度避免出现明星用户带节奏的情况,因为他不知道自己受到多少人关注。

要发言请先登录注册

要发言请先登录注册

发起人

旧品,id已改,反正就是渣渣。

状态

  • 最新活动: 2019-05-17
  • 浏览: 4571