Greasy Fork

Togetter User Ban

try to take over the world!

目前为 2019-07-09 提交的版本。查看 最新版本

// ==UserScript==
// @name         Togetter User Ban
// @namespace    https://greasyfork.org/ja/scripts/387330-togetter-user-ban
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        https://togetter.com/*
// @grant        none
// ==/UserScript==

/**
 * ユーザーを表すデータ型
 * @typedef {Object} User
 * @property {string} userId
 * @property {string} icon
 */

/**
 * @param {string} userId 
 * @param {string} icon 
 * @returns {User}
 */
const User = (userId, icon) => {
    return { userId: userId, icon: icon };
};

/**
 * user1がuser2と同じユーザーを示すか判別します。
 * @param {User} user1 
 * @param {User} user2 
 */
const isSameUser = (user1, user2) => {
    return (user1.userId === user2.userId);
};

/**
 * 与えられた時間だけスレッドを停止します。
 * @param {number} ms ミリ秒
 */
const sleep = ms => {
    return new Promise(resolve => {
        setTimeout(resolve, ms);
    });
};

/**
 * 文字列からHTMLElementを作り返します。
 * @param {string} h htmlを表現する文字列
 * @returns {HTMLElement}
 */
const html = h => {
    const div = document.createElement("div");
    div.insertAdjacentHTML("afterbegin", h);
    return div.firstChild;
}

/**
 * userListがuserを持っているか判別します。
 * @param {User[]} userList 
 * @param {User} user 
 */
const userContains = (userList, user) => userList.filter(i => isSameUser(i, user)).length > 0;

/**
 * objListをJSON CSVに変換して返します。
 * @param {{}[]} objList 
 */
const objListToJson = objList => objList.map(o => JSON.stringify(o)).join(",");

//-------------------------------------------------------------------------------------------------
const parser = {};

/**
 * strをstringの配列にして返します。
 * @param {string} str '["hoge", "fuga"]'のような配列を意味する文字列
 */
