「Bad Comment Delete」のコードは、システム側の更新で動作不能になり、修復を断念しましたが、このツールの全ての目的を達成する「コメント管理 / コメントブロック機能」が、アメーバから提供されています。
従って、「Bad Comment Delete」のツールとしての存在価値はなくなりましたが、部分的なコードを参考にする場合を考慮して、記事を残しています。
「ログアウトユーザー・外部ユーザー」に対するブロック
「ログアウトユーザー・外部ユーザー」は、10文字以内の自由な文字列をコメントユーザー名としてコメントが出来、その場合のアメーバIDは「無」になります。 アメーバのブロック機能は働かず、結局「ログアウトユーザー・外部ユーザー」のコメント遮断が必要になる場合が多いでしょう。 攻撃者は、すぐに代わりの方法を探します。
しかし懇意の外部ユーザーが居れば、コメントの遮断は辛いでしょう。 また、広くアメブロ外からのコンタクトを求めるブログもあると思います。
この場合、「Bad Comment Delete」は少しは役にたちます。 攻撃者が毎回同じコメントユーザー名を使う場合は、そのコメントユーザー名を登録して自動削除できるからです。 この機能は大きな期待はできないですが、どうしても外部のコメントを遮断したくない場合は、無いよりましです。
「ログアウトユーザー・外部ユーザー」を検索上で区別する
奸計に長けた攻撃者は、コメントが頻繁な他のユーザー名を「コメントユーザー名」として騙る可能性があります。 被害ユーザーがコメント選別の際に、誤って開く事を期待して、そういう事をするかも知れません。
これまでのコードは、外部コメントの「コメントユーザー名」と「アメーバID」を一緒に扱っていました。「コメントユーザー名」をブロック対象に登録すると、それと全く同じ文字列の「アメーバID」があれば、そのコメントも削除してしまいます。
そのため、「他のユーザーID名を騙る」コメントを受けた場合は、ブロック設定が出来ません。 この問題は、外部の「コメントユーザー名」と「アメーバID」を区別して扱えば無くせます。
具体的な方法は、外部の「コメントユーザー名」に関して、判定時・ブロック対象の登録時に、アメーバIDと区別するマークを付ける事にしました。
アメブロ外部からのコメントの特徴について
下図のサンプルコメントの上側は「外部コメント」で、下側はアメブロの「内部コメント」です。
「外部コメント」は「英大文字・小文字」「漢字」「一部のWin10絵文字」を、コメントユーザー名に使う事が出来ます。 これらのコメントユーザー名は、ブログページのコメント欄にもそのまま反映します。
一方、アメーバIDは「3文字以上24文字以内」の「英数小文字」で、記号文字は「-」(マイナス)のみ可という制限があります。
また上のリスト欄で、「外部コメント」の「アメーバID」部分が空白になっていますが、この表示の元となる「comment_url」が外部コメントには無いからです。
この特徴を利用して、「アメーバID」にはあり得ない「絵文字」などを外部からの「コメントユーザー名」に付加すれば、「アメーバID」と常に区別ができます。
「マーク付加」を実装した画面
最終的に小さな「▴」のマークを、処理過程で追加する事にしました。 受信コメントに「comment_url」が無ければ外部コメントと判断出来ます。 その場合は、「コメントユーザー名」に「▴」を追加して、それをブロック対象の検索にかけます。
一方、ブロック対象を登録する際、コメントに「comment_url」が無ければ、「コメントユーザー名」に「▴」を追加して、ブロック対象のデータ配列に加えます。 データ配列上で、「アメーバID」には「▴」が無く、外部の「コメントユーザー名」には「▴」が付きます。 これで同じ文字列でも、両者は完全に区別されます。
下は、「Stopman」で外部コメントを受けた場合で、これをブロック対象に登録している所ですが、登録は「▴Stopman」になります。
これで指定したコメントは削除され、削除リストには「▴Stopman」が登録されています。「▴」が付いているのが外部コメントの「コメントユーザー名」です。
削除処理の内容表示
これまでの削除処理の表示は「件数」だけでした。「アメーバID」「コメントユーザー名」のどれが削除されたかは表示がなく、実情がよく判りません。 そこで、削除した対象の「ブロック登録」とその件数を表示する様にしました。
これは、削除対象の登録数と同数の配列を用意して、削除処理ごとにカウントを記録します。 その内容を簡素な文字列にして、前回の「削除処理数」の代わりにローカルストレージに記録し、次の「処理待ち」画面に表示します。
下は、外部コメントの「▴Stopman」のコメント「1件」が削除された表示です。
下は「▴Stopman」のコメントが「2件」、「アメーバID」の「paperbackwriter」のコメントが「1件」削除された表示です。
この内容表示は、削除処理後に「処理待ち」画面に1回表示されるだけで、別画面を開くと消えます。 処理経過は控え目に表示します。
「Bad Comment Delete」ver. 0.5
このツールは Chrpme・新Edge / Firefox で動作を確認しています。 今後も登録データの引継ぎを考慮して、更新をして行く可能性があります。
以下のコードを「Tampermonkey」の新規スクリプトの編集画面にコピー&ペーストして「保存」する事で、ツールが利用できる様になります。 ペーストの際、編集画面の初期テンプレートを完全に空白にしてから、ペーストをしてください。
〔コピー方法〕 軽量シンプルなツール「PreBox Button 」を使うと
コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」
の操作で、掲載コードのコピーが可能になります。
〔 Bad Comment Delete 〕ver. 0.5
// ==UserScript== // @name Bad Comment Delete // @namespace http://tampermonkey.net/ // @version 0.5 // @description コメント管理画面におけるコメント自動削除機能 // @author Ameba Blog User // @match https://blog.ameba.jp/ucs/comment/comment* // @noframes // @run-at document-start // @grant none // ==/UserScript== let ua=0; let agent=window.navigator.userAgent.toLowerCase(); if(agent.indexOf('firefox') > -1){ ua=1; } // Firefoxの場合のフラッグ window.addEventListener('DOMContentLoaded', function(){ let path=document.location.pathname; if(path=='/ucs/comment/commentlist.do'){ // コメント管理画面の場合 let comm_block_data=[]; // 総合の配列 let comm_block_ary=[]; // ブロックID のみの配列 let comm_block_count=[]; // ブロック処理カウント let edit_mode=0; disp_environ(); let read_json=localStorage.getItem('comm_id_back'); // ローカルストレージ 保存名 comm_block_data=JSON.parse(read_json); if(comm_block_data==null){ comm_block_data=['BadCommDelete','stop','']; } comm_block_data[1]='stop'; // フラグ初期化 let announcer=document.querySelector('#announcer'); if(announcer){ if(comm_block_data[2]!=''){ announcer.textContent='Deleted'+ comm_block_data[2]; }} // 処理数報告 comm_block_data[2]=''; // フラグ初期化 let write_json=JSON.stringify(comm_block_data); localStorage.setItem('comm_id_back', write_json); // ローカルストレージ 保存 reg_set(); function reg_set(){ comm_block_ary=comm_block_data.slice(); comm_block_ary.splice(0, 3); comm_block_count=comm_block_ary.slice(); for(let k=0; k<comm_block_count.length; k++){ comm_block_count[k]=0; } list_set(); } function list_set(){ let list_select=document.querySelector('#list_select'); if(list_select){ list_select.innerHTML=''; // リセット let option0=document.createElement("option"); option0.setAttribute('hidden', ''); option0.text='ブロック指定 - 登録解除'; option0.value=0; list_select.appendChild(option0); for(let k=3; k<comm_block_data.length; k++){ let option=document.createElement("option"); option.text=comm_block_data[k]; option.value=k; list_select.appendChild(option); }}} function disp_environ(){ let announcer=document.createElement('span'); announcer.setAttribute('id', 'announcer'); let b_panel=document.createElement('li'); b_panel.setAttribute('id', 'b_panel'); let comm_set=document.querySelector('ul.commentSetting'); if(comm_set){ comm_set.appendChild(announcer); comm_set.appendChild(b_panel); b_panel.innerHTML= '<input id="export" type="submit" value="Export">'+ '<input id="import" type="submit" value="Import">'+ '<input id="reader" type="file" value="">'+ '<select id="list_select">'+ '</select>'; } let css= '#ucsMainLeft { position: relative; } '+ '#b_panel { position: absolute; top: 8px; right: 15px; display: none; } '+ '#list_select { width: 230px; height: 26px; padding: 1px 15px 0; '+ 'font-size: 15px !important; background: #fff; } '+ '#list_select option { font-size: 16px; } '+ '#export { font-size: 17px; padding: 2px 8px 0; margin-right: 10px; '+ 'height: 26px; vertical-align: -3px; } '+ '#import { font-size: 17px; padding: 2px 8px 0; margin-right: 15px; '+ 'height: 26px; vertical-align: -3px; } '+ '#reader{ display: none; }'+ '#announcer { position: absolute; top: 12px; right: 30px; font-size: 16px; }'; if(ua==1){ css+='#export, #import { padding: 0 8px 0; height: 27px; }'; } let style=document.createElement('style'); style.setAttribute('id', 's_list'); style.innerHTML=css; let target=document.querySelector('body'); if(!target.querySelector('#s_list')){ target.appendChild(style); } block_manage(); } function block_manage(){ let ex_file=document.querySelector('#export'); ex_file.onclick=function(){ let write_json=JSON.stringify(comm_block_data); let blob=new Blob([write_json], {type: 'application/json'}); let a_elem=document.createElement('a'); a_elem.href=URL.createObjectURL(blob); a_elem.download='Bad_Comment_Delete.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 im_file=document.querySelector('#import'); im_file.onclick=function(){ file_read.click(); } let file_read=document.querySelector('#reader'); file_read.addEventListener("change" , function(){ if(!(file_read.value)) return; // ファイルが選択されない場合 let file_list=file_read.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, 16)=='["BadCommDelete"'){ // ファイルデータの確認 comm_block_data=JSON.parse(file_reader.result); // 読出して配列を上書きする let write_json=JSON.stringify(comm_block_data); localStorage.setItem('comm_id_back', write_json); // ローカルストレージ 保存 reg_set(); alert("✅ ブロック指定のデータを読込みました\n"+ " 読込んだファイル名: " + file.name); } else{ alert("❌ Bad Comment Delete の Exportファイルではありません\n"+ " Importファイル名は「Bad_Comment_Delete ... 」で始まります"); }}}); let list_select=document.querySelector('#list_select'); list_select.addEventListener('change', (event)=>{ let sel_id=list_select.options[list_select.selectedIndex].text; let sel_index=list_select.selectedIndex+2; let ok=confirm(" ⛔ "+ sel_id +" のブロック指定を解除しますか?"); if(ok){ comm_block_data.splice(sel_index, 1); reg_set(); let write_json=JSON.stringify(comm_block_data); localStorage.setItem('comm_id_back', write_json); } // ローカルストレージ 保存 else{ list_set(); }}); } // block_manage() let target=document.querySelector('body'); // 監視 target let monitor=new MutationObserver(main); monitor.observe(target, {childList: true}); // 監視開始 function main(){ comm_blocker(); checker(); let b_panel=document.querySelector('#b_panel'); let commHeader=document.querySelector('#ucsMainLeft h1'); if(commHeader){ commHeader.textContent='💛 コメント管理'; commHeader.style.paddingLeft='5px'; commHeader.onclick=function(){ if(edit_mode==0){ edit_mode=1; commHeader.style.color='#fff'; commHeader.style.background='red'; b_panel.style.display='block'; list_set(); link_prevent(); comm_blocker(); } else{ edit_mode=0; commHeader.style.color='#444'; commHeader.style.background='none'; b_panel.style.display='none'; link_prevent(); comm_blocker(); }}} } // main() function link_prevent(){ let comm_li=document.querySelectorAll('#comments li'); if(comm_li.length!=0){ for(let k=0; k<comm_li.length; k++){ if(edit_mode==1){ comm_li[k].querySelector('#comments .left h3').style.pointerEvents='none'; comm_li[k].querySelector('#comments .left h4').style.pointerEvents='none'; comm_li[k].querySelector('#comments .right').style.pointerEvents='none'; comm_li[k].querySelectorAll('#comments .right a')[0].style.pointerEvents='none'; comm_li[k].querySelectorAll('#comments .right a')[1].style.pointerEvents='none'; let public_a=comm_li[k].querySelector('#comments .right publicconfirm a'); if(public_a){ public_a.style.pointerEvents='none'; } let cmtTxt_a=comm_li[k].querySelector('#comments .cmtTxt a'); if(cmtTxt_a){ cmtTxt_a.style.pointerEvents='none'; }} if(edit_mode==0){ comm_li[k].querySelector('#comments .left h3').style.pointerEvents='auto'; comm_li[k].querySelector('#comments .left h4').style.pointerEvents='auto'; comm_li[k].querySelector('#comments .right').style.pointerEvents='auto'; comm_li[k].querySelectorAll('#comments .right a')[0].style.pointerEvents='auto'; comm_li[k].querySelectorAll('#comments .right a')[1].style.pointerEvents='auto'; let public_a=comm_li[k].querySelector('#comments .right publicconfirm a'); if(public_a){ public_a.style.pointerEvents='auto'; } let cmtTxt_a=comm_li[k].querySelector('#comments .cmtTxt a'); if(cmtTxt_a){ cmtTxt_a.style.pointerEvents='auto'; }}}}} function comm_blocker(){ let comm_name=[]; let count=0; let comm_li=document.querySelectorAll('#comments li'); if(comm_li.length!=0){ for(let k=0; k<comm_li.length; k++){ if(comm_li[k].querySelector('input[name="comment_name"]')){ let comm_url=comm_li[k].querySelector('input[name="comment_url"]').value; if(!comm_url){ comm_name[k]= '▴'+comm_li[k].querySelector('input[name="comment_name"]').value; } else{ comm_name[k]= comm_li[k].querySelector('input[name="comment_name"]').value; } let index=comm_block_ary.findIndex(item=>item===comm_name[k]); if(index!=-1){ count+=1; comm_block_count[index]+=1; comm_li[k].querySelector('input[name="del_index"]').checked=true; }}}} if(count!=0){ comm_block_data[1]='delete'; // 削除フラグを設定 let result=''; for(let k=0; k<comm_block_ary.length; k++){ if(comm_block_count[k]!=0){ result+=' : '+ comm_block_ary[k] +' -'+ comm_block_count[k]; }} comm_block_data[2]=result; // 削除したコメント内訳を保存 let write_json=JSON.stringify(comm_block_data); localStorage.setItem('comm_id_back', write_json); // ローカルストレージ 保存 let btn_del=document.querySelector('.actionControl .btnDelete'); if(btn_del){ btn_del.click(); }} } // comm_blocker() function checker(){ let comm_name=[]; let comm_li=document.querySelectorAll('#comments li'); if(comm_li.length!=0){ for(let k=0; k<comm_li.length; k++){ if(comm_li[k].querySelector('input[name="comment_name"]')){ let comm_url=comm_li[k].querySelector('input[name="comment_url"]').value; if(!comm_url){ comm_name[k]= '▴'+comm_li[k].querySelector('input[name="comment_name"]').value; } else{ comm_name[k]= comm_li[k].querySelector('input[name="comment_name"]').value; } comm_li[k].onclick=function(event){ // リストのクリックで設定 set_guard(k); }}} function set_guard(n){ if(edit_mode==1){ let index=comm_block_ary.findIndex(item=>item===comm_name[n]); if(index==-1){ comm_li[n].style.outline='solid 2px red'; comm_li[n].style.outlineOffset='-2px'; setTimeout(()=>{ let ok= confirm(" ⛔ "+ comm_name[n] +" をブロック指定しますか?"); if(ok){ comm_block_data.push(comm_name[n]); reg_set(); let write_json=JSON.stringify(comm_block_data); localStorage.setItem('comm_id_back', write_json); // ストレージ 保存 comm_blocker(); } // 排除リストに従って削除を実行 else{ comm_li[n].style.outline='none'; } }, 20); }}} }} // checker() }}); window.addEventListener('DOMContentLoaded', function(){ let path=document.location.pathname; if(path=='/ucs/comment/commentconfirm.do'){ // 削除確認画面の場合 let comm_block_data=[]; // 総合ブロックデータ let read_json=localStorage.getItem('comm_id_back'); // ローカルストレージ 保存名 comm_block_data=JSON.parse(read_json); if(comm_block_data!=null){ if(comm_block_data[1]=='delete'){ // 削除実行フラグがある場合 let btn_del=document.querySelector( '.actionControl .btnPrimary[value="削除"]'); if(btn_del){ btn_del.click(); }}} }}); window.addEventListener('DOMContentLoaded', function(){ let path=document.location.pathname; if(path=='/ucs/comment/commentend.do'){ // 削除完了画面の場合 let comm_block_data=[]; // 総合ブロックデータ let read_json=localStorage.getItem('comm_id_back'); // ローカルストレージ 保存名 comm_block_data=JSON.parse(read_json); if(comm_block_data!=null){ if(comm_block_data[1]=='delete'){ // 削除実行フラグがある場合 comm_block_data[1]='stop'; // フラグを戻す let write_json=JSON.stringify(comm_block_data); localStorage.setItem('comm_id_back', write_json); // ローカルストレージ 保存 setTimeout(()=>{ window.location.href= 'https://blog.ameba.jp/ucs/comment/commentlist.do?public_comment_flg=false'; }, 20); }} }});