不適当なフィルターによる暴走を抑止する 

「リンクコード」フィルターで既に経験済でしたが、「テキスト」フィルターはよりタイムラインの暴走が生じる可能性が大です。「テキスト」フィルターの設定は自由度が高く、暴走を意識しないユーザーを窮地に追いやるかもしれません。

 

「リンクコード」フィルターは、そのコードが指すタイムラインを開いた時には無効になる様に、安全機能を追加しました。 しかし、テキストはそういうテクニックが使えないので、「暴走」自体を検知してストップをかける安全機能を制作しました。

 

 

安全機能のあらまし 

最初に作ったテストコードは、一定の時間内(3sec)にタイムラインの更新が行われた回数をカウントして、それが20回を超えると過剰な更新(暴走)と判定する方法でした。 この 3secの観測は、繰り返して行うものです。

 

これは一定の成果がありましたが、ユーザーのマウス操作、スクロールバーの操作、などで自然に生じるタイムラインの更新は、時には20回を超えます。 この許容回数を増やすと、ユーザーの処理に反応しない様に出来ますが、暴走に鈍感になります。 この仕切りが上手く行かず、安全機能としては不確実でした。

 

そこで、判定方法を改めました。

 

❶ タイムラインの更新ごとに20件程度のツイートが更新され、それらの表示・非表示を処理しますが、非表示のツイート、表示のツイートの件数をカウントする。

 

❷ 3secに渡って、非表示ツイート・表示ツイート を合計カウントする。

 

❸ 下の関数で、fuse値を計算して出力する。 fuse値は、一定の時間内で、非表示にした件数の全体件数に対する割合(100%で10、50%なら 5)です。

 

function fuse_value(c, un_c){
    if(c+un_c>50){ // 3secの処理件数 上限 🟦
        let av=Math.round(10*c/(c+un_c));
        return av; }
    else{
        return 0; }}

 

「c」は、非表示にした件数、「un_c」は表示した件数。 上の関数では、3sec以内の処理した全体件数が 50件以下の場合は「0」を出力させています。 これは、タイムラインのスクロールが緩やかで、新しいツイート呼込みが余りない状態です。

 

以上の観測を 3secごとに繰り返すと、ユーザーによる操作では、「fuse値」は 6を超える事は少なく、「暴走」の状態では一気に 8~10位に跳ね上がります。

 

この様に区別が付き易く出来たのは、暴走時は、タイムラインの更新が連続する事と、呼込まれたツイートの殆どが非表示に処理される、両方の傾向を観測する様にしたからです。

 

以下は、上の関数を組込んだ、安全機能のコードの本体です。

 

let count=0; // 非表示にした件数
let un_count=0; // 非表示にしなかった件数
let fuse=0; // 0 : フィルター処理 ON 1 : フィルター処理 OFF

s_meter();

function s_meter(){
    count=0;
    un_count=0;

    setTimeout(()=>{
        if(fuse_value(count, un_count)>6){ // 70%以上が非表示の場合 🟦
            fuse=1;
            let ok=confirm(
                '💢 OVERLOAD 💢\n'+
                '不適当なフィルターの設定で、通信過剰に陥っています\n'+
                '以下のどちらかの方法でフィルター設定を変更してください\n\n'+
                '❶ 「OK」➔ フィルター設定を変更する\n'+
                '❷ 「キャンセル」➔ タイムラインの表示に戻る');
            if(ok){
                disp_panel();
                mode=0;
                mode_lt=0;
                set_link('');
                s_meter(); }
            else{
                s_meter(); }}
        else{
            fuse=0;
            s_meter(); }
    }, 3000); } // 3secごとの集計 🟦

 

関数「s_meter()」は、起動して 3sec 後に「fuse値」を計算して、それが 6より大きいと、処理停止フラグ「fuse」を 1にしてフィルター処理を停止します。

 

「fuse値」が 6以下の場合は、自らの「s_meter()」を起動するので、この安全機能は 3secごとに監視動作を繰り返します。

 

 

安全機能が動作した場合 

安全機能が「暴走」と判断すると、下のアラートが出ます。

 

