処理速度は予想通り

「Every Page Snap」がページ内のデータを記録(SNAP)するスピードは速く、2年分の約700件/50ページの記事を、初期設定で1分以内に処理出来ました。 色々とテストをしましたが、この速度でデータの読取りは全く問題がない様です。 ただ、アメブロ側のサーバー応答が10秒程度止まる事があります。 深夜等は渋滞がない様ですが、放置していると再びSNAP動作を始めます。 このスクリプトは通信応答が滞ったらそこで待ち、スクリプト自体が止まる事はありません。

 

問題になったのは、連続動作の意図的な停止方法です。 初期の速度では「停止ボタン」の表示とページ移動が同時で、クリックが出来ません。 そこでページが開いて僅かの時間を待機させ、クリックを可能に調整しました。

 

他の問題は、ページ遷移時の画面のちらつきです。「Stylus」を使った「Ameblo Management」はページデフォルトの表示を隠せますが、「Tampermonkey」のスクリプトによるCSS適用は、デフォルト画面がページ送りの度に露出します。 可能な高速化の処置を駆使しても改善出来ません。 仕方が無いので画面フェードインのCSSを使い、少しちらつきを緩和しています。

 

 

コントロール部のコード

以下は、開始・停止・連続動作・終了の動作を分岐するコードです。

 

下の関数は引数「d」に従って制御され、「d」が「s」の時は開始または停止、「c」は連続動作、「e」で終了表示をします。 開始・停止・終了時は全てのボタンを表示し、連続動作時は「停止ボタン」とSNAPデータのみ表示します。

 

function control_pannel(d){

    let button1=document.createElement('input');
    button1.setAttribute('id', 'list_snap');
    button1.setAttribute('type', 'submit');
    button1.setAttribute('style', 'padding: 2px 8px 0; margin: 7px 25px 7px 0; width: 150px');
    insert_div0.appendChild(button1);

    if(d=='s'){
        button1.setAttribute('value', '公開設定のSNAP開始');
        button1.onclick=function(e){
            e.preventDefault();
            start(); }

        function start(){
            let conf_str=['   🔴 このページ以降の記事に関して 「公開設定」を記録します',
                          '\n      自動でページが送られますが、停止ボタンで中断可能です'].join(' ');
            let ok=confirm(conf_str);
            if(ok){
                button1.setAttribute('value', '  SNAPを停止  ');
                blogDB[0][1]='c'; // 連続動作フラグをセット
                let write_json=JSON.stringify(blogDB);
                localStorage.setItem('blogDB_back', write_json); // ローカルストレージ 保存
                next(); }}}

    else if(d=='c'){ // 「c」は連続動作
        button1.setAttribute('value', '  SNAPを停止  ');
        button1.addEventListener('mouseover', function(e){
            e.preventDefault();
            stop(); }, false);

        function stop(){
            blogDB[0][1]='s'; // 連続動作フラグをリセット
            let write_json=JSON.stringify(blogDB);
            localStorage.setItem('blogDB_back', write_json); } // ローカルストレージ 保存
        setTimeout(next, 400); } // 連続実行のぺージ遷移のタイミング 0.4sec ⭕

    else if(d=='e'){ // 「e」は終了
        button1.setAttribute('value', 'SNAPが終了しました');
        button1.style.pointerEvents='none'; }}

 

赤文字の部分は、「動作フラグ」をローカルストレージのSNAPデータの「配列の先頭要素」に記録するものです。

 

下は、DevToolsでローカルストレージを開いたところです。 各ページで記録した「記事ID」と「公開設定」を配列に収めて「blogDB_back」の名で保存しています。 赤枠は「配列の先頭要素」で、ID「00000000000」動作フラグは「s」の状態です。

 

 

スクリプトは起動時に「blogDB_back」を読込み、「先頭要素」から「動作フラグ」を受け取ります。「動作フラグ」はコントロール部の引数「d」に渡り、連続動作 / 停止が決まります。フラグが「c」ならすぐにSNAP動作を行い、「s」ならコントロール部を表示して次の作業の指示を待ちます。

 

