您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add a "MUTE USER" button to each post's options menu on Bluesky and capture parent divs on button click
// ==UserScript== // @name Add Mute User Button to Bluesky Posts Menu // @namespace http://tampermonkey.net/ // @version 0.9 // @description Add a "MUTE USER" button to each post's options menu on Bluesky and capture parent divs on button click // @author JouySandbox, Trevusimon // @match https://bsky.app/* // @grant none // @license GNU GPLv3 // ==/UserScript== (function () { 'use strict'; let hostApi = 'https://russula.us-west.host.bsky.network' let token = null; let profileFetched = new Map(); // Set to track fetched profiles let DidPlcTarget = null; // Function to get the token directly from localStorage 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 to extract did:plc from URL function extractDidPlc(url) { const match = url.match(/did:plc:[^/]+/); return match ? match[0] : null; } function removeButton(buttonLabel) { // Find the button by aria-label document.querySelectorAll(`[aria-label="${buttonLabel}"]`).forEach(button => { // Remove the found button button.remove(); }); } // Function to add the "MUTE USER" button in the menu before "Mute thread" function addMuteButton() { document.querySelectorAll('[data-testid="postDropdownMuteThreadBtn"]') .forEach(muteThreadBtn => { if (!muteThreadBtn.parentNode.querySelector('.mute-user-button') && !profileFetched.get( DidPlcTarget)) { // Create the MUTE USER button div with the same style as the menu let muteButton = document.createElement('div'); muteButton.setAttribute('aria-label', 'Mute user'); muteButton.setAttribute('role', 'menuitem'); muteButton.setAttribute('tabindex', '-1'); muteButton.className = 'css-175oi2r r-1loqt21 r-1otgn73 mute-user-button'; muteButton.style.flexDirection = 'row'; muteButton.style.alignItems = 'center'; muteButton.style.gap = '16px'; muteButton.style.padding = '8px 10px'; muteButton.style.borderRadius = '4px'; muteButton.style.minHeight = '32px'; muteButton.style.outline = '0px'; muteButton.style.cursor = 'pointer'; muteButton.style.transition = 'background-color 0.2s ease'; // Add hover (highlight) on mouse over muteButton.onmouseover = function () { muteButton.style.backgroundColor = 'rgba(29, 161, 242, 0.1)'; }; muteButton.onmouseout = function () { muteButton.style.backgroundColor = ''; }; // Add the text "Mute User" let buttonText = document.createElement('div'); buttonText.className = 'css-146c3p1'; buttonText.style.fontSize = '14px'; buttonText.style.letterSpacing = '0.25px'; buttonText.style.color = 'rgb(215, 221, 228)'; buttonText.style.flex = '1 1 0%'; buttonText.style.fontWeight = '600'; buttonText.style.lineHeight = '14px'; buttonText.textContent = 'Mute User'; // Add the emoji let buttonIcon = document.createElement('div'); buttonIcon.className = 'css-175oi2r'; buttonIcon.style.marginRight = '-2px'; buttonIcon.style.marginLeft = '12px'; buttonIcon.textContent = '☠️'; // Adding the emoji here // Add the button to the DOM before "Mute thread" muteButton.appendChild(buttonText); muteButton.appendChild(buttonIcon); muteThreadBtn.parentNode.insertBefore(muteButton, muteThreadBtn); // Function when clicking the MUTE USER button muteButton.onclick = () => { let post = muteThreadBtn.closest('.post-class'); // Adjust selector to find the post // let userId = post.getAttribute('data-user-id'); // Adjust to get the user ID correctly muteUser(DidPlcTarget); }; } }); } function addUnmuteButton() { document.querySelectorAll('[data-testid="postDropdownMuteThreadBtn"]') .forEach(muteThreadBtn => { if (!muteThreadBtn.parentNode.querySelector('.unmute-user-button') && profileFetched.get(DidPlcTarget)) { // Create the UNMUTE USER button div with the same style as the menu let unmuteButton = document.createElement('div'); unmuteButton.setAttribute('aria-label', 'Unmute User'); unmuteButton.setAttribute('role', 'menuitem'); unmuteButton.setAttribute('tabindex', '-1'); unmuteButton.className = 'css-175oi2r r-1loqt21 r-1otgn73 unmute-user-button'; unmuteButton.style.flexDirection = 'row'; unmuteButton.style.alignItems = 'center'; unmuteButton.style.gap = '16px'; unmuteButton.style.padding = '8px 10px'; unmuteButton.style.borderRadius = '4px'; unmuteButton.style.minHeight = '32px'; unmuteButton.style.outline = '0px'; unmuteButton.style.cursor = 'pointer'; unmuteButton.style.transition = 'background-color 0.2s ease'; // Add hover (highlight) on mouse over unmuteButton.onmouseover = function () { unmuteButton.style.backgroundColor = 'rgba(29, 161, 242, 0.1)'; }; unmuteButton.onmouseout = function () { unmuteButton.style.backgroundColor = ''; }; // Add the text "Unmute User" let buttonText = document.createElement('div'); buttonText.className = 'css-146c3p1'; buttonText.style.fontSize = '14px'; buttonText.style.letterSpacing = '0.25px'; buttonText.style.color = 'rgb(215, 221, 228)'; buttonText.style.flex = '1 1 0%'; buttonText.style.fontWeight = '600'; buttonText.style.lineHeight = '14px'; buttonText.textContent = 'Unmute User'; // Add the emoji let buttonIcon = document.createElement('div'); buttonIcon.className = 'css-175oi2r'; buttonIcon.style.marginRight = '-2px'; buttonIcon.style.marginLeft = '12px'; buttonIcon.textContent = '😃'; // Adding the emoji here // Add the button to the DOM before "Mute thread" unmuteButton.appendChild(buttonText); unmuteButton.appendChild(buttonIcon); muteThreadBtn.parentNode.insertBefore(unmuteButton, muteThreadBtn); // Function when clicking the UNMUTE USER button unmuteButton.onclick = () => { let post = muteThreadBtn.closest('.post-class'); // Adjust selector to find the post unmuteUser(DidPlcTarget); }; } }); } // Function to mute the user async function muteUser(userId) { if (!token) { alert('Failed to get authorization token'); return; } try { let response = await fetch( `${hostApi}/xrpc/app.bsky.graph.muteActor`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ actor: userId // Send the userId as "actor" }) }); if (response.ok) { getUserProfile(userId); // Update mute state in profileFetched profileFetched.set(userId, true); // After muting, the user is muted // Remove the Mute button and add the Unmute button removeButton('Mute User'); addUnmuteButton(); alert('User muted successfully ☠️☠️☠️'); } else { alert('Failed to mute user'); } } catch (error) { console.error('Error muting user:', error); } } // Function to unmute the user async function unmuteUser(userId) { if (!token) { alert('Failed to get authorization token'); return; } try { let response = await fetch( `${hostApi}/xrpc/app.bsky.graph.unmuteActor`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ actor: userId // Send the userId as "actor" }) }); if (response.ok) { await getUserProfile(userId); profileFetched.set(userId, false); removeButton('Unmute User'); addMuteButton(); alert('User unmuted successfully 😃😃😃'); } else { alert('Failed to unmute user'); } } catch (error) { console.error('Error unmuting user:', error); } } // Função para obter o perfil do usuário async function getUserProfile(didPlc) { if (!token) { alert('Failed to get authorization token'); return; } try { const encodedDidPlc = encodeURIComponent(didPlc); const response = await fetch(`${hostApi}/xrpc/app.bsky.actor.getProfile?actor=${encodedDidPlc}`, { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); if (response.ok) { return await response.json(); } else { console.error('Failed to fetch user profile'); } } catch (error) { console.error('Error fetching user profile:', error); } } // Function to add listeners to the dropdown buttons. function addDropdownButtonListeners() { document.querySelectorAll('[data-testid="postDropdownBtn"]').forEach(button => { button.addEventListener('click', async (event) => { // Capture the div where the button was clicked const divWhereButtonHasBeenClicked = button.closest('div'); // Capture all parent divs const parentDivs = []; let selectedElement = divWhereButtonHasBeenClicked; while (selectedElement) { parentDivs.push(selectedElement); selectedElement = selectedElement.parentElement; } // Show the divs where the button was clicked and its parents //console.log('Clicked Div:', divWhereButtonHasBeenClicked); // console.log('Parent Divs:', parentDivs); // Get the HTML content of the 6th parent div const targetDivHtml = parentDivs[5].innerHTML; // console.log('Div alvo HTML:', targetDivHtml); // Extract did:plc from the HTML content const didPlc = extractDidPlc(targetDivHtml); // console.log('did:plc:', didPlc); // Get the user profile and check if the user is muted if (didPlc && !profileFetched.has(didPlc)) { let user = await getUserProfile(didPlc); profileFetched.set(didPlc, user.viewer.muted); if (user.viewer.muted) { addUnmuteButton(); } else { addMuteButton(); } DidPlcTarget = didPlc; } else if (didPlc && profileFetched.get(didPlc)) { DidPlcTarget = didPlc; addUnmuteButton(); } else if (didPlc) { DidPlcTarget = didPlc; addMuteButton(); } }); }); } // Capture the token from localStorage getTokenFromLocalStorage(); // Add the MUTE USER button periodically setInterval(addMuteButton, 2000); // Ajuste o intervalo conforme necessário setInterval(addUnmuteButton, 2000); // Ajuste o intervalo conforme necessário // Add listeners to the dropdown buttons periodically setInterval(addDropdownButtonListeners, 2000); // Ajuste o intervalo conforme necessário })();