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
赤文字の部分がページを特定します。 スクリプトで次ページを開くには、それに相当するパラメーターを生成してウインドウを開きます。
更に、このパラメーターが現在の月を越える場合は、スクリプトを停止させるコードが必要になります。 それが無いと永遠に止まらなくなりますから。
以下が現在のこの部分のコードです。
ページデザインの変更
現在のコントロール部のデザインです。 サブの編集ウインドウを開く必要がないので、ページ幅を広くしてコントロール部のまとまりを良くしました。 また、リストの不要な部分を削って、ページの高さを低くしました。
現在のコード
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 💢」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。