SNAP動作の開始

「公開設定のスナップ開始」を押すと、「動作フラグ」の 連続「c」をストレージに記録し、そのページのSNAP(データ記録)を行います。

 

 

SNAPしたデータをストレージに記録したら、次のぺージを開いて自らは終了します。

以降は「c」の「動作フラグ」を読み、SNAP→記録→移動を繰り返します。

 

SNAP動作の停止

「Every Page Snap」の連続動作の停止は、ストレージの「動作フラグ」を「s」にする必要があります。 ページリロードではストレージは消えず、「記事の編集・削除」のページを開くと再び連続動作が始まります。 止めるには「停止操作」をするか、ブログの最後までの処理を待つかの、どちらかしかありません。

 

連続SNAP動作は終了までそう時間がかかりませんが、中断したい場合はマウスのポインタをコントロール部上に入れます。 これが「停止操作」でクリックは不要です。

 

 

ポインタを感知すると、上の様にコントロール部の背景色が一瞬変化し、次のページを開いた時に停止します。 クリックを排除したのは、高速動作を止める工夫です。

 

SNAPしたデータはページ移動の度にストレージに保存され、停止したページから再びSNAPを始める事が出来ます。 通過した範囲のデータは蓄積し、間の抜けが無いなら全ブログのデータとする事が出来ます。 また、SNAPが2度重複した部分は、最新のデータに更新されるので重複は問題ありません。 しかし、SNAP動作は短時間で済むので、ブログの最初から最後まで一気に終わらせるのが推奨です。

 

初期化

同じ場所のSNAPが重複しても、ブログ全体のSNAPデータは全記事数を越えません。 約100件のSNAPデータは2KB程度で、ローカルストレージは充分に余裕があります。

 

従って「初期化」ボタンは余り出番が無いかも知れません。 特定の範囲のSNAPデータを確認する時などに利用できるかも。 ボタンを押すと、配列の先頭要素を残し全記録を消去します。 消去されるのはローカルストレージのSNAPデータです。

 

ファイルの保存

SNAPデータをダウンロードフォルダに、ファイル名「blogDB.json」で保存します。 次に保存すると自動的に連番が付けられ「blogDB(1).json」「blogDB(2).json」と増えて行きます。 ローカルストレージのデータはブラウザしか扱えませんが、ダウンロードしたファイルは、保存・コピー・移動・加工などが自由にできます。

 

ファイルの読込み

ダウンロードフォルダ等に保存した「blogDB(n).json」タイプのSNAPデータファイルを読込んで、それまでのローカルストレージのデータを上書きします。 ファイルに記録された配列の先頭要素で「適合ファイル」か否かを判断します。 ファイル名が変更されていても、「Every Page Snap」が保存したファイルは読み込めます。 他のツールが保存したファイルは除外され、ストレージが上書きされる事はありません。

 

「Every Page Snap」は、ブログ記事を一時的に「下書き」に変更したり、再び元の状態に「公開」する事に使用します。 従って、ファイル保存したデータは「いつの状態の記録」かが明確でなければ、意味がありません

 

SNAPしたデータの表示

「記録件数」「全員に公開」「アメンバー限定公開」「下書き」の各件数が表示されます。 これらの値は、ローカルストレージにSNAP記録した内容です。

「Every Page Snap」の SNAP動作中は、リアルタイムにカウントが増えます。 ブログの記事リストが正常なら、「記録件数」は他の3者の合計になります。

 

 

「Every Page Snap 💢」 ver. 1.8

「Every Page Snap」が、ほぼ使えるツールになりました。 今後に小さな修正があるかも知れませんが、既に実使用する事ができます。 このツールでSNAPしたデータを使う「Every Page Opener」を作るのが次の課題です。

 

