Bluesky Unified Block & Hide

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

  1. // ==UserScript==
  2. // @name Bluesky Unified Block & Hide
  3. // @namespace https://gf.qytechs.cn/en/users/567951-stuart-saddler
  4. // @version 1.2
  5. // @description Automatically hides Bluesky posts immediately after confirming the block action using the native Block button.
  6. // @icon https://i.ibb.co/Vv9LhQv/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. // Stores the username of the post being blocked
  16. let currentUsername = null;
  17.  
  18. /**
  19. * Logs messages with a specific prefix for 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. * Checks if an 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. * Checks if an element is the "Block account" button.
  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('Extracting username from post.');
  56.  
  57. // Locate the profile link
  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(`Username found: ${username}`);
  65. return username.toLowerCase();
  66. }
  67. }
  68.  
  69. // Alternative method: Locate a span starting with "@"
  70. const possibleUsernameElements = postContainer.querySelectorAll('span, div');
  71. for (let el of possibleUsernameElements) {
  72. const text = el.textContent.trim();
  73. if (text.startsWith('@')) {
  74. const username = text.substring(1);
  75. log(`Username extracted from span: ${username}`);
  76. return username.toLowerCase();
  77. }
  78. }
  79.  
  80. log('Username extraction failed.');
  81. return null;
  82. }
  83.  
  84. /**
  85. * Hides all posts from a specific 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: @${username}`);
  91.  
  92. const selector = `div[role="link"][tabindex="0"], div[role="article"], section[role="article"]`;
  93. const posts = document.querySelectorAll(selector);
  94.  
  95. let hiddenCount = 0;
  96. posts.forEach(post => {
  97. const postUsername = getUsernameFromPost(post);
  98. if (postUsername && postUsername === username) {
  99. post.style.display = 'none';
  100. log(`Hidden post from: @${username}`);
  101. hiddenCount++;
  102. }
  103. });
  104.  
  105. log(`Total posts hidden from @${username}: ${hiddenCount}`);
  106. }
  107.  
  108. /**
  109. * Handles blocking a user by hiding their posts.
  110. * @param {string} username - The username to block.
  111. */
  112. function addBlockedUser(username) {
  113. if (!username) return;
  114. hidePostsFromUser(username);
  115. }
  116.  
  117. /**
  118. * Listens for clicks on the "Three Dots" button to capture the username.
  119. */
  120. function setupPostOptionsListener() {
  121. document.addEventListener('click', function(event) {
  122. let target = event.target;
  123.  
  124. while (target && target !== document.body) {
  125. if (isPostOptionsButton(target)) {
  126. log('"Three Dots" button clicked.');
  127. const postContainer = target.closest('div[role="link"][tabindex="0"], div[role="article"], section[role="article"]');
  128. if (postContainer) {
  129. const username = getUsernameFromPost(postContainer);
  130. if (username) {
  131. currentUsername = username;
  132. log(`Selected username: @${username}`);
  133. } else {
  134. log('Username not found.');
  135. currentUsername = null;
  136. }
  137. } else {
  138. log('Post container not found.');
  139. currentUsername = null;
  140. }
  141. break;
  142. }
  143. target = target.parentElement;
  144. }
  145. }, true);
  146. }
  147.  
  148. /**
  149. * Listens for clicks on the "Block account" button to handle confirmation.
  150. */
  151. function setupBlockButtonListener() {
  152. document.addEventListener('click', function(event) {
  153. let target = event.target;
  154.  
  155. while (target && target !== document.body) {
  156. if (isBlockButton(target)) {
  157. log('"Block account" button clicked.');
  158. // Waiting for confirmation to hide posts
  159. break;
  160. }
  161. target = target.parentElement;
  162. }
  163. }, true);
  164. }
  165.  
  166. /**
  167. * Listens for clicks on the confirmation "Block" button to hide posts.
  168. */
  169. function setupConfirmationButtonListener() {
  170. document.addEventListener('click', function(event) {
  171. const target = event.target;
  172.  
  173. // Target the confirmation button using data-testid
  174. const confirmBtn = target.closest('button[data-testid="confirmBtn"]');
  175. if (confirmBtn) {
  176. log('Confirmation "Block" button clicked.');
  177. if (currentUsername) {
  178. addBlockedUser(currentUsername);
  179. currentUsername = null;
  180. } else {
  181. log('No username to block.');
  182. }
  183. }
  184. }, true);
  185. }
  186.  
  187. /**
  188. * Debounces a function to limit its execution rate.
  189. * @param {Function} func - The function to debounce.
  190. * @param {number} delay - Delay in milliseconds.
  191. * @returns {Function} - The debounced function.
  192. */
  193. function debounce(func, delay) {
  194. let timeout;
  195. return function(...args) {
  196. clearTimeout(timeout);
  197. timeout = setTimeout(() => func.apply(this, args), delay);
  198. };
  199. }
  200.  
  201. /**
  202. * Initializes the userscript by setting up event listeners.
  203. */
  204. function init() {
  205. setupPostOptionsListener();
  206. setupBlockButtonListener();
  207. setupConfirmationButtonListener();
  208. log('Bluesky Unified Block & Hide script initialized.');
  209. }
  210.  
  211. /**
  212. * Ensures the script runs after the DOM is fully loaded.
  213. */
  214. function waitForDOM() {
  215. if (document.readyState === 'loading') {
  216. document.addEventListener('DOMContentLoaded', () => {
  217. setTimeout(init, 500);
  218. });
  219. } else {
  220. setTimeout(init, 500);
  221. }
  222. }
  223.  
  224. // Start the script
  225. waitForDOM();
  226.  
  227. })();

QingJ © 2025

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