▪「暴走」の判断は、時にはデリケート過ぎると感じるかも知れません。「キャンセル」で無視をして、そのままタイムラインを閲覧できる場合があります。「暴走」状態にならないなら、それで構いません。

 

 

 

❶「OK」を押すと、「リストパネル」が表示されます。 

 

▪ 考えられる「暴走」の原因と思われる不適当な「フィルター」を削除します。

 

▪ ツイッターの画面を少しスクロールすると、再びアラートが出ます。

 

❷「キャンセル」を押すと、フィルター動作は一旦停止した状態で、元のタイムラインの画面に戻れます。 スクロールをしない間は、次のツイート呼込みが起こらないので、フィルターの変更ができます。

 

▪ フィルターに問題がある場合は、少しタイムラインをスクロールすると、再びアラート画面が出ます。

 

▪ 安全機能がタイムラインの局所の読込みに敏感に反応しただけの場合などでは、その後の普通の操作では、アラートが出ません。 再びアラートが出る場合は、フィルターの過剰や不適当が考えられます。

 

 

安全機能の制限 

この機能は、不適切なフィルター設定で、全件非表示➔タイムラインの暴走が生じ、最後にサーバー側の通信ブロックを受けるのを防ぐものです。 感度の匙加減は、少し安全側としています。 同系統のタイムラインに効く沢山のフィルターを設定すると、その系統のタイムラインを開いた最初にアラートが出易くなります。

 

アラートが頻繁な場合は、フィルターを減らす方向に改善した方が良いでしょう。 もし通信がブロックされた場合は、通常は5~10分位で解除されますが、フィルター設定の改善は必至と考えてください。

 

また、PCの処理能力や通信環境が良いほど、アラートが出易い傾向があります。 他の方の環境は推測するしかないのですが、もし暴走に至らないのに過剰にアラートが出るなら、ご相談ください。

 

 

 

「TwAvoid」を利用するには

このツールは Chrome / Edge / Firefox版の拡張機能「Tampermonkey」上で動作します。 以下に、このツールの導入手順を簡単に説明します。

 

❶「Tampermonkey」を導入します

◎ 使用しているブラウザに拡張機能「Tampermonkey」を導入する事が必要です。

既に「Tampermonkey」を導入している場合は、この手順 ❶ は不要です。 

拡張機能の導入については、以下のページに簡単な説明があるので参照ください。

 

 

❷「Tampermonkey」にスクリプトを登録します

◎「Tampermonkey」の「」マークの「新規スクリプト」タブを開きます。

 

 

 

◎「新規スクリプト」には、最初からテンプレートが記入されています。 これは全て削除して、完全に空白の編集枠に 下のコードをコピー&ペーストします。

 

〔コピー方法〕 軽量シンプルなツール「PreBox Button   」を使うと

  コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」

  の操作で、掲載コードのコピーが可能になります。

 

◎ 最後に「ファイル」メニューの「保存」を押すと、ツールが使用可能になります。

 

 

〔 TwAvoid 〕 ver. 0.7

 

// ==UserScript==
// @name         TwAvoid
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  Twitter Filter
// @author        Everyone
// @match        https://twitter.com/*
// @icon           https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @grant        none
// ==/UserScript==


let mode; // フィルターの追加「1」削除「0」
let mode_lt; // Link「0」・Text「1」 の処理選択
let avoid=[]; // リンクフィルターの配列
let avoid_tx=[]; // テキストフィルターの配列
let avoid_regexp; // 正規表現オブジェクト
let home_ua; // 開いているタイムラインのユーザーアカウント


let read_json=localStorage.getItem('TwAvoid_Link'); // ローカルストレージ保存名
avoid=JSON.parse(read_json);
if(avoid==null){
    avoid=['temporary-link']; }

read_json=localStorage.getItem('TwAvoid_Text'); // ローカルストレージ保存名
avoid_tx=JSON.parse(read_json);
if(avoid_tx==null){
    avoid_tx=[]; }
new_reg();

function new_reg(){
    if(avoid_tx.length==0){
        avoid_regexp=0; } // 配列の子要素が0の場合は特別処理
    if(avoid_tx.length>0){
        let tmp_ex=avoid_tx.join('|');
        avoid_regexp=new RegExp(tmp_ex); }}