「Tampermonkey」のスクリプトによるCSS適用が一歩遅く、「Stylus」の様に瞬時に適用することが出来ません。 これは残念で、今後も追及したい部分です。

 

コードは Chrome版/Firefox版/Edge版の「Tampermonkey」で動作を確認しています。 Chrome / Firefox のブラウザの場合は「Stylus」で「Ameblo Management」

のページアレンジを適用した方が、画面のちらつきが少なくなります。

 

 

〔 Every Page Snap Ⅱ 〕 ver. 1.8 

// ==UserScript==
// @name         Every Page Snap 💢
// @namespace  http://tampermonkey.net/
// @version       1.8
// @description  「記事の編集・削除」ページで全記事の「公開設定」を記録する
// @author        Ameba Blog User
// @match        https://blog.ameba.jp/ucs/entry/srventrylist*
// @run-at        document-start
// ==/UserScript==


window.addEventListener('DOMContentLoaded', function(){ // CSSデザインを適用するためのスクリプト
    let css=[
        'html { overflow-y: overlay !important }',
        'body { animation: fadeIn 0.2s ease 0s 1 normal; }',
        '@keyframes fadeIn { 0% {opacity: 0} 100% {opacity: 1}}',
        '#globalHeader, #ucsHeader, #ucsMainLeft h1, .l-ucs-sidemenu-area, #ucsMainRight,',
        '#entryList dl, #entryList .deleteCol, #entryList .rightCol input, .actionControl,',
        '#footerAd, #globalFooter, .checkboxAllControl { display: none }',
        '#ucsContent{ width: 840px; box-shadow: 0 0 0 100vh #c5d8e1; }',
        '#ucsMain{ background: #fff } #ucsMainLeft { width: 810px; float: none }',
        '#entryList .rightCol{ width: 225px } #entryList .leftCol, #entryList .titleCol h2{ width: 560px }',
        '#entryList li{ padding: 6px 5px !important; height: 18px}',
        '#entryList li:hover{ background-color: #e2eef0 }',
        '.leftCol a:hover{ text-decoration: none } .leftCol a:visited{ color: #3970b5 }',
        '#entryList .txtCol .date{ font-size: 13px; color: #000 }',
        '#entryList .txtCol{ white-space: nowrap; font-size: 13px; margin-top: 2px; line-height: 16px }',
        '#entryList ul input[value="1"] + input + li .txtCol span:first-child{ display: inline-block;',
        'margin-top: -2px; padding: 2px 13px 0; color: #fff; background: #2196f3 !important }',
        '#entryList ul input[value="2"] + input + li .txtCol span:first-child{ display: inline-block;',
        'width: 3em; text-indent: -1em; overflow: hidden; margin-top: -2px; padding: 2px 13px 0;',
        'vertical-align: -4px; color: #fff; background: #009688 !important }',
        '#entryMonth { overflow: visible } #entryMonth li { padding: 2px 12px 4px }',
        '#nowMonth { border: none; outline: 2px solid red; outline-offset: 2px; color: #000 }',
        '#sorting{ margin: 0 0 4px; padding: 4px 10px; height: 110px; background: #ddedf3}',
        '#sorting select, #sorting ul { display: none; }',
        '.pagingArea .active{ border: 2px solid #0066cc; }'].join(' ');

    let style=document.createElement('style');
    style.insertAdjacentHTML('afterbegin', css);
    document.documentElement.appendChild(style);
})



