// ==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
// @downloadURL 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(/(? {
const li = html(
`
@${user.userId}
`);
const button = html(``);
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(
``);
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(`${text}`);
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));
})();