setTimeout(()=>{
    page_ck();
    let target=document.querySelector('head title');
    let monitor0=new MutationObserver(page_ck);
    monitor0.observe(target, { childList: true });
}, 200);


function page_ck(){
    home_ua='/'+ location.pathname.split('/')[1];

    let retry=0;
    let interval=setInterval(wait_target, 100);
    function wait_target(){
        retry++;
        if(retry>50){ // リトライ制限 5secまで
            clearInterval(interval); }
        let time_line=get_line(); // 監視 target
        if(time_line){
            clearInterval(interval);
            main();
            let monitor1=new MutationObserver(main);
            monitor1.observe(time_line, { childList: true }); }}

    function get_line(){
        let cell=document.querySelector('[data-testid="cellInnerDiv"]');
        if(cell){
            let container=cell.parentNode;
            return container; }}

} // page_ck()



let count=0; // 非表示にした件数
let un_count=0; // 非表示にしなかった件数
let fuse=0; // 0 : フィルター処理 ON 1 : フィルター処理 OFF

s_meter();

function s_meter(){
    count=0;
    un_count=0;

    setTimeout(()=>{
        if(fuse_value(count, un_count)>6){ // 70%以上が非表示の場合 🟦
            fuse=1;
            let ok=confirm(
                '💢 OVERLOAD 💢\n'+
                '不適当なフィルターの設定で、通信過剰に陥っています\n'+
                '以下のどちらかの方法でフィルター設定を変更してください\n\n'+
                '❶ 「OK」➔ フィルター設定を変更する\n'+
                '❷ 「キャンセル」➔ タイムラインの表示に戻る');
            if(ok){
                disp_panel();
                mode=0;
                mode_lt=0;
                set_link('');
                s_meter(); }
            else{
                s_meter(); }}
        else{
            fuse=0;
            s_meter(); }
    }, 3000); } // 3secごとの集計 🟦


function fuse_value(c, un_c){
    if(c+un_c>50){ // 3secの処理件数 上限 🟦
        let av=Math.round(10*c/(c+un_c));
        return av; }
    else{
        return 0; }}



function main(){
    if(fuse==0){

        let cell=document.querySelectorAll('[data-testid="cellInnerDiv"]');
        for(let k=0; k<cell.length; k++){
            if(cell[k].style.opacity!='2'){
                engine(cell[k]);
                cell[k].style.opacity='2' }}


        function engine(tweet){
            let hav=0;
            let all_link=tweet.querySelectorAll('a');
            let url=[];
            for(let i=0; i<all_link.length; i++){
                url.push(select_url(all_link[i])); }
            if(check_url(url)){ // check1
                hav=1; }
            else{
                if(avoid_regexp!=0){ // 0の時は処理しない
                    let tweet_text=tweet.textContent;
                    if(avoid_regexp.test(tweet_text)){ // check2
                        hav=1; }}}

            if(hav==1){
                count+=1;
                tweet.style.display='none'; }
            else{
                un_count+=1; }
        } // engine()


        function select_url(get_url){
            let get=get_url.getAttribute('href');
            if(get!=home_ua){
                return get; }}

        function check_url(url){
            let result=url.filter(data=>avoid.includes(data));
            if(result.length>0){
                return true; }}

    }
} // main()




function link_pointer(){
    let elem=document.elementFromPoint(event.clientX, event.clientY);
    if(elem){
        let link_a=elem.closest('a');
        if(link_a){
            event.preventDefault();
            event.stopImmediatePropagation();
            let link_url=link_a.getAttribute('href');
            disp_panel();
            mode=1;
            mode_lt=0;
            set_link(link_url);
        }}}


setTimeout(()=>{
    let layers=document.querySelector('#layers');
    let monitor2=new MutationObserver(card_ck);
    monitor2.observe(layers, { childList: true });
}, 200);

function card_ck(){
    let card=document.querySelector('[data-testid="hoverCardParent"]');
    if(card){
        card.addEventListener('contextmenu', function(event){
            if(event.altKey){
                link_pointer();
            }}); }
} // card_ck()