window.addEventListener('load', function(){
    let drive_mode;
    let blogDB={}; //「アメンバー公開」の記事IDリスト
    let entry_id;
    let entry_id_DB;
    let publish_f;
    let pub_all;
    let pub_dra;
    let pub_ame;

    let ua=0;
    let agent=window.navigator.userAgent.toLowerCase();
    if(agent.indexOf('firefox') > -1){ ua=1; } // Firefoxの場合のフラグ
    if(agent.indexOf('edge') > -1){ ua=2; } // Edgeの場合のフラグ

    let read_json=localStorage.getItem('blogDB_back'); // ローカルストレージ 保存名
    blogDB=JSON.parse(read_json);
    if(blogDB==null){
        blogDB=[['00000000000', 's']]; }

    drive_mode=blogDB[0][1]; // 起動時に動作フラグを取得
    if(drive_mode==0){ drive_mode='s'; } // 旧ファイルの救済

    reg_set();

    function reg_set(){
        let k;
        entry_id_DB=[]; // リセット
        pub_all=0;
        pub_dra=0;
        pub_ame=0;

        for(k=0; k<blogDB.length; k++){
            entry_id_DB[k]=blogDB[k][0]; // ID検索用の配列を作成
            if(blogDB[k][1]=='0'){
                pub_all +=1; continue; }
            if(blogDB[k][1]=='1'){
                pub_dra +=1; continue; }
            if(blogDB[k][1]=='2'){
                pub_ame +=1; continue; }}}

    control_pannel(drive_mode);

    function control_pannel(d){
        let sty;
        let insert_div0;
        insert_div0=document.createElement('div');
        insert_div0.setAttribute('id', 'div0');
        insert_div0.style='color: #333; font-size: 14px; margin: 10px -10px 0 15px';
        let box=document.querySelector('#sorting');
        box.appendChild(insert_div0);

        let insert_div1;
        insert_div1=document.createElement('div');
        insert_div1.setAttribute('id', 'div1');
        insert_div1.style='color: #000; font-size: 14px; margin: 8px 15px; border: 1px solid #888';
        box.appendChild(insert_div1);

        let button1=document.createElement('input');
        button1.setAttribute('id', 'list_snap');
        button1.setAttribute('type', 'submit');
        button1.setAttribute('style', 'padding: 2px 8px 0; margin: 7px 25px 7px 0; width: 150px');
        insert_div0.appendChild(button1);

        if(d=='s'){
            button1.setAttribute('value', '公開設定のSNAP開始');
            button1.onclick=function(e){
                e.preventDefault();
                start(); }

            function start(){
                let conf_str=['   🔴 このページ以降の記事に関して 「公開設定」を記録します',
                              '\n      連続動作はコントロール部にマウスに乗せると停止します'].join(' ');
                let ok=confirm(conf_str);
                if(ok){
                    blogDB[0][1]='c'; // 連続動作フラグをセット
                    let write_json=JSON.stringify(blogDB);
                    localStorage.setItem('blogDB_back', write_json); // ローカルストレージ 保存
                    next(); }}}
        else if(d=='c'){ // 「c」は連続動作
            button1.setAttribute('value', '  SNAPを停止  ');
            button1.style.pointerEvents='none';
            button1.style.width='760px';
            box.addEventListener('mouseover', function(e){
                e.preventDefault();
                box.setAttribute('style', 'background: #96b6d2');
                stop(); }, false);

            function stop(){
                blogDB[0][1]='s'; // 連続動作フラグをリセット
                let write_json=JSON.stringify(blogDB);
                localStorage.setItem('blogDB_back', write_json); } // ローカルストレージ 保存
            setTimeout(next, 400); } // 連続実行のぺージ遷移のタイミング 0.4sec ⭕

        else if(d=='e'){ // 「e」は終了
            button1.setAttribute('value', 'SNAPが終了しました');
            button1.style.pointerEvents='none';
            box.addEventListener('mouseover', function(e){
                e.preventDefault();
                box.setAttribute('style', 'background: #ddedf3'); }, false); }

        if(d=='s' || d=='e'){ // 動作モードが「c」の場合はボタンを作らない
            let button2=document.createElement('input');
            button2.setAttribute('id', 'reset');
            button2.setAttribute('type', 'submit');
            button2.setAttribute('value', ' 初期化 ');
            button2.setAttribute('style', 'padding: 2px 8px 0; margin-right: 30px; width: 68px');

            insert_div0.appendChild(button2);
            button2.onclick=function(e){
                e.preventDefault();
                blogDB=[['00000000000', 's']];
                let write_json=JSON.stringify(blogDB);
                localStorage.setItem('blogDB_back', write_json); // ローカルストレージ保存
                snap_disp();
                document.querySelector('#reset').value='〔 〕'; }

            let button3=document.createElement('input');
            button3.setAttribute('type', 'submit');
            button3.setAttribute('value', 'SNAPをファイル保存');
            button3.setAttribute('style', 'padding: 2px 8px 0; margin: 7px 25px');
            if(ua==1){
                button3.setAttribute('style', 'padding: 2px 8px 0; margin: 7px 25px 6px'); }
            if(ua==2){
                button3.setAttribute('style', 'padding: 1px 8px 2px; margin: 9px 25px'); }
            insert_div0.appendChild(button3);

            button3.onclick=function(e){
                e.preventDefault();
                let write_json=JSON.stringify(blogDB);
                let blob=new Blob([write_json], {type: 'application/json'});
                if(ua==2){
                    window.navigator.msSaveBlob(blob, 'blogDB.json'); } // 保存ファイル名
                else{
                    let a_elem=document.createElement('a');
                    a_elem.href=URL.createObjectURL(blob);
                    a_elem.download='blogDB.json'; // 保存ファイル名
                    if(ua==1){
                        a_elem.target='_blank';
                        document.body.appendChild(a_elem); }
                    a_elem.click();
                    if(ua==1){
                        document.body.removeChild(a_elem); }
                    URL.revokeObjectURL(a_elem.href); }}

            let button4=document.createElement('input');
            button4.setAttribute('type', 'file');
            button4.setAttribute('style', 'vertical-align: 1px;width: 300px');
            insert_div0.appendChild(button4);

            button4.addEventListener("change", function(){
                if(!(button4.value)) return; // ファイルが選択されない場合
                let file_list=button4.files;
                if(!file_list) return; // ファイルリストが選択されない場合
                let file=file_list[0];
                if(!file) return; // ファイルが無い場合

                let file_reader=new FileReader();
                file_reader.readAsText(file);
                file_reader.onload=function(){
                    if(file_reader.result.slice(0, 15)=='[["00000000000"'){ // blogDB.jsonの確認
                        let data_in=JSON.parse(file_reader.result);
                        blogDB=data_in; // 読込み上書き処理
                        let write_json=JSON.stringify(blogDB);
                        localStorage.setItem('blogDB_back', write_json); // ローカルストレージ 保存
                        reg_set();
                        snap_disp(); }
                    else{
                        alert("   ⛔ 不適合なファイルです  blogDB(n).json ファイルを選択してください");}};});}

        let span5=document.createElement('span');
        span5.setAttribute('id', 'snap_result');
        span5.style='display: inline-block; margin: 6px 12px 4px';
        insert_div1.appendChild(span5);
        snap_disp(); }


    function snap_disp(){
        reg_set();
        let span5=document.querySelector('#snap_result');
        span5.textContent=' 記録件数:' + (blogDB.length -1) + '  全員に公開:' + pub_all +
            '  アメンバー限定公開:' + pub_ame + '  下書き:' + pub_dra; }


    function next(){
        let win_url;
        let current;
        let pageid;
        let next_url;
        let pager;
        let end;

        entry_id=document.querySelectorAll('input[name="entry_id"]');
        if(entry_id.length >0){
            snap(); } // 投稿記事がある場合SNAPを実行 無ければスルーする

        win_url=window.location.search.substring(1,window.location.search.length);
        current=win_url.slice(-6);

        if(win_url.indexOf('pageID') ==-1){ // pageIDが無い 月のトップページの場合
            pager=document.querySelector('.pagingArea');
            if(pager){ // ページャーが有りその末尾でなければ同月次ページへ
                next_url=['https://blog.ameba.jp/ucs/entry/srventrylist.do?',
                          'pageID=2&entry_ym=' + current].join('');
                window.open( next_url, '_self'); }
            else{ // ページャーが無ければ次月トップページへ
                current=make_next(current);
                if(current!=0){ // 現在を越えないなら次月へ
                    next_url=['https://blog.ameba.jp/ucs/entry/srventrylist.do?',
                              'entry_ym=' + current].join('');
                    window.open( next_url, '_self'); }
                else{ // 現在を越えたら0が戻り停止
                    when_edge(); }}}

        else{ // pageIDを含み 月のトップページでない場合
            end=document.querySelector('.pagingArea .disabled.next');
            if(!end){ // ページャーの末尾でなければ同月次ページへ
                pageid=parseInt(win_url.slice(7).slice(0, -16), 10) +1;
                next_url=['https://blog.ameba.jp/ucs/entry/srventrylist.do?',
                          'pageID=' + pageid + '&entry_ym=' + current].join('');
                window.open( next_url, '_self'); }
            else{ // ページャーの末尾なら次月トップページへ
                current=make_next(current);
                if(current!=0){ // 現在を越えないなら次月へ
                    next_url=['https://blog.ameba.jp/ucs/entry/srventrylist.do?',
                              'entry_ym=' + current].join('');
                    window.open( next_url, '_self'); }
                else{ // 現在を越えたら0が戻り停止
                    when_edge(); }}}

        function make_next(curr){
            let ym;
            let y;
            let m;
            ym=parseInt(curr, 10); // 10進数値化
            y=Math.floor(ym/100); // 年は100で割った商
            m=ym % 100; // 月は100で割った余り
            if(m !=12){
                ym=100*y + m +1; }
            else{
                ym=100*y + 101; }

            let now=new Date();
            if(ym > 100*now.getFullYear() + now.getMonth() +1){
                return 0; } // 現在の月を越える場合は0を返す
            else{
                return ym; }} // 次年月の数値を返す

        function when_edge(){
            blogDB[0][1]='s'; // 連続動作フラグをリセット
            let write_json=JSON.stringify(blogDB);
            localStorage.setItem('blogDB_back', write_json); // ローカルストレージ保存
            document.querySelector('#div0').remove();
            document.querySelector('#div1').remove();
            control_pannel('e'); }} // SNAP終了時の表示をさせる


    function snap(){ // ページ内の「公開設定」をSNAPする
        let k;
        entry_id=document.querySelectorAll('input[name="entry_id"]');
        publish_f=document.querySelectorAll('input[name="publish_flg"]');

        for(k=0; k< entry_id.length; k++){
            let index=entry_id_DB.indexOf(entry_id[k].value);
            if(index==-1){ // IDがblogDBに記録されていない場合
                blogDB.push([entry_id[k].value, publish_f[k].value]); } // 公開設定の登録を追加
            else{ // IDがblogDBに記録されていた場合
                blogDB[index]=[entry_id[k].value, publish_f[k].value]; }} // 公開設定の登録を更新
        setTimeout(write_local, 10);

        function write_local(){
            let write_json=JSON.stringify(blogDB);
            localStorage.setItem('blogDB_back', write_json); }} // ローカルストレージ保存

})


 

 

〔追記〕2019.09.03

全記事の公開情報をSNAPして Close / Open を可能とする方式を標準とする事にしました。 この方式のツールは、判り易い様に「💢」マークをつける事にしました。

 

  Every Page Snap 💢

  Every Page Closer 💢

  Every Page Opener 💢

 

のシリーズがこれに相当します。 特徴はクローズ処理では「アメンバー」「全員に公開」「下書き」のすべてを「下書き」に変更し、オープン処理では元の公開状態に戻せる事です。 アメンバー記事を処理しない場合は、旧タイプのCloserが使えます。

 

 

 

「Every Page Snap 💢」最新版について 

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

 

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