Bluesky Unified Block & Hide

Automatically hides Bluesky posts immediately after confirming the block action using the native Block button.

当前为 2024-12-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Bluesky Unified Block & Hide
  3. // @namespace https://greasyfork.org/en/users/567951-stuart-saddler
  4. // @version 1.0
  5. // @description Automatically hides Bluesky posts immediately after confirming the block action using the native Block button.
  6. // @icon https://images.seeklogo.com/logo-png/52/2/bluesky-logo-png_seeklogo-520643.png
  7. // @match https://bsky.app/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Global variable to store the currently selected username
  16. let currentUsername = null;
  17.  
  18. /**
  19. * Utility function to log messages with a prefix for easier debugging.
  20. * @param {string} message - The message to log.
  21. */
  22. function log(message) {
  23. console.log(`[Bluesky Auto Hide Blocked Posts] ${message}`);
  24. }
  25.  
  26. /**
  27. * Determines if a given element is the "Three Dots" (Post Options) button.
  28. * @param {Element} element - The DOM element to check.
  29. * @returns {boolean} - True if it's the "Three Dots" button, else false.
  30. */
  31. function isPostOptionsButton(element) {
  32. if (!element) return false;
  33. const ariaLabel = element.getAttribute('aria-label') || '';
  34. return ariaLabel.includes('Open post options menu');
  35. }
  36.  
  37. /**
  38. * Determines if a given element is the "Block account" button based on its text content.
  39. * @param {Element} element - The DOM element to check.
  40. * @returns {boolean} - True if it's the "Block account" button, else false.
  41. */
  42. function isBlockButton(element) {
  43. if (!element) return false;
  44. const blockButtonText = "Block account";
  45. return element.textContent.trim() === blockButtonText;
  46. }
  47.  
  48. /**
  49. * Extracts the username from a post container.
  50. * @param {Element} postContainer - The post container element.
  51. * @returns {string|null} - The username or null if not found.
  52. */
  53. function getUsernameFromPost(postContainer) {
  54. if (!postContainer) return null;
  55. log('Attempting to extract username from post container.');
  56.  
  57. // Attempt to find an <a> tag with href containing "/profile/"
  58. const usernameLink = postContainer.querySelector('a[href*="/profile/"]');
  59. if (usernameLink) {
  60. const href = usernameLink.getAttribute('href');
  61. const parts = href.split('/');
  62. const username = parts[parts.length - 1] || null;
  63. if (username) {
  64. log(`Extracted username: ${username}`);
  65. return username.toLowerCase(); // Ensure lowercase for consistency
  66. }
  67. }
  68.  
  69. // Alternative method: Look for a span containing "@" symbol
  70. const possibleUsernameElements = postContainer.querySelectorAll('span, div');
  71. for (let el of possibleUsernameElements) {
  72. const text = el.textContent.trim();
  73. if (text.startsWith('@')) { // Look for text starting with "@"
  74. const username = text.substring(1); // Remove "@" symbol
  75. log(`Extracted username from span: ${username}`);
  76. return username.toLowerCase(); // Ensure lowercase for consistency
  77. }
  78. }
  79.  
  80. log('Username could not be extracted from the post.');
  81. return null;
  82. }
  83.  
  84. /**
  85. * Hides all posts from the specified username.
  86. * @param {string} username - The username whose posts should be hidden.
  87. */
  88. function hidePostsFromUser(username) {
  89. if (!username) return;
  90. log(`Hiding posts from user: @${username}`);
  91.  
  92. // Define selectors based on post container identification
  93. const selector = `div[role="link"][tabindex="0"], div[role="article"], section[role="article"]`;
  94. const posts = document.querySelectorAll(selector);
  95.  
  96. let hiddenCount = 0;
  97. posts.forEach(post => {
  98. const postUsername = getUsernameFromPost(post);
  99. if (postUsername && postUsername === username) {
  100. post.style.display = 'none';
  101. log(`Post from @${username} has been hidden.`);
  102. hiddenCount++;
  103. }
  104. });
  105.  
  106. log(`Total posts hidden from @${username}: ${hiddenCount}`);
  107. }
  108.  
  109. /**
  110. * Adds a username to the blocked list and hides their posts.
  111. * @param {string} username - The username to block.
  112. */
  113. function addBlockedUser(username) {
  114. if (!username) return;
  115. hidePostsFromUser(username);
  116. }
  117.  
  118. /**
  119. * Hides all posts from blocked users.
  120. * (This function is now redundant since we're not maintaining a blocked users list.)
  121. */
  122. function hidePostsFromBlockedUsers() {
  123. // No longer needed as we're not maintaining a blocked users list.
  124. // Removed to streamline the script.
  125. }
  126.  
  127. /**
  128. * Initializes the script by hiding posts from blocked users.
  129. * (This function is now redundant since we're not maintaining a blocked users list.)
  130. */
  131. function initializeBlockedUsers() {
  132. // No longer needed as we're not maintaining a blocked users list.
  133. // Removed to streamline the script.
  134.  
  135. // Optionally, if you still want to hide posts immediately after blocking without relying on storage,
  136. // you can keep any necessary logic here.
  137. }
  138.  
  139. /**
  140. * Sets up the listener for the "Three Dots" (Post Options) button to capture the username.
  141. */
  142. function setupPostOptionsListener() {
  143. document.addEventListener('click', function(event) {
  144. let target = event.target;
  145.  
  146. // Traverse up the DOM tree to check if a "Three Dots" button was clicked
  147. while (target && target !== document.body) {
  148. if (isPostOptionsButton(target)) {
  149. log('"Three Dots" button clicked.');
  150. // Find the post container associated with this button
  151. const postContainer = target.closest('div[role="link"][tabindex="0"], div[role="article"], section[role="article"]');
  152. if (postContainer) {
  153. const username = getUsernameFromPost(postContainer);
  154. if (username) {
  155. currentUsername = username;
  156. log(`Current post's username set to: @${username}`);
  157. } else {
  158. log('Username could not be determined from the post.');
  159. currentUsername = null;
  160. }
  161. } else {
  162. log('Post container not found.');
  163. currentUsername = null;
  164. }
  165. break; // Exit once handled
  166. }
  167. target = target.parentElement;
  168. }
  169. }, true); // Use capture phase
  170. }
  171.  
  172. /**
  173. * Sets up the listener for the "Block account" button within the menu to handle confirmation.
  174. */
  175. function setupBlockButtonListener() {
  176. document.addEventListener('click', function(event) {
  177. let target = event.target;
  178.  
  179. // Traverse up the DOM tree to check if a "Block account" button was clicked
  180. while (target && target !== document.body) {
  181. if (isBlockButton(target)) {
  182. log('"Block account" button clicked.');
  183. // Do NOT hide posts here; wait for confirmation
  184. // The hiding will be handled in the confirmation dialog listener
  185. break; // Exit once handled
  186. }
  187. target = target.parentElement;
  188. }
  189. }, true); // Use capture phase
  190. }
  191.  
  192. /**
  193. * Sets up a listener for the confirmation button to add the user to the blocked list and hide their posts.
  194. */
  195. function setupConfirmationButtonListener() {
  196. document.addEventListener('click', function(event) {
  197. const target = event.target;
  198.  
  199. // Check if the clicked element or its parent has data-testid="confirmBtn"
  200. const confirmBtn = target.closest('button[data-testid="confirmBtn"]');
  201. if (confirmBtn) {
  202. log('Confirmation button clicked.');
  203. if (currentUsername) {
  204. addBlockedUser(currentUsername);
  205. currentUsername = null;
  206. } else {
  207. log('No user recorded for blocking.');
  208. }
  209. }
  210. }, true); // Use capture phase
  211. }
  212.  
  213. /**
  214. * Utility function to debounce frequent function calls.
  215. * @param {Function} func - The function to debounce.
  216. * @param {number} delay - The delay in milliseconds.
  217. * @returns {Function} - The debounced function.
  218. */
  219. function debounce(func, delay) {
  220. let timeout;
  221. return function(...args) {
  222. clearTimeout(timeout);
  223. timeout = setTimeout(() => func.apply(this, args), delay);
  224. };
  225. }
  226.  
  227. /**
  228. * Initializes the script by setting up listeners.
  229. */
  230. function init() {
  231. setupPostOptionsListener();
  232. setupBlockButtonListener();
  233. setupConfirmationButtonListener();
  234. // initializeBlockedUsers(); // No longer needed
  235. log('Bluesky Auto Hide Blocked Posts script has been initialized.');
  236. }
  237.  
  238. /**
  239. * Waits for the DOM to be fully loaded before initializing the script.
  240. */
  241. function waitForDOM() {
  242. if (document.readyState === 'loading') {
  243. document.addEventListener('DOMContentLoaded', () => {
  244. setTimeout(init, 500); // Slight delay to ensure all elements are loaded
  245. });
  246. } else {
  247. setTimeout(init, 500);
  248. }
  249. }
  250.  
  251. // Start the script
  252. waitForDOM();
  253.  
  254. })();