別のIDでログインした時の問題
色々なテストで必要なので、私はテスト用の別IDのブログを作っています。 そちらはフォロワーが無く、そこで「Authentic Reader」を起動して「履歴」を表示すると、このメインブログの全フォロワーが「フォロー削除」をしたフォロワーとして表示されました。 これには驚きました。
「フォロワー履歴」はブラウザごとに1箇所のローカルストレージに保存しています。 ローカルストレージはブラウザが開いたドメインごとに管理され、別IDでログインしても、同じ「フォロワー履歴」が使われるのが原因でした。
結局、ver.0.2 までのコードでは、別IDでログインすると「フォロワー履歴」の混合が行われます。 いったん混合されると、ログインした側の「フォロワーリスト」に無い別IDのフォロワーが、全て「履歴のみのフォロワー」として扱われます。
この場合、その「履歴だけのフォロワー」が「フォロワーリスト」にカラー表示される事は殆どないでしょうが、履歴期限切れの処理に別IDのリスト末尾の日付が使われるなど、想定外のことが生じ得ます。 とにかく、IDごとに別の「フォロワー履歴」を作る必要があります。
「フォロワー履歴」を ログインID ごとに管理する
最初に問題に気付いた時、対策にはかなりコード追加が必要と思いましたが、案外と簡単なコードで済みました。
ストレージ記録は、固有の「key」を指定してデータを書込みます。 データ読出し時はその「key」でアクセスするので、「key」をIDごとに変えれば良いわけです。
「UserID」は、管理ページのヘッダー部の「ログインユーザーID」から取得できるので、ストレージの保存「Key」は「AR_UserID」としました。 接頭部の「AR_」は「Authentic Reader」から引用していて、他と区別できれば良い適当な部分です。
以下は、この方針で書き変えたコードです。 このコードはツールの先頭にあり、ツール起動時に、ストレージから「フォロワー履歴」を読込む部分です。
起動時に「UserID」を取得しますが、何等かの理由でこれが取得出来なかった場合は、「フォロワー履歴」の読込みも保存も出来ません。 これは正常な動作にならないので、上記のコードの最後の警告表示を出す仕組みにしました。 この取得に失敗した状態は、次の「フォロワーリスト」のチェックでエラーが生じ、ツールの「コントロールパネル」を表示せずに停止します。(一般の操作は続行出来ます)
正常に「UserID」を取得できた場合は、次にストレージから「フォロワー履歴」を読込みます。 もし「フォロワー履歴」が無い場合は、このツールを最初に使った時です。 その場合は、空のフォロワー履歴「['name', '1990年time', '0']」を生成して、ストレージに保存します。 これは、これ以降の処理コードが履歴の1個が必要なためで、いわば「呼び水」のデータです。
この空データは、実際のフォロワーが1件でも出来ると、その日付より「1990年」が旧いと判断され、削除されて任務を終えます。
今回の問題で、上記の起動時のコードのみ書換えましたが、導入部がより強固になったと思います。
「Authentic Reader」ver.0.3 テスト版
「Authentic Reader」ver.0.3 は、手直しが必要なテスト版です。 操作を誤って実際のフォロワー登録を削除しない様に、もしテストされる場合は注意してください。
「Export」「Import」のボタンは動作しません。「フォロワー履歴」の生成や、「フォロワー履歴」の管理機能は動作しますが、最初は「フォロワー履歴」= 現在の「フォロワーリスト」なので、「イエロー」の表示は当面はないと思います。
「Authentic Reader」を利用するには
このツールは Chrome / Edge / Firefox の拡張機能「Tampermonkey」上で動作します。 以下に、このツールの導入手順を簡単に説明します。
❶「Tampermonkey」を導入します
◎ 使用しているブラウザに拡張機能「Tampermonkey」を導入する事が必要です。
既に「Tampermonkey」を導入している場合は、この手順 ❶ は不要です。
拡張機能の導入については、以下のページに簡単な説明があるので参照ください。
❷「Tampermonkey」にスクリプトを登録します
◎「Tampermonkey」の「+」マークの「新規スクリプト」タブを開きます。
◎「新規スクリプト」には、最初からテンプレートが記入されています。 これは全て削除して、完全に空白の編集枠に 下のコードをコピー&ペーストします。
〔コピー方法〕 軽量シンプルなツール「PreBox Button 」を使うと
コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」
の操作で、掲載コードのコピーが可能になります。
◎ 最後に「ファイル」メニューの「保存」を押すと、ツールが使用可能になります。
〔 Authentic Reader 〕 ver. 0.3
// ==UserScript== // @name Authentic Reader // @namespace http://blog.ameba.jp // @version 0.3 // @description 自動プログラムの時限フォローを判定する // @author Ameba Blog User // @match https://blog.ameba.jp/ucs/reader/readerlist.do // @match https://blog.ameba.jp/ucs/reader/readerlist.do?pageID=1 // @icon https://www.google.com/s2/favicons?sz=64&domain=ameba.jp // @grant none // ==/UserScript== let readers={}; // フォロワー登録データ let deleted_reader; // フォロワーリスト表示数とフォロワー履歴数の差 let UserID; // アメーバログインID let amebaId=document.querySelector('.amebaId'); if(amebaId){ UserID=amebaId.textContent; } function write_local(){ let write_json=JSON.stringify(readers); if(UserID){ localStorage.setItem('AR_'+UserID, write_json); } // ローカルストレージ 保存名 else{ id_alert(); }} if(UserID){ let read_json=localStorage.getItem('AR_'+UserID); // ローカルストレージ 保存名 readers=JSON.parse(read_json); if(readers==null){ readers=[['name', '1990年time', '0']]; write_local(); }} else{ id_alert(); } function id_alert(){ alert( '⛔ ======== Authentic Reader ========\n'+ ' ユーザーIDが取得出来ないため処理を実行できません\n'+ ' ページのリロードを試し、さらにアメーバのログインに問題\n'+ ' が無いかを確認してください'); } check(); // フォロワー管理のトップページを開いた時にデータベースを更新 disp_list(); // フォロワーリストで属性のカラー表示 function disp_list(){ let table_tr=document.querySelectorAll('.tableList tbody tr'); for(let k=0; k<table_tr.length; k++){ let name=table_tr[k].querySelector('.name a').textContent; for(let i=0; i<readers.length; i++){ if(name==readers[i][0]){ table_tr[k].style.background=set_color(readers[i][2]); }}}} function deleted_count(){ // フォロワーリスト数と履歴データ数の差をチェック let deleted=0; // チェック時にリセット for(let i=0; i<readers.length; i++){ let table_tr=document.querySelectorAll('.tableList tbody tr'); let live=0; for(let k=0; k<table_tr.length; k++){ let name=table_tr[k].querySelector('.name a').textContent; if(readers[i][0]==name){ live+=1; }} if(live==0){ deleted +=1; }} return deleted; } function disp_deleted_count(){ let data_disp=document.querySelector('#inner_AR2 .data_disp'); if(data_disp){ data_disp.textContent='hidden: '+ deleted_reader; }} control_panel(); // コントロールパネルを表示 function control_panel(){ let style= '#panel_AR { position: absolute; z-index: 1; top: 55px; left: calc(50% + 57px); '+ 'display: flex; align-items: center; '+ 'font: bold 16px/16px Meiryo; color: #666; background: #e0f0fd; '+ 'width: 330px; height: 30px; padding: 3px 0 1px 15px; '+ 'border: 1px solid #ccc; border-radius: 2px; } '+ '#inner_AR1, #inner_AR2 { margin-bottom: 3px; } '+ '.swe, .sec { cursor: pointer; } '+ '.swe { font: normal 16px Meiryo; padding: 1px 6px 0; height: 28px; '+ 'margin-right: 12px; } '+ '.data_disp { display: inline-block; width: 115px; height: 28px; text-align: center; '+ 'padding: 6px 6px 0; margin-right: 12px; font: normal 16px/16px Meiryo; '+ 'color: #000; background: #fff; border: 1px solid #ccc; box-sizing: border-box; } '+ '.swc { font: normal 16px Meiryo; padding: 1px 4px 0; height: 28px; } '+ '.help_AR { height: 18px; width: 18px; margin: 0 45px -3px 12px; cursor: pointer; } '+ '.setting_AR { margin: 0 0 -2px 4px; } '+ '#inner_AR2 { display: none; }'; let SVG_h= '<svg class="help_AR" viewBox="0 0 150 150">'+ '<path d="M66 13C56 15 47 18 39 24C-12 60 18 146 82 137C92 '+ '135 102 131 110 126C162 90 128 4 66 13M68 25C131 17 145 117 81 '+ '125C16 133 3 34 68 25M69 40C61 41 39 58 58 61C66 63 73 47 82 57C84 '+ '60 83 62 81 65C77 70 52 90 76 89C82 89 82 84 86 81C92 76 98 74 100 66'+ 'C105 48 84 37 69 40M70 94C58 99 66 118 78 112C90 107 82 89 70 94z">'+ '</path></svg>'; let SVG_g= '<svg class="setting_AR" height="16" width="16" viewBox="0 0 256 256">'+ '<path d="M114 19C110 22 109 30 108 34C106 41 102 46 96 49C88 53 81 52 '+ '73 48C69 46 63 41 58 42C53 43 49 49 46 52C44 54 41 56 40 59C38 64 44 69 '+ '46 73C50 82 52 90 47 99C42 107 35 109 27 112C23 113 18 114 16 118C15 124 '+ '16 132 16 138C16 140 16 143 18 145C20 148 25 149 28 150C36 152 44 155 48 '+ '164C52 173 50 180 46 188C44 192 38 198 41 203C42 206 45 208 47 210C50 '+ '213 54 218 58 219C62 220 68 216 71 214C78 210 84 208 92 210C99 213 105 '+ '218 107 225C109 230 110 239 114 242C117 244 123 243 126 243C130 243 138 '+ '244 141 241C146 238 146 229 148 224C151 216 159 210 168 209C175 208 182 '+ '213 188 216C191 218 195 221 199 218C204 216 208 210 212 206C213 204 215 '+ '202 215 200C215 196 212 193 210 190C206 182 203 175 206 166C210 157 217 '+ '153 225 150C229 149 237 148 239 143C240 137 239 129 239 123C239 121 239 '+ '118 237 116C235 113 229 112 226 111C218 108 210 105 207 96C203 86 206 80 '+ '210 71C212 68 215 65 215 61C215 59 213 57 212 55C208 51 204 45 199 43C195 '+ '40 191 43 188 45C181 48 174 54 166 52C158 50 151 45 148 37C146 32 146 22 '+ '141 19C137 17 129 18 125 18C122 18 117 17 114 19z" style="fill:#000"></path>'+ '<path d="M123 70C116 71 109 72 103 75C82 85 69 106 68 129C66 162 97 195 '+ '131 191C162 187 185 164 187 132C189 99 157 66 123 70z" style="fill:#fff">'+ '</path></svg>'; let panel= '<div id="panel_AR">'+ '<div id="inner_AR1">'+ 'Authentic Reader<span>'+ SVG_h +'</span>'+ '<button class="mentenance swe" type="submit" >Setting'+ SVG_g +'</button>'+ '</div>'+ '<div id="inner_AR2">'+ '<input class="export swe" type="submit" value="Export">'+ '<input class="import swe" type="submit" value="Import">'+ '<span class="data_disp"> </span>'+ '<input class="close swc" type="submit" value="✖"></div>'+ '<style>'+ style +'</style></div>'; let panel_AR=document.querySelector('#F_AR'); if(!panel_AR){ document.querySelector('body').insertAdjacentHTML('beforeend', panel); } let AR1=document.querySelector('#inner_AR1'); let AR2=document.querySelector('#inner_AR2'); let mentenance=document.querySelector('.mentenance'); let close=document.querySelector('.close'); if(mentenance && AR1 && AR2){ mentenance.onclick=function(){ AR1.style.display='none'; AR2.style.display='block'; database(); disp_deleted_count(); }} if(close && AR1 && AR2){ close.onclick=function(){ AR1.style.display='block'; AR2.style.display='none'; database_close(); disp_list(); }} own_delete(); } // control_panel() function check(){ // データベースの更新 let table_tr=document.querySelectorAll('.tableList tbody tr'); for(let k=0; k<table_tr.length; k++){ let name=table_tr[k].querySelector('.name a').textContent; let time=table_tr[k].querySelector('.rdrCmnt span').textContent; let count=0; for(let i=0; i<readers.length; i++){ if(name==readers[i][0]){ count+=1; if(time!=readers[i][1]){ // 同じデータは更新しない if(readers[i][2]=='0'){ readers[i][2]='1'; // 時限フォロー readers[i][1]=time; // 今回の日付に差換え(更新データ) table_tr[k].style.background=set_color('1'); } else if(readers[i][2]=='1'){ readers[i][2]='1'; // 時限フォロー readers[i][1]=time; // 今回の日付に差換え(更新データ) table_tr[k].style.background='red'; } else if(readers[i][2]=='2'){ // こちらでフォロー削除 readers[i][1]=time; // 今回の日付に差換え(更新データ) table_tr[k].style.background=set_color('2'); }}}} if(count==0){ // 同データが無い新しい登録 readers.push([name, time, '0']); }} readers.sort((a, b)=>{ return b[1].replace(/[^0-9]/g, '')*1 - a[1].replace(/[^0-9]/g, '')*1 }); setTimeout(()=>{ let last_time=table_tr[table_tr.length-1]; if(last_time){ last_time= last_time.querySelector('.rdrCmnt span').textContent.replace(/[^0-9]/g, '')*1; for(let i=0; i<readers.length; i++){ if(readers[i][1].replace(/[^0-9]/g, '')*1 < last_time){ if(readers[i][2]!='1'){ readers.splice(i, 1); }}}} deleted_reader=deleted_count(); write_local(); }, 500); } // check() function own_delete(){ let table_tr=document.querySelectorAll('.tableList tbody tr'); for(let k=0; k<table_tr.length; k++){ let name=table_tr[k].querySelector('.name a').textContent; let btnDelete=table_tr[k].querySelector('.btnDelete'); btnDelete.onmouseup=function(){ let AppButton=document.querySelector('.minimumApplyButton a:first-child'); if(AppButton){ AppButton.addEventListener('mouseup', function(){ for(let i=0; i<readers.length; i++){ if(name==readers[i][0]){ if(readers[i][2]=='0'){ // フラグ無しフォロワーをユーザー側で削除した場合 readers[i][2]='2'; } // オウン削除のフラグを設定 write_local(); }}}); }}}} function database(){ disp_datalist(); change_color(); remove_data(); } function disp_datalist(){ let thead_AR= '<span class="thead_AR">履歴属性'+ '<style>'+ '.tableList thead .closeRow { padding: 6px 124px 4px 20px; } '+ '.tableList .thead_AR { margin-left: 55px; } '+ '.tableList .rdrCmnt p { width: 400px; } '+ '.tableList tbody .closeRow { position: relative; width: 140px !important; '+ ' box-shadow: none !important; } '+ '.tableList .color.btn { position: absolute; top: 19px; left: 120px; '+ 'width: 24px; height: 24px; cursor: pointer; }'+ '.btnDelete.AR { width: 90px !important; margin-right: -30px; '+ 'background-color: #bedcf7 !important; } '+ '</style></span>'; let thead_closeRow=document.querySelector('.tableList thead .closeRow'); if(thead_closeRow && !document.querySelector('.thead_AR')){ thead_closeRow.insertAdjacentHTML('beforeend', thead_AR); } let SVG_user= '<svg viewBox="0 0 240 240">'+ '<path d="M0 0L0 240L240 240L240 0L0 0z" style="fill:#fff;"></path>'+ '<path d="M118 32C111 32 104 33 98 36C79 45 73 67 72 86C71 98 72 113 80 '+ '123C92 136 114 135 129 134C138 133 149 131 156 124C165 115 166 103 166 '+ '91C166 63 151 29 118 32M37 224L201 224C194 199 186 177 163 162C128 139 '+ '81 148 55 180C45 193 40 208 37 224z" style="fill:#d4e7f5;"></path></svg>'; for(let i=0; i<readers.length; i++){ // リストにデータベースの全フォロワーを追加表示 insert(i); } disp_list(); // 生成したデータベースリストに属性色表示 function insert(n){ let table_tr=document.querySelectorAll('.tableList tbody tr'); if(table_tr.length==0){ // リストにフォロワーが無い場合 creat_tr3(n); } else{ // リストにフォロワーが1人でもある場合 for(let k=0; k<table_tr.length; k++){ let name=table_tr[k].querySelector('.name a').textContent; let time=table_tr[k].querySelector('.rdrCmnt span').textContent; let time_d=time.replace(/[^0-9]/g, '')*1; if(readers[n][1].replace(/[^0-9]/g, '')*1 > time_d){ creat_tr1(n, table_tr[k]); // 削除されたフォロワー break; } else if(readers[n][1].replace(/[^0-9]/g, '')*1 == time_d){ if(readers[n][0]==name){ // 登録更新が無い正常フォロワー creat_tr2(n, table_tr[k]); break; }} // 登録日時が同じ別フォロワーがある場合は、次行でヒットするか // ヒットが無い場合は 次行の上に削除されたフォロワー行が作成される else if(readers[n][1].replace(/[^0-9]/g, '')*1 < time_d){ if(k==table_tr.length-1){ // 末尾行よりデータが旧い creat_tr3(n); }}}} function creat_tr1(m, ta_tr){ ta_tr.insertAdjacentHTML('beforebegin', tr_org(m)); } function creat_tr2(m, ta_tr){ let c_btn='<input type="button" value="" class="color btn">'; let btnDelete=ta_tr.querySelector('.btnDelete'); if(btnDelete){ btnDelete.insertAdjacentHTML('afterend', c_btn); }} function creat_tr3(m){ let tbody=document.querySelector('.tableList tbody'); if(tbody){ tbody.insertAdjacentHTML('beforeend', tr_org(m)); }} function tr_org(index){ let tr_text= '<tr class="history">'+ '<td><p class="profThmb"><a class="thumb">'+ SVG_user +'</a></p></td>'+ '<td class="rdrCmnt"><p class="name">'+ '<a target="_blank" href="https://profile.ameba.jp/ameba/'+ readers[index][0] + '">'+ readers[index][0] +'</a>さん</p>'+ '<a class="blogLink"></a><span>'+ readers[index][1] +'</span></td>'+ '<td class="openRow"></td>'+ '<td class="closeRow">'+ '<input type="button" value="履歴削除" class="btnDelete AR">'+ '<input type="button" value="" class="color btn">'+ '</td></tr>'; return tr_text; } } // insert(n) } // disp_datalist() function database_close(){ let thead_AR=document.querySelector('.thead_AR'); if(thead_AR){ thead_AR.remove(); } let table_tr=document.querySelectorAll('.tableList tbody tr'); for(let k=0; k<table_tr.length; k++){ if(table_tr[k].classList.contains('history')){ table_tr[k].remove(); } else{ let c_button=table_tr[k].querySelector('.color.btn'); if(c_button){ c_button.remove(); }}}} function set_color(flag){ if(flag=='0'){ return '#fff'; } if(flag=='1'){ return '#fe0'; } else{ return '#d6fed8' }} function change_color(){ let table_tr=document.querySelectorAll('.tableList tbody tr'); for(let k=0; k<table_tr.length; k++){ let name=table_tr[k].querySelector('.name a').textContent; let c_button=table_tr[k].querySelector('.color.btn'); c_button.onclick=function(){ for(let i=0; i<readers.length; i++){ if(name==readers[i][0]){ readers[i][2]=((readers[i][2]+1)%3).toString(); write_local(); table_tr[k].style.background=set_color(readers[i][2]); }}} }} // change_color() function remove_data(){ let table_tr=document.querySelectorAll('.tableList tbody tr'); for(let k=0; k<table_tr.length; k++){ let name=table_tr[k].querySelector('.name a').textContent; let d_button=table_tr[k].querySelector('.btnDelete.AR'); if(d_button){ d_button.onclick=function(){ for(let i=0; i<readers.length; i++){ if(name==readers[i][0]){ let result=window.confirm( '💢 この行のユーザーは、現在はフォロワーではありません。\n'+ ' 「履歴削除」を実行すると、このユーザーを追跡する\n'+ ' 情報は 完全にクリアーされます。\n\n'+ ' 履歴削除をする場合は「OK」をクリックしてください。'); if(result){ readers.splice(i, 1); write_local(); disp_datalist(); }}}}} }} // remove_data()
「Authentic Reader」最新版について
旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。
●「Authentic Reader」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。