parser.list = str => str.replace(/\[|\]/g, "").split(/,(?={)/);

/**
 * strをオブジェクトの形式にして返します。
 * @param {string} str '{"name":"tanaka","sex":"male"}'のようなオブジェクトを意味する文字列
 */
parser.json = str => {
    const obj = {};
    str.replace(/"/g, "")    // '{"userId":"hoge","icon":"fuga.jpg"}' -> '{userId:hoge,icon:fuga.jpg}'
        .replace(/{|}/g, "") // -> "userId:hoge,icon:fuga.jpg"
        .split(",")          // -> ["userId:hoge", "icon:fuga.jpg"]
        .some(i => {
            const kvPair = i.split(/(?<!https):/);
            if (kvPair.length !== 2) return true;
            obj[kvPair[0]] = kvPair[1];
        });
    return obj;
};


//-------------------------------------------------------------------------------------------------
const ele = {};

/**
 * @param {User} user
 */
ele.li = user => {
    const li = html(
    `<li class="clearfix" style="display: flex;">
        <a href="/id/${user.userId}" title="@${user.userId}" style="flex: auto;">
            <p class="">@${user.userId}</p>
            <img class="icon_24 lazy lazy-hidden loaded" src="${user.icon}">
        </a>
    </li>`);
    const button = html(`<button style="width: 1.5em; height: 1.5em; font-size: 14px; margin: auto; margin-right: 5px; margin-left: 5px;">×</button>`);
    button.onclick = () => {
        dao.delete(user);
        li.parentElement.removeChild(li);
    };
    li.appendChild(button);
    return li;
};

/**
 * @param {User[]} users
 */
ele.div = users => {
    const lis = users.map(u => ele.li(u));
    const div = html(
    `<div class="side_box side_line_box list_recommend expandable scrollable">
        <h3 class="title">バンリスト</h3>
        <div class="main_box closed">
            <ul>
            </ul>
        </div>
    </div>`);
    lis.forEach(l => div.querySelector("div.main_box ul").appendChild(l));
    return div;
};

/**
 * ユーザーのバンやバン解除を行うボタンを返します。
 * @param {User} user
 */
ele.button = user => {
    const isBanned = userContains(dao.find(), user);
    const text = isBanned ? "バンしている" : "バンする";
    const clazz = isBanned ? "btn active" : "btn";
    const button = html(`<a class="${clazz}" data-title="バンする" data-active-title="バンしている" data-active-hover-title="バンを解除する">${text}</a>`);
    const clickEvent = () => {
        const isActive = button.classList.contains("active");
        if (isActive) {
            dao.delete(user);
            button.setAttribute("class", "btn");
            button.innerText = button.getAttribute("data-title");
            console.log(`${user.userId}のバンを解除しました。`);
        } else {
            dao.add(user);
            button.setAttribute("class", "btn active");
            button.innerText = button.getAttribute("data-active-title");
            console.log(`${user.userId}をバンしました。`);
        };
    };
    const hoverEvent = () => {
        const isActive = button.classList.contains("active");
        if (!isActive) return false;
        button.innerText = button.getAttribute("data-active-hover-title");
    };
    const outEvent = () => {
        const isActive = button.classList.contains("active");
        if (!isActive) return false;
        button.innerText = button.getAttribute("data-active-title");
    };
    button.onclick = clickEvent;
    button.onmouseover = hoverEvent;
    button.onmouseout = outEvent;
    return button;
};

//-------------------------------------------------------------------------------------------------
const dao = {};

/**
 * ローカルストレージに保存されたユーザーの配列を返します。
 * @returns {User[]}
 */
dao.find = () => {
    const raw = localStorage.bannedUser;
    if (raw === undefined || raw === "[]") return [];
    return raw.split(/,(?={)/).map(i => JSON.parse(i));
};

/**
 * ローカルストレージにuserを保存します。
 * @param {User} user
 */
dao.add = user => {
    const bannedUsers = dao.find();
    if (!userContains(bannedUsers, user)) bannedUsers.push(user);
    const jsonList = objListToJson(bannedUsers);
    dao.save(jsonList);
};

/**
 * ローカルストレージからuserを消去します。
 * @param {User} user
 */
dao.delete = user => {
    const bannedUsers = dao.find();
    const newUserList = bannedUsers.filter(i => !isSameUser(i, user));
    const jsonList = objListToJson(newUserList);
    dao.save(jsonList);
};

/**
 * ローカルストレージのbannedUserキーに文字列を保存します。
 * @param {string} str 
 */
dao.save = str => localStorage.setItem("bannedUser", str);

//HTMLの取得と操作---------------------------------------------------------------------------------
/**
* 条件に一致した要素を見えなくします。
* @param {string} eleSel 消したい要素のセレクター
* @param {string} checkEleSel eleSelを祖先に持ち、かつattrを有する要素のセレクター
* @param {string} attr 要素を消す基準の属性。"text"を与えた場合はinnerTextを探す。
* @param {string[]} bannedList バンリスト
*/
const hideElement = (eleSel, checkEleSel, attr, bannedList) => {
    document.querySelectorAll(eleSel).forEach(ele => {
        const innerEle = ele.querySelector(checkEleSel);
        if (innerEle === null) return false;
        const property = (attr === "text") ? innerEle.innerText : innerEle.getAttribute(attr);
        if (bannedList.includes(property) && ele.style.display !== "none") {
            ele.style.display = "none";
            console.log(`hideElement: 要素を消しました。(${property})`);
        };
    });
};

/**
 * togetterサイトのフォローボタンを格納したボックスを返します。
 * バンボタンを設置するのに使います。
 */
const followBox = () => document.querySelector("#follow_box");

/**
 * togetterサイトのプロフィールボックスを返します。
 */
const profileBox = () => document.querySelector("div.profile_box").parentElement;

/**
 * バンボタンを設置します。
 */
const addBanButton = () => {
    const profile = profileBox();
    const userId = profile.querySelector("a.status_name").innerText.replace("@", "");
    const icon = profile.querySelector("img").getAttribute("src");
    const user = User(userId, icon);
    followBox().appendChild(ele.button(user));
};

/**
 * バンリストを設置します。
 */
const addBannedList = () => {
    const bannedUsers = dao.find();
    const div = ele.div(bannedUsers);
    document.querySelector("#right_wrap_middle .right_wrap").appendChild(div);
};

/**
 * 特定のユーザーが作ったまとめを非表示にします。
 * @param {User[]} bannedList 非表示にしたいユーザーのリスト
 */
const hideMatome = bannedList => {
    hideElement("li.clearfix", "img.icon_24", "data-lazy-src", bannedList.map(u => u.icon));
};

/**
 * 特定のユーザーのコメントを非表示にします。
 * @param {User[]} bannedList 非表示にしたいユーザーのリスト
 */
const hideComment = bannedList => {
    hideElement("#comment_box .list_box", ".status_name", "text", bannedList.map(u => u.userId).map(i => "@" + i));
};

/**
 * 最近見たまとめなどのリストに表示されている、特定のユーザーの作ったまとめを非表示にします。
 * @param {User[]} bannedList 非表示にしたいユーザーのリスト
 */
const hideThumbList = bannedList => {
    hideElement("ul.simple_list.thumb_list li", "img.icon_20", "data-lazy-src", bannedList.map(u => u.icon));
};

/**
 * @param {Function} callback コールバック
 * @param {number} ms
 */
const thread = async (callback, ms = 300) => {
    while (true) {
        callback();
        await sleep(ms);
    };
};

(async () => {
    'use strict';
    addBannedList();

    const bannedList = dao.find();
    thread(() => hideThumbList(bannedList));

    if (location.href.startsWith("https://togetter.com/id/")) {
        addBanButton();
        return false;
    };

    if (location.href.startsWith("https://togetter.com/li/")) {
        addBanButton();
        thread(() => hideComment(bannedList));
        return false;
    };
    
    thread(() => hideMatome(bannedList));
})();