SNAPをして歩くロボットプログラム

「Every Page Snap」は、「記事の編集・削除」の開いた任意のページで「スタート」を押すと、それ以降のページを順に開きながら、それぞれのページで記事の「公開設定」を記録し続けます。 これは「ストップ」ボタンを押して中断しない限り、現在の月のページまで進行し、現在の月を越えた時に停止します。 この様なロボットプログラムの構成を、以下に簡単な模式図にしました。

 

 

基本になるのが「Snap」部で、ページ内の記事リストから「記事ID」と「公開設定」を記録します。 その後に「Next」部で次ページへの移動を行います。

 

ページ遷移により、スクリプトは自らを終了させ、別ウインドウで再起動します。 スクリプトはページ単位で動作する仕様だからです。 ところが、このスクリプトは次の2種の異なる動作をする必要があります。

 

◎ 最初に起動した時は「Start」を押さないと「Snap」「Next」を行なわない。

◎「Start」を押した後は、「Stop」を押すか最終ページに至るまで、起動すれば自動的に「Snap」「Next」を行い、次のスクリプトを起動させる。 

 

このコントロールをするのがスクリプトの最初の「Start」「Stop」部です。 上図では同じスクリプトが3個並んでいますが、それぞれ別ページで再起動したものです。 どちらの動作をするかは、動作種のフラグを起動時に得る事で選択できます。 フラグを伝える方法は、ローカルストレージか URLパラメーターが使えます。 今回は、URLの扱いが複雑なのでローカルストレージを使おうと考えています。

 

 

ページ移動を受け持つコード

「記事の編集・削除」の画面は、月ごとに記事を纏めています。 ページの最大件数は20件で、それを越えるとページャーが現れ、複数ページで保存されます。 このページ構成のために、単なる連番ページを移動するより複雑なコードが必要になります。

 

◎月の先頭ページ

 ▪ページャーがある (同月内に次ページがある)➔ 同月次ページへ

 ▪ページャーがない (先頭ページのみの月)➔ 次月トップページへ

◎月の先頭ページでない 

 ▪ページャーの最後のページでない ➔ 同月次ページへ

 ▪ページャーの最後のページ ➔ 次月トップページへ

 

「記事の編集・削除」の各ページのURLパラメーターは、以下の様な構成です。

   ……entry/srventrylist.do?pageID=2&entry_ym=201908 

赤文字の部分がページを特定します。 スクリプトで次ページを開くには、それに相当するパラメーターを生成してウインドウを開きます。

 

更に、このパラメーターが現在の月を越える場合は、スクリプトを停止させるコードが必要になります。 それが無いと永遠に止まらなくなりますから。

 

以下が現在のこの部分のコードです。

 

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

    win_url=window.location.search.substring(1,window.location.search.length);

    if(win_url.indexOf('pageID') ==-1){ // RLにpageIDが無い 月のトップページの場合
        current=win_url.slice( -6 );
        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){ // 現在を越えたら0が戻るので停止
                next_url=['https://blog.ameba.jp/ucs/entry/srventrylist.do?',
                          'entry_ym=' + current].join('');
                window.open( next_url, '_self'); }}}

    else{ // URLにpageIDを含み 月のトップページでない場合
        pageid=win_url.replace('pageID=', '')
        current=win_url.slice( -6 );
        pageid=pageid.slice( 0, -16 ); // pageIDの値を切出し
        end=document.querySelector('.pagingArea .disabled.next');
        if(!end){ // ページャーの末尾でなければ同月次ページへ
            pageid=parseInt(pageid, 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){ // 現在を越えたら0が戻るので停止
                next_url=['https://blog.ameba.jp/ucs/entry/srventrylist.do?',
                          'entry_ym=' + current].join('');
                window.open( next_url, '_self'); }}}

    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; }}} // 次年月の数値を返す

 

 

ページデザインの変更

現在のコントロール部のデザインです。 サブの編集ウインドウを開く必要がないので、ページ幅を広くしてコントロール部のまとまりを良くしました。 また、リストの不要な部分を削って、ページの高さを低くしました。

 

 

 

現在のコード

SNAPの実行部「Snap」とページ送り部「Next」のコードが出来ました。 自動実行のコントロール部が無いので、「公開設定のSNAP開始」を押して1ページずつ先に進める必要がありますが、順序よく次のページを開いて行く事が確認出来ます。

 

〔 Every Page Snap 〕 ver. 1.7

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


window.addEventListener('DOMContentLoaded', function(){ // CSSデザインを適用するためのスクリプト
    'use strict';
    var css=[
        '#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 }',
        '.rightCol{ width: 210px !important } .leftCol, .titleCol h2{ width: 353px !important }',
        '#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 }',
        '#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(' ');

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



window.addEventListener('load', function(){
    'use strict';

    var blogDB={}; //「アメンバー公開」の記事IDリスト
    var entry_id;
    var entry_id_DB;
    var publish_f;
    var pub_all;
    var pub_dra;
    var pub_ame;


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

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


    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; }
            else if(blogDB[k][1]==1){
                pub_dra +=1; }
            else if(blogDB[k][1]==2){
                pub_ame +=1; }}}


    control_pannel();

    function control_pannel(){
        let sty;
        let insert_div0;
        insert_div0=document.createElement('div');
        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.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('value', '公開設定のSNAP開始');
        button1.setAttribute('style', 'padding: 2px 8px 0; margin-right: 25px');

        insert_div0.appendChild(button1);
        button1.onclick=function(e){
            e.preventDefault();
            start_select(); }

        function start_select(){
            entry_id=document.querySelectorAll('input[name="entry_id"]');
            if(entry_id.length==0 || entry_id==null){ // 編集対象がリストに無い場合
                alert('編集対象の記事がありません'); }
            if(entry_id.length >0){ // 編集対象がリストに有る場合
                let conf_str=['  ⛔ このページ以降の記事に関して 「公開設定」を記録します',
                              '\n     自動でページが送られますが、停止ボタンで中断可能です'].join(' ');
                let ok=confirm(conf_str);
                if(ok){
                    document.querySelector('#list_snap').value='  SNAPを停止  ';
                    next(); }}}

        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');

        insert_div0.appendChild(button2);
        button2.onclick=function(e){
            e.preventDefault();
            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'); }
        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'});
            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(){
        let span5=document.querySelector('#snap_result');
        span5.textContent=' 記録件数:' + blogDB.length + '  全員に公開:' + pub_all +
            '  アメンバー限定公開:' + pub_ame + '  下書き:' + pub_dra; }


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

        snap(); // SNAPを実行

        win_url=window.location.search.substring(1,window.location.search.length);

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

        else{ // URLにpageIDを含み 月のトップページでない場合
            pageid=win_url.replace('pageID=', '')
            current=win_url.slice( -6 );
            pageid=pageid.slice( 0, -16 ); // pageIDの値を切出し
            end=document.querySelector('.pagingArea .disabled.next');
            if(!end){ // ページャーの末尾でなければ同月次ページへ
                pageid=parseInt(pageid, 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){ // 現在を越えたら0が戻るので停止
                    next_url=['https://blog.ameba.jp/ucs/entry/srventrylist.do?',
                              'entry_ym=' + current].join('');
                    window.open( next_url, '_self'); }}}

        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 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); // ローカルストレージ名
            reg_set();
            snap_disp(); }}

    })

 

 

 

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

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

 

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