「Bad Comment Delete」のコードは、システム側の更新で動作不能になり、修復を断念しましたが、このツールの全ての目的を達成する「コメント管理 / コメントブロック機能」が、アメーバから提供されています。
従って、「Bad Comment Delete」のツールとしての存在価値はなくなりましたが、部分的なコードを参考にする場合を考慮して、記事を残しています。
特定の語句でブロックする「Word Block」を実装
アメーバ純正のアメーバIDブロックや、この「Bad Comment Delete」のブロックを使えば、アメブロの内部コメントは、ほぼ完全にブロックが出来ます。 しかし、外部コメントを受け入れた場合に、攻撃者の外部コメント経由のコメントには、自動ブロックが難しくなります。
「コメントユーザー名」による外部コメントのブロック機能は作ったのですが、攻撃者がランダムな「コメントユーザー名」を使うと対処ができません。 しかし、攻撃者がコメント文で頻繁に使う「特徴的な語句」を指定して、その「語句」を含むコメントを自動削除する「キーワードブロック」という手法があります。
これは案外と効果を発揮する場合があり、私は携帯の「詐欺メール」の排除に使っています。 これと同様の機能の「Word Block」を制作しました。
「Word Block」は使い方しだい
アメブロのシステムには「不適当な語」をチェックするキーワードブロックが働き、明らかに異常な言葉は送信できなくなる様です。「Word Block」にそんな「語句」を指定しても良いのですが、攻撃者の「特徴的」な言い回し等を指定した方が、効果が高いと思われます。 その「語句」が、一般のコメントには含まれ難いものなら、排除の効率を高めることができます。
たとえば、口癖や方言とか、句読点の使い方、余り他で見ないフェイスマーク(フェイスマーク は → ('◇')ゞ などの表現)など、特徴的な文字列に着目します。
外部コメントの量にも拠りますが、普通のユーザーのコメントにもよく現れる「語句」を「Word Block」に指定すると、ヒットしたまともなコメントが自動削除されてしまいます。 この機能は、登録する「語句」の選択しだいで、効果が決まります。
「Word Block」の操作
「Word Block」のスイッチは「編集モード」の赤いタイトル上に表示されます。
これをクリックすると、下の様に「検索語句の入力枠」が表示されます。
「特徴的」な語句を入力して登録しますが、これは一般の入力枠と少し違います。
「検索語句」を登録する
「検索語句」を登録すると、その語句は「アメブロID」と同様にローカルストレージに記憶され、「Export」でファイルにバックアップ保存する事が出来ます。
● 単純なひと続きの文字列の場合は、その文字列と全く同じ語句がコメント文内にあれば、そのコメントは削除対象になります。(完全一致)
「原因結果」「了承、致しました。」「2020年9月」等、一続きの文字列が1個の場合は、普通の検索と同じです。
● 空白文字で文字列が別けられている場合、空白文字を含めたひと続きの文字列が検索語として検索が実行されます。
「原因 結果」の検索語指定では、「原因」や「結果」だけの語句はヒットしません。 もちろん「原因」の検索語指定では、「原因結果」「原因 結果」はヒットします。
● 複数の検索語を指定して、そのどれかが含まれる場合を削除対象とする事ができます。 複数指定は、半角英数の「|」で区切って、検索語を併記します。
「原因|結果」の入力で、「原因」と「結果」の語句のどちらかまたは両方を含むコメントを、削除対象とします。
「原因|結果|起承転結」は、「原因」と「結果」と「起承転結」の3つどれか、またはその複数を含むコメントを削除対象とします。(OR検索)
語句の入力上の注意
● 文字列を枠内に記入したら、最後に「Enter」を押します。 この「Enter」キーの入力により、記入された文字列が登録されます。 もし最後に押さないと、「Wb」ボタンを2度押すと元に戻ります。 登録状態を確かめるには「Wb」を2度押します。
● この検索語句の指定が無い場合は「コメントに特徴的な語句を指定する」の表示が出ます。 これはOKですが、もし半角空白・全角空白などを残した状態で登録されると、ほぼ全ての外部コメントが削除されますから、注意してください。
●「原因|」という「|」文字の後方の文字が無い指定は、全ての文字にヒットすることになり、同様に全ての外部コメントが削除されますから、注意が必要です。
● この「Word Block」機能は、アメブロ外部からのコメントのみに働きます。 アメブロ内部のコメントに検索語句があっても、内部コメントは削除はされません。
● この入力枠内の文字の指定は、「正規表現検索」の書式に準じています。
● 検索語句に以下の半角英文字(記号文字)が含まれていると、想定外の削除動作の原因になります。 これらは正規表現検索の特殊な動作を指定するもので、上記の複数指定の際に「|」を使う以外は、指定語句に含めない様に注意してください。
「Word Block」による削除処理の表示
「Word Block」が外部コメントを削除処理した場合、その直後の「承認待ち」の画面で「削除処理があった事」を表示します。 表示は「Wblock」として、削除個数のみ表示します。 このタイミングは、他の削除処理と同様です。
「Bad Comment Delete」ver. 0.6
このツールは Chrpme・新Edge / Firefox で動作を確認しています。
以下のコードを「Tampermonkey」の新規スクリプトの編集画面にコピー&ペーストして「保存」する事で、ツールが利用できる様になります。 ペーストの際、編集画面の初期テンプレートを完全に空白にしてから、ペーストをしてください。
〔コピー方法〕 軽量シンプルなツール「PreBox Button 」を使うと
コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」
の操作で、掲載コードのコピーが可能になります。
〔 Bad Comment Delete 〕ver. 0.6
// ==UserScript== // @name Bad Comment Delete // @namespace http://tampermonkey.net/ // @version 0.6 // @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=[]; // ブロック名 のみの配列 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, 4); // ブロック名だけの配列 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=4; k<comm_block_data.length; k++){ let option=document.createElement("option"); option.text=comm_block_data[k]; option.value=k-3; 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="cutlery" type="submit" value="Wb">'+ '<div id="boxin">'+ '<input id="cutbox" type="text" placeholder="コメントに特徴的な語句を指定する">'+ '</div>'+ '<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; background: red; '+ ' display: none; } '+ '#list_select { width: 230px; height: 26px; padding: 1px 15px 0; '+ 'font-size: 15px !important; background: #fff; } '+ '#list_select option { font-size: 16px; } '+ '#cutlery { font-size: 17px; padding: 2px 1px 0; margin-right: 15px; '+ 'height: 26px; vertical-align: -3px; } '+ '#boxin { position: absolute; top: 0; right: 0; width: 410px; background: red; } '+ '#cutbox { font-size: 16px; padding: 3px 8px 0; height: 19px; width: 280px; } '+ '#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+='#cutlery { padding: 0 8px; height: 27px; } '+ '#cutbox { padding: 1px 8px 0; height: 23px; vertical-align: -9px; } '+ '#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(); } // disp_environ() function block_manage(){ let cutlery=document.querySelector('#cutlery'); let boxin=document.querySelector('#boxin'); let cutbox=document.querySelector('#cutbox'); cutlery.onclick=function(){ if(boxin.style.display=='none'){ boxin.style.display='block'; cutbox.value=comm_block_data[3]; cutbox.onkeydown=function(event){ if(event.keyCode==13){ comm_block_data[3]=cutbox.value; let write_json=JSON.stringify(comm_block_data); localStorage.setItem('comm_id_back', write_json); }}} // ストレージ保存 else{ boxin.style.display='none'; }} 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+3; 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 boxin=document.querySelector('#boxin'); 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'; boxin.style.display='none'; 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 comm_text=[]; let count=0; let count_wb=0; let comm_li=document.querySelectorAll('#comments li'); if(comm_li.length!=0){ for(let k=0; k<comm_li.length; k++){ 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; 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(!comm_url){ // ブログ外コメント 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; } else{ comm_text[k]= comm_li[k].querySelector('input[name="comment_text"]').value; if(comm_block_data[3]!=''){ // 無入力時は動作させない。 let regex=new RegExp(comm_block_data[3]); let result=regex.test(comm_text[k]); if(result==true){ count_wb+=1; comm_li[k].querySelector('input[name="del_index"]').checked=true; }}} }} if(count!=0 || count_wb!=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]; }} if(count_wb!=0){ result+=' : Wblock -'+ count_wb; } 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); }} }});