document.addEventListener('contextmenu', function(event){
    if(event.altKey){
        link_pointer();
    }});



function disp_panel(){
    let panel=
        '<div id="twa_panel">'+
        '<div class="swich_box">'+
        '<div class="swich">'+
        '<input class="li_tx" type="button" value="Link">'+
        '<input class="add_b" type="button" value="フィルター追加">'+
        '<input class="rem_b" type="button" value="フィルター削除">'+
        '</div>'+
        '<input class="twa_close" type="button" value="✖">'+
        '</div>'+
        '<div>'+
        '<input class="set_box" type="text">'+
        '</div>'+
        '<div class="avoid_list">'+
        '<ul class="avoid_ul"></ul>'+
        '</div>'+

        '<style>#twa_panel { position: fixed; top: 60px; right: 40px; width: 340px; '+
        'font: 16px Meiryo; color: #000; background: #f4fbff; padding: 15px; '+
        'border: 1px solid #aaa; box-shadow: 10px 20px 30px 0 #ccc; } '+
        '.swich_box { display: flex; height: 30px; align-items: center; '+
        'justify-content: space-between; } '+
        '.swich_box input { padding: 3px 7px 2px; border: 1px solid #aaa; '+
        'border-radius: 4px; height: 27px; } '+
        '.swich_box input.li_tx { font: normal 16px/23px Meiryo; } '+
        '.swich { display: flex; justify-content: space-between; width: 280px; } '+
        '.set_box { font: 16px Meiryo; padding: 4px 10px 3px; margin: 10px 0; '+
        'width: calc(100% - 23px); } '+
        '.set_box::placeholder { font-size: 14px; } '+
        '.avoid_list { margin: 10px 0 0; padding: 4px 0; min-height: 30px; '+
        'max-height: 240px; border: 1px solid #aaa; overflow-y: scroll; } '+
        '.avoid_ul { list-style: none; padding: 0; margin: 0; } '+
        '.avoid_ul.rem { background: #fff; } '+
        '.avoid_ul.rem .avoid_li { cursor: pointer; } '+
        '.avoid_li { padding: 3px 10px 0; height: 26px; border-bottom: 1px solid #ddd; '+
        'white-space: nowrap; overflow-x: scroll; scrollbar-width: none; } '+
        '.avoid_li:first-child { border-top: 1px solid #ddd; } '+
        '.avoid_li::-webkit-scrollbar { display: none; } '+
        '</style></div>';

    if(!document.querySelector('#twa_panel')){
        document.body.insertAdjacentHTML('beforeend', panel); }

} // disp_panel()



function set_link(str){
    let twa_panel=document.querySelector('#twa_panel');
    let set_box=document.querySelector('.set_box');
    if(set_box){
        set_box.value=str; }
    set_new_link();

    if(mode==1){
        add_link(); }
    else{
        rem_link(); }


    let li_tx=document.querySelector('.li_tx');
    if(li_tx && twa_panel){
        if(mode_lt==0){
            li_tx.value='Link';
            twa_panel.style.background='#f4fbff'; }
        else{
            li_tx.value='Text';
            twa_panel.style.background='#fff7e4'; }

        li_tx.onclick=()=>{
            if(mode_lt==0){
                mode_lt=1;
                li_tx.value='Text';
                twa_panel.style.background='#fff7e4';
                set_link(''); }
            else{
                mode_lt=0;
                li_tx.value='Link';
                twa_panel.style.background='#f4fbff';
                set_link(''); }}}


    let twa_close=document.querySelector('.twa_close');
    if(twa_panel && twa_close){
        twa_close.onclick=()=>{
            twa_panel.remove(); }}

} // set_link()



