別の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」から引用していて、他と区別できれば良い適当な部分です。

 

以下は、この方針で書き変えたコードです。 このコードはツールの先頭にあり、ツール起動時に、ストレージから「フォロワー履歴」を読込む部分です。

 

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'+
        '  が無いかを確認してください'); }

 

起動時に「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」の最新バージョンへのリンクは、以下のページのリンクリストから探せます。