// ==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)); })();