「ファイル読込み」時の判定を厳密にしました
ファイル機能のテストを行っていると、「履歴データの混合」についての対策が甘いと感じました。
複数のアメーバIDを使い分けている場合、通常はフォロワーはブログごとに異なり、これが混ざると「フォロワー履歴」の修復が必要になります。 別IDのフォロワーは現在のフォロワーリストにないので、「フォロー削除」をした「フォロワー」と判断されるからです。
ローカルストレージの「保存Key」はユーザーIDで完全に分別しています。 また「フォロワー履歴」のファイルは、ファイル名にユーザーIDを追加して、見分けられる様にしています。 しかし、ファイル読込み時に、意図的に別IDのファイルを読込むことが可能でした。
ver.0.4 のコードは、「ファイル名」の先頭が「auth_reader」であれば、読込みを許可していました。 その後に続く「ユーザーID」までの一致が無いと読み込めないコードにすべきでしたが、開発時テストで不便なので後回しになっていました。
下は ver.0.4 のファイル読込みコードを簡略化したものです。(ver.0.4 452行~)
太字部分でファイル名を判定していますが、これを改善しました。
「フォロワー履歴」のリセット機能を追加しました
履歴データの混入は、上記の対策でほぼ防げると思います。 ただ、「フォロワー履歴」のリセット機能を作る事は、これまで迷いがあって作らずに来ました。
「Authentic Reader Storage Manager 」は、その迷いの結果で制作したのですが、今回の対策で、もう少し簡単にリセット出来る方が良いと考えました。
最初は、ファイル読込みは単純な履歴の「上書き」仕様でコードを作りましたが、それでは「イエロー」フラグの履歴を引き継げないので、「差分追加」仕様のコードに改めました。 しかしこの仕様は、履歴に無いデータを読込みごとに追加するので、履歴の混入が生じると、大変に手間な事態になります。
そこから、「差分追加」仕様にすると、履歴のリセット(履歴を削除して初期化)が必要になる場合が有り得ると考えが固まりました。 上記の「Storage Manager」を使う事でリセットが可能ですが、「Authentic Reader」にリセット機能を追加してしまえば、手間要らずです。 リセットについては、まわりくどい事をして来ましたが、この ver.0.5 で追加しました。
● 安易なリセットを避けるため、「Ctrl」+「Import」をスイッチにしています。
少しおおげさですが、以下の注意書きが出ます。
「OK」を押すと「フォロワー履歴」のデータが削除され、ツールが初期化されます。
「Authentic Reader」を利用するには
このツールは Chrome / Edge / Firefox版の拡張機能「Tampermonkey」上で動作します。 以下に、このツールの導入手順を簡単に説明します。
❶「Tampermonkey」を導入します
◎ 使用しているブラウザに拡張機能「Tampermonkey」を導入する事が必要です。
既に「Tampermonkey」を導入している場合は、この手順 ❶ は不要です。
拡張機能の導入については、以下のページに簡単な説明があるので参照ください。
❷「Tampermonkey」にスクリプトを登録します
◎「Tampermonkey」の「+」マークの「新規スクリプト」タブを開きます。
◎「新規スクリプト」には、最初からテンプレートが記入されています。 これは全て削除して、完全に空白の編集枠に 下のコードをコピー&ペーストします。
〔コピー方法〕 軽量シンプルなツール「PreBox Button 」を使うと
コード枠内を「Ctrl+左Click」➔「Copy code 」を「左Click」
の操作で、掲載コードのコピーが可能になります。
◎ 最後に「ファイル」メニューの「保存」を押すと、ツールが使用可能になります。
〔 Authentic Reader 〕 ver. 0.5
// ==UserScript==
// @name Authentic Reader
// @namespace http://blog.ameba.jp
// @version 0.5
// @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; } '+
'.file_input { display: none; } '+
'.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">'+
'<input class="file_input" type="file">'+
'<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();
own_delete();
AR_backup(); }}
if(close && AR1 && AR2){
close.onclick=function(){
AR1.style.display='block';
AR2.style.display='none';
database_close();
disp_list();
own_delete(); }}
let help=document.querySelector('.help_AR');
if(help){
help.onclick=function(){
window.open("https://ameblo.jp/personwritep/entry-12794112842.html", '_blank'); }}
} // 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 now=new Date();
let year=now.getFullYear()-1; // 1年前 🔴🔴「-2」2年前までになります 🔴🔴
let month=now.getMonth()+1;
let date=now.getDate();
let last_time=year*10000 + month*100 + date; // データの有効期限を1年とする
readers=readers.filter(value=>{
if(value[1].replace(/[^0-9]/g, '')*1 > last_time || value[2]=='1'){
return true; }});
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(){
set_2(name); }); }}}
function set_2(name_){
for(let i=0; i<readers.length; i++){
if(name_==readers[i][0]){
if(readers[i][2]=='0'){ // フラグ無しフォロワーをユーザー側で削除した場合
readers[i][2]='2'; } // オウン削除のフラグを設定
write_local(); }}}
} // own_delete()
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');
if(c_button){
c_button.onclick=function(){
tr_color(name);
disp_list(); }}}
function tr_color(name_){
for(let i=0; i<readers.length; i++){
if(name_==readers[i][0]){
readers[i][2]=((readers[i][2]+1)%3).toString();
write_local(); }}}
} // 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(){
off_history(name); }}}
function off_history(name_){
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();
window.location.reload(true); }}}}
} // remove_data()
function AR_backup(){
let exp=document.querySelector('#inner_AR2 .export');
let imp=document.querySelector('#inner_AR2 .import');
let file_input=document.querySelector('#inner_AR2 .file_input');
if(exp){
exp.onclick=function(){
let write_json=JSON.stringify(readers); //「フォロワー履歴」を書出す
let blob=new Blob([write_json], {type: 'application/json'});
let a_elem=document.createElement('a');
a_elem.href=URL.createObjectURL(blob);
a_elem.download='auth_reader_'+ UserID +'.json'; // 保存ファイル名
a_elem.click();
URL.revokeObjectURL(a_elem.href); }}
if(imp && file_input){
imp.onclick=function(event){
if(!event.ctrlKey){
file_input.click(); }
else{
let result=window.confirm(
'💢 === すべての「フォロワー履歴」を削除して初期化します ===\n'+
' 誤って別のUserIDのファイルを読込むと、無関係な履歴が混入します。\n'+
' 無関係なリスト行は「履歴削除」で削除できますが、削除するリスト行\n'+
' が多い場合は手間がかかります。\n'+
' 「フォロワー履歴」の初期化をすると有効な履歴も失われますが、履歴\n'+
' が混入した時の最短の修復方法です。 また、最近の「フォロワー履歴」\n'+
' のファイルがあれば、初期化後に読込むことで履歴を復元できます。\n\n'+
' 「フォロワー履歴」を初期化する場合は「OK」をクリックしてください。');
if(result){
localStorage.removeItem('AR_'+UserID); // ローカルストレージ key名を指定して削除
window.location.reload(true); // 再起動でストレージを初期化
}}}
file_input.addEventListener('change' , function(){
if(!(file_input.value)) return; // ファイルが選択されない場合
let file_list=file_input.files;
if(!file_list) return; // ファイルリストが選択されない場合
let file=file_list[0];
if(!file) return; // ファイルが無い場合
if(file.name.startsWith('auth_reader_'+ UserID)){ // ファイル名の確認
let file_reader=new FileReader();
file_reader.readAsText(file);
file_reader.onload=function(){
let data_in=JSON.parse(file_reader.result);
add_readers(data_in);//「フォロワー履歴」の統合
write_local(); // ローカルストレージに保存
database_close();
setTimeout(()=>{
alert(
"✅ フォロワー履歴データを読込みました\n"+
" 読込んだファイル名: " + file.name);
window.location.reload(true);
}, 200); }}
else{
alert(
"❌ Authentic Reader の Exportファイルではありません\n"+
" Importファイルは「auth_reader ... 」の名前です"); }
}); }
function add_readers(data){
for(let d=0; d<data.length; d++){
compare(data[d]); }
function compare(data_){
let count=0;
for(let i=0; i<readers.length; i++){
if(data_[0]==readers[i][0]){
count+=1;
// 日付が新しい場合のみ上書き
if(data_[1].replace(/[^0-9]/g, '')*1>readers[i][1].replace(/[^0-9]/g, '')*1){
readers[i][1]=data_[1];
readers[i][2]=data_[2]; }}}
if(count==0){
readers.push(data_); }}}
} // AR_backup()
「Authentic Reader」最新版について
旧いバージョンの JavaScriptツールは、アメーバのページ構成の変更で動作しない場合があり、導入する場合は最新バージョンをお勧めします。
●「Authentic Reader」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。