function add_link(){
    let add_b=document.querySelector('.add_b');
    add_b.style.outline='2px solid #2196f3';
    let rem_b=document.querySelector('.rem_b');
    rem_b.style.outline='';
    let set_box=document.querySelector('.set_box');
    set_box.disabled=false;
    set_box.style.borderColor='';
    set_box.style.background='';
    if(mode_lt==0){
        set_box.setAttribute('placeholder', 'ページ上のリンクを「Alt+右Click」して採取'); }
    else{
        set_box.setAttribute('placeholder', 'テキストを記入するかコピーして貼付けます'); }
    let avoid_ul=document.querySelector('.avoid_ul');
    avoid_ul.classList.remove('rem');


    add_b.onclick=()=>{
        let link_str=set_box.value;
        if(link_str!=''){
            if(space_check(link_str)){
                alert("⛔ 空白文字が含まれています"); }
            else if(mode_lt==0 && avoid.includes(link_str)){
                alert("⛔ 既に登録済のリンクコードです"); }
            else if(mode_lt==1 && avoid_tx.includes(link_str)){
                alert("⛔ 既に登録済のテキストです"); }
            else{
                set_box.value='';
                if(mode_lt==0 ){
                    avoid.push(link_str); }
                else{
                    avoid_tx.push(link_str); }
                set_new_link(); }}

        function space_check(str){
            let check=/\s+/g;
            if(check.test(str)){
                return true; }}}


    rem_b.onclick=()=>{
        mode=0;
        set_link(''); }

} // add_link()



function rem_link(){
    let add_b=document.querySelector('.add_b');
    add_b.style.outline='';
    let rem_b=document.querySelector('.rem_b');
    rem_b.style.outline='2px solid #2196f3';
    let set_box=document.querySelector('.set_box');
    set_box.setAttribute('placeholder', '下のフィルターリストを「左Click」して指定');
    set_box.disabled=true;
    set_box.style.borderColor='transparent';
    set_box.style.background='transparent';
    let avoid_ul=document.querySelector('.avoid_ul');
    avoid_ul.classList.add('rem');


    let avoid_li=document.querySelectorAll('.avoid_li');
    for(let k=0; k<avoid_li.length; k++){
        avoid_li[k].onclick=()=>{
            clear();
            rem_b.style.outline='2px solid red';
            avoid_li[k].style.background='#cfd8dc'; }}

    function clear(){
        let avoid_li=document.querySelectorAll('.avoid_li');
        for(let k=0; k<avoid_li.length; k++){
            avoid_li[k].style.background=''; }}


    rem_b.onclick=()=>{
        let avoid_li=document.querySelectorAll('.avoid_li');
        for(let k=0; k<avoid_li.length; k++){
            if(avoid_li[k].style.background){
                let select_text=avoid_li[k].textContent;
                if(mode_lt==0){
                    avoid=avoid.filter(function(item){
                        return item!=select_text; }); }
                else{
                    avoid_tx=avoid_tx.filter(function(item){
                        return item!=select_text; }); }
                set_new_link();
                rem_link(); }}}


    add_b.onclick=()=>{
        mode=1;
        set_link(''); }

} // rem_link()



function set_new_link(){
    if(mode_lt==0){
        let set=new Set(avoid);
        avoid=[...set]; // 追加分をチェックして配列を更新
        let write_json=JSON.stringify(avoid);
        localStorage.setItem('TwAvoid_Link', write_json); } // ストレージ保存
    else{
        let set_tx=new Set(avoid_tx);
        avoid_tx=[...set_tx]; // 追加分をチェックして配列を更新
        let write_json=JSON.stringify(avoid_tx);
        localStorage.setItem('TwAvoid_Text', write_json); } // ストレージ保存

    new_reg(); // テキスト検索設定を更新

    let avoid_ul=document.querySelector('.avoid_ul');
    if(avoid_ul){
        avoid_ul.innerHTML=''; // リスト表示をクリア

        if(mode_lt==0){
            for(let k=avoid.length-1; k>=0; k--){
                let link_li='<li class="avoid_li">'+ avoid[k] +'</li>';
                avoid_ul.insertAdjacentHTML('beforeend', link_li); }}
        else{
            for(let k=avoid_tx.length-1; k>=0; k--){
                let link_li='<li class="avoid_li">'+ avoid_tx[k] +'</li>';
                avoid_ul.insertAdjacentHTML('beforeend', link_li); }}}

} // set_new_link()




 

 

 

「TwAvoid」最新版について 

旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。

 

● 最新バージョンへのリンクは、以下のページのリンクリストから探せます。