您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add a mute button to Bluesky posts, to allow you to quickly mute a user
- // ==UserScript==
- // @name Add "Mute User" Button to Bluesky Posts
- // @namespace plonked
- // @description Add a mute button to Bluesky posts, to allow you to quickly mute a user
- // @author @plonked.bsky.social
- // @match *://bsky.app/*
- // @grant none
- // @version 0.0.1.20241128204020
- // ==/UserScript==
- (function() {
- 'use strict';
- const BUTTON_CLASS = 'bsky-mute-btn';
- const PROCESSED_CLASS = 'bsky-mute-processed';
- const POST_SELECTORS = {
- feedItem: '[data-testid^="feedItem-by-"]',
- postPage: '[data-testid^="postThreadItem-by-"]',
- searchItem: 'div[role="link"][tabindex="0"]'
- };
- let hostApi = 'https://cordyceps.us-west.host.bsky.network';
- let token = null;
- function getTokenFromLocalStorage() {
- const storedData = localStorage.getItem('BSKY_STORAGE');
- if (storedData) {
- try {
- const localStorageData = JSON.parse(storedData);
- token = localStorageData.session.currentAccount.accessJwt;
- } catch (error) {
- console.error('Failed to parse session data', error);
- }
- }
- }
- function createMuteButton() {
- const button = document.createElement('div');
- button.className = `css-175oi2r r-1loqt21 r-1otgn73 ${BUTTON_CLASS}`;
- button.setAttribute('role', 'button');
- button.setAttribute('tabindex', '0');
- button.style.cssText = `
- position: absolute;
- top: 8px;
- right: 8px;
- border-radius: 999px;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- overflow: hidden;
- padding: 5px;
- cursor: pointer;
- transition: background-color 0.2s ease;
- opacity: 0.5;
- z-index: 10;
- `;
- const icon = document.createElement('div');
- icon.textContent = '🔇';
- icon.style.cssText = `
- font-size: 16px;
- filter: grayscale(1);
- `;
- button.appendChild(icon);
- button.onmouseover = () => {
- button.style.backgroundColor = 'rgba(29, 161, 242, 0.1)';
- button.style.opacity = '1';
- };
- button.onmouseout = () => {
- button.style.backgroundColor = '';
- button.style.opacity = '0.5';
- };
- return button;
- }
- async function muteUser(userId) {
- if (!token) {
- console.error('Failed to get authorization token');
- return false;
- }
- try {
- const response = await fetch(
- `${hostApi}/xrpc/app.bsky.graph.muteActor`,
- {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`
- },
- body: JSON.stringify({ actor: userId })
- }
- );
- return response.ok;
- } catch (error) {
- console.error('Error muting user:', error);
- return false;
- }
- }
- function extractDidPlc(element) {
- const html = element.innerHTML;
- const match = html.match(/did:plc:[^/"]+/);
- return match ? match[0] : null;
- }
- function findNameInPost(post) {
- const testId = post.getAttribute('data-testid');
- if (testId) {
- const match = testId.match(/(?:feedItem-by-|postThreadItem-by-)([^.]+)/);
- if (match) return match[1];
- }
- const profileLinks = post.querySelectorAll('a[href^="/profile/"]');
- for (const link of profileLinks) {
- const nameElement = link.querySelector('.css-1jxf684[style*="font-weight: 600"]');
- if (nameElement) {
- let name = nameElement.textContent.trim();
- if (name.startsWith('@')) name = name.slice(1);
- if (name.endsWith('.bsky.social')) name = name.replace('.bsky.social', '');
- return name;
- }
- }
- return null;
- }
- function hideAllPostsForUser(didPlc) {
- document.querySelectorAll(Object.values(POST_SELECTORS).join(',')).forEach(post => {
- if (post.innerHTML.includes(didPlc)) {
- post.style.display = 'none';
- }
- });
- }
- async function addMuteButton(post) {
- if (post.classList.contains(PROCESSED_CLASS)) return;
- if (window.getComputedStyle(post).position === 'static') {
- post.style.position = 'relative';
- }
- const didPlc = extractDidPlc(post);
- if (!didPlc) return;
- const username = findNameInPost(post);
- if (!username) return;
- const button = createMuteButton();
- button.setAttribute('data-did-plc', didPlc);
- button.onclick = async (e) => {
- e.preventDefault();
- e.stopPropagation();
- const success = await muteUser(didPlc);
- if (success) {
- hideAllPostsForUser(didPlc);
- }
- };
- post.appendChild(button);
- post.classList.add(PROCESSED_CLASS);
- }
- function initialize() {
- console.log('Initializing Bluesky Direct Mute Button');
- getTokenFromLocalStorage();
- const observer = new MutationObserver((mutations) => {
- if (mutations.some(mutation => mutation.addedNodes.length)) {
- const unprocessedPosts = document.querySelectorAll(
- Object.values(POST_SELECTORS)
- .map(selector => `${selector}:not(.${PROCESSED_CLASS})`)
- .join(',')
- );
- unprocessedPosts.forEach(addMuteButton);
- }
- });
- observer.observe(document.body, { childList: true, subtree: true });
- document.querySelectorAll(
- Object.values(POST_SELECTORS)
- .map(selector => `${selector}:not(.${PROCESSED_CLASS})`)
- .join(',')
- ).forEach(addMuteButton);
- }
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initialize);
- } else {
- initialize();
- }
- })();