Greasy Fork镜像 支持简体中文。

Togetter User Ban

try to take over the world!

  1. // ==UserScript==
  2. // @name Togetter User Ban
  3. // @namespace https://gf.qytechs.cn/ja/scripts/387330-togetter-user-ban
  4. // @version 1.0
  5. // @description try to take over the world!
  6. // @author You
  7. // @match https://togetter.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. /**
  12. * ユーザーを表すデータ型
  13. * @typedef {Object} User
  14. * @property {string} userId
  15. * @property {string} icon
  16. */
  17.  
  18. /**
  19. * @param {string} userId
  20. * @param {string} icon
  21. * @returns {User}
  22. */
  23. const User = (userId, icon) => {
  24. return { userId: userId, icon: icon };
  25. };
  26.  
  27. /**
  28. * user1がuser2と同じユーザーを示すか判別します。
  29. * @param {User} user1
  30. * @param {User} user2
  31. */
  32. const isSameUser = (user1, user2) => {
  33. return (user1.userId === user2.userId);
  34. };
  35.  
  36. /**
  37. * 与えられた時間だけスレッドを停止します。
  38. * @param {number} ms ミリ秒
  39. */
  40. const sleep = ms => {
  41. return new Promise(resolve => {
  42. setTimeout(resolve, ms);
  43. });
  44. };
  45.  
  46. /**
  47. * 文字列からHTMLElementを作り返します。
  48. * @param {string} h htmlを表現する文字列
  49. * @returns {Node}
  50. */
  51. const html = h => {
  52. const div = document.createElement("div");
  53. div.insertAdjacentHTML("afterbegin", h);
  54. return div.firstChild;
  55. }
  56.  
  57. /**
  58. * userListがuserを持っているか判別します。
  59. * @param {User[]} userList
  60. * @param {User} user
  61. */
  62. const userContains = (userList, user) => userList.filter(i => isSameUser(i, user)).length > 0;
  63.  
  64. /**
  65. * objListをJSON CSVに変換して返します。
  66. * @param {{}[]} objList
  67. */
  68. const objListToJson = objList => objList.map(o => JSON.stringify(o)).join(",");
  69.  
  70. //-------------------------------------------------------------------------------------------------
  71. const ele = {};
  72.  
  73. /**
  74. * バンリストの項目を返します。
  75. * @param {User} user
  76. */
  77. ele.li = user => {
  78. const li = html(
  79. `<li class="clearfix" style="display: flex;">
  80. <a href="/id/${user.userId}" title="@${user.userId}" style="flex: auto;">
  81. <p class="">@${user.userId}</p>
  82. <img class="icon_24 lazy lazy-hidden loaded" src="${user.icon}">
  83. </a>
  84. </li>`);
  85. /** @type {HTMLElement} */
  86. // @ts-ignore
  87. const button = html(`<button style="width: 1.5em; height: 1.5em; font-size: 14px; margin: auto; margin-right: 5px; margin-left: 5px;">×</button>`);
  88. button.onclick = () => {
  89. dao.delete(user);
  90. li.parentElement.removeChild(li);
  91. };
  92. li.appendChild(button);
  93. return li;
  94. };
  95.  
  96. /**
  97. * バンリストを返します。
  98. * @param {User[]} users
  99. */
  100. ele.div = users => {
  101. const lis = users.map(u => ele.li(u));
  102. /** @type {HTMLElement} */
  103. // @ts-ignore
  104. const div = html(
  105. `<div class="side_box side_line_box list_recommend expandable scrollable">
  106. <h3 class="title">バンリスト</h3>
  107. <div class="main_box closed">
  108. <ul>
  109. </ul>
  110. </div>
  111. </div>`);
  112. lis.forEach(l => div.querySelector("div.main_box ul").appendChild(l));
  113. return div;
  114. };
  115.  
  116. /**
  117. * ユーザーのバンやバン解除を行うボタンを返します。
  118. * @param {User} user
  119. */
  120. ele.button = user => {
  121. const isBanned = userContains(dao.find(), user);
  122. const text = isBanned ? "バンしている" : "バンする";
  123. const clazz = isBanned ? "btn active" : "btn";
  124. /** @type {HTMLElement} */
  125. // @ts-ignore
  126. const button = html(`<a class="${clazz}" data-title="バンする" data-active-title="バンしている" data-active-hover-title="バンを解除する">${text}</a>`);
  127. const clickEvent = () => {
  128. const isActive = button.classList.contains("active");
  129. if (isActive) {
  130. dao.delete(user);
  131. button.setAttribute("class", "btn");
  132. button.innerText = button.getAttribute("data-title");
  133. console.log(`${user.userId}のバンを解除しました。`);
  134. } else {
  135. dao.add(user);
  136. button.setAttribute("class", "btn active");
  137. button.innerText = button.getAttribute("data-active-title");
  138. console.log(`${user.userId}をバンしました。`);
  139. };
  140. };
  141. const hoverEvent = () => {
  142. const isActive = button.classList.contains("active");
  143. if (!isActive) return false;
  144. button.innerText = button.getAttribute("data-active-hover-title");
  145. };
  146. const outEvent = () => {
  147. const isActive = button.classList.contains("active");
  148. if (!isActive) return false;
  149. button.innerText = button.getAttribute("data-active-title");
  150. };
  151. button.onclick = clickEvent;
  152. button.onmouseover = hoverEvent;
  153. button.onmouseout = outEvent;
  154. return button;
  155. };
  156.  
  157. //-------------------------------------------------------------------------------------------------
  158. const dao = {};
  159.  
  160. /**
  161. * ローカルストレージに保存されたユーザーの配列を返します。
  162. * @returns {User[]}
  163. */
  164. dao.find = () => {
  165. const raw = localStorage.bannedUser;
  166. if (raw === undefined || raw === "[]") return [];
  167. return raw.split(/,(?={)/).map(i => JSON.parse(i));
  168. };
  169.  
  170. /**
  171. * ローカルストレージにuserを保存します。
  172. * @param {User} user
  173. */
  174. dao.add = user => {
  175. const bannedUsers = dao.find();
  176. if (!userContains(bannedUsers, user)) bannedUsers.push(user);
  177. const jsonList = objListToJson(bannedUsers);
  178. dao.save(jsonList);
  179. };
  180.  
  181. /**
  182. * ローカルストレージからuserを消去します。
  183. * @param {User} user
  184. */
  185. dao.delete = user => {
  186. const bannedUsers = dao.find();
  187. const newUserList = bannedUsers.filter(i => !isSameUser(i, user));
  188. const jsonList = objListToJson(newUserList);
  189. dao.save(jsonList);
  190. };
  191.  
  192. /**
  193. * ローカルストレージのbannedUserキーに文字列を保存します。
  194. * @param {string} str
  195. */
  196. dao.save = str => localStorage.setItem("bannedUser", str);
  197.  
  198. //HTMLの取得と操作---------------------------------------------------------------------------------
  199. /**
  200. * 条件に一致した要素を見えなくします。
  201. * @param {string} selector 消したい要素のセレクター
  202. * @param {string} checkEleSel eleSelを祖先に持ち、かつattrを有する要素のセレクター
  203. * @param {string} attr 要素を消す基準の属性。"text"を与えた場合はinnerTextを探す。
  204. * @param {string[]} bannedList バンリスト
  205. */
  206. const hideElement = (selector, checkEleSel, attr, bannedList) => {
  207. /** @type {NodeListOf<HTMLElement>} */
  208. const elementList = document.querySelectorAll(selector);
  209. const getProperty = e => (attr === "text") ? e.innerText : e.getAttribute(attr);
  210. Array.from(elementList)
  211. .filter(e => e.style.display !== "none")
  212. .filter(e => e.querySelector(checkEleSel) !== null)
  213. .forEach(e => {
  214. [e]
  215. .map(e => e.querySelector(checkEleSel))
  216. .map(getProperty)
  217. .filter(p => bannedList.includes(p))
  218. .forEach(p => {
  219. e.style.display = "none";
  220. console.log(`hideElement: 要素を消しました。(${p})`);
  221. });
  222. });
  223. };
  224.  
  225. /**
  226. * togetterサイトのフォローボタンを格納したボックスを返します。
  227. * バンボタンを設置するのに使います。
  228. */
  229. const followBox = () => document.querySelector("#follow_box");
  230.  
  231. /**
  232. * togetterサイトのプロフィールボックスを返します。
  233. */
  234. const profileBox = () => document.querySelector("div.profile_box").parentElement;
  235.  
  236. /**
  237. * バンボタンを設置します。
  238. */
  239. const addBanButton = () => {
  240. const profile = profileBox();
  241. // @ts-ignore
  242. const userId = profile.querySelector("a.status_name").innerText.replace("@", "");
  243. const icon = profile.querySelector("img").getAttribute("src");
  244. const user = User(userId, icon);
  245. followBox().appendChild(ele.button(user));
  246. };
  247.  
  248. /**
  249. * バンリストを設置します。
  250. */
  251. const addBannedList = () => {
  252. const bannedUsers = dao.find();
  253. const div = ele.div(bannedUsers);
  254. document.querySelector("#right_wrap_middle .right_wrap").appendChild(div);
  255. };
  256.  
  257. /**
  258. * Twitterのデフォルトアイコン
  259. */
  260. const defaultIcon = "https://abs.twimg.com/sticky/default_profile_images/default_profile_normal.png";
  261.  
  262. /**
  263. * userListからアイコンのリストを作り返します。
  264. * @param {User[]} userList
  265. */
  266. const iconList = userList => {
  267. return userList
  268. .map(u => u.icon)
  269. .filter(i => i !== defaultIcon);
  270. debugger
  271. };
  272.  
  273. /**
  274. * userListからIDのリストを作り変えします。
  275. * @param {User[]} userList
  276. */
  277. const idList = userList => {
  278. return userList
  279. .map(u => u.userId)
  280. .map(i => "@" + i);
  281. };
  282.  
  283. /**
  284. * 特定のユーザーが作ったまとめを非表示にします。
  285. * @param {User[]} bannedList 非表示にしたいユーザーのリスト
  286. */
  287. const hideMatome = bannedList => {
  288. const list = iconList(bannedList);
  289. hideElement("li.clearfix", "img.icon_24", "data-lazy-src", list);
  290. };
  291.  
  292. /**
  293. * 特定のユーザーのコメントを非表示にします。
  294. * @param {User[]} bannedList 非表示にしたいユーザーのリスト
  295. */
  296. const hideComment = bannedList => {
  297. const list = idList(bannedList);
  298. hideElement("#comment_box .list_box", ".status_name", "text", list);
  299. };
  300.  
  301. /**
  302. * 最近見たまとめなどのまとめを非表示にします。
  303. * @param {User[]} bannedList 非表示にしたいユーザーのリスト
  304. */
  305. const hideThumbList = bannedList => {
  306. const list = iconList(bannedList);
  307. hideElement("ul.simple_list.thumb_list li", "img.icon_20", "data-lazy-src", list);
  308. };
  309.  
  310.  
  311. /**
  312. * おすすめまとめの要素を非表示にします。
  313. * @param {User[]} bannedList
  314. */
  315. const hideRecommendList = bannedList => {
  316. const list = iconList(bannedList);
  317. hideElement(".list_recommend .clearfix", "img.icon_24", "data-lazy-src", list);
  318. };
  319.  
  320. /**
  321. *
  322. * @param {User[]} bannedList
  323. */
  324. const hideCommentPopular = bannedList => {
  325. const list = iconList(bannedList);
  326. hideElement(".comment_popular .clearfix", "img.icon_24", "data-lazy-src", list);
  327. };
  328.  
  329. /**
  330. * @param {Function} callback
  331. * @param {number} ms
  332. */
  333. const thread = async (callback, ms = 300) => {
  334. while (true) {
  335. callback();
  336. await sleep(ms);
  337. };
  338. };
  339.  
  340. /**
  341. * selectorの要素に何らかの変更が加えられたとき、callbackを呼びます。
  342. * @param {MutationCallback} callback
  343. * @param {string} selector
  344. * @param {MutationObserverInit} options
  345. */
  346. const observe = (callback, selector, options = { childList: true }) => {
  347. // @ts-ignore
  348. callback();
  349. const observer = new MutationObserver(callback);
  350. const content = document.querySelector(selector);
  351. observer.observe(content, options);
  352. };
  353.  
  354. (async () => {
  355. 'use strict';
  356.  
  357. const bannedList = dao.find();
  358. hideThumbList(bannedList);
  359. hideRecommendList(bannedList);
  360. hideCommentPopular(bannedList);
  361. addBannedList();
  362.  
  363. if (location.href.startsWith("https://togetter.com/id/")) {
  364. addBanButton();
  365. return false;
  366. };
  367.  
  368. if (location.href.startsWith("https://togetter.com/li/")) {
  369. addBanButton();
  370. observe(() => hideComment(bannedList), "#comment_box");
  371. return false;
  372. };
  373. observe(() => hideMatome(bannedList), ".simple_list");
  374. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址