Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Twitter 1-click blocker
- // @namespace http://tampermonkey.net/
- // @version 1.1.1
- // @description Block users on Twitter in one click instead of 3. Block from any page where users are listed (/retweets, /followers, etc)!
- // @author Dara Oladosu (przerobiony na skrypt użytkownika)
- // @match https://*.x.com/*
- // @match https://*.twitter.com/*
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_openInTab
- // @grant GM_addStyle
- // @grant GM_registerMenuCommand
- // @grant GM_addValueChangeListener
- // @grant window.close
- // ==/UserScript==
- (function() {
- 'use strict';
- // Konfiguracja (z index.js)
- const CONFIG = {
- colors: {
- "#000000": "rgb(113, 118, 123)",
- "#15202B": "rgb(139, 152, 165)",
- "#FFFFFF": "rgb(83, 100, 113)",
- },
- selectors: {
- username: '[data-testid="SideNav_AccountSwitcher_Button"]',
- userActions: '[data-testid="userActions"]',
- confirmButton: '[data-testid="confirmationSheetConfirm"]',
- menuCaret: '[data-testid="caret"]',
- userName: '[data-testid="User-Name"]',
- grokActions: '[aria-label="Grok actions"]',
- blockButton: '[data-testid="block"]',
- confirmationDialog: '[data-testid="confirmationSheetDialog"]',
- userCell: '[data-testid="UserCell"]',
- cellInnerDiv: '[data-testid="cellInnerDiv"]',
- primaryColumn: '[data-testid="primaryColumn"]',
- placementTracking: '[data-testid="placementTracking"]',
- followButton: '[data-testid*="-follow"]',
- tweet: '[data-testid="tweet"]',
- hoverCard: '[data-testid="HoverCard"]'
- },
- classes: {
- blockButton: 'sw-block-button',
- blockButtonWrapper: 'sw-block-button-wrapper',
- blockButtonBackground: 'sw-block-button-background',
- confirmationClicked: 'sw-block-confirmation-clicked',
- menu: 'sw-menu',
- blockIcon: 'sw-block-icon',
- tooltipText: 'sw-tooltip-block-text'
- },
- styles: {
- errorWrapper: {
- backgroundColor: "red",
- color: "white",
- textAlign: "center",
- padding: "10px",
- borderRadius: "5px",
- margin: "20px",
- position: "fixed",
- top: "10px",
- left: "50%",
- transform: "translateX(-50%)",
- zIndex: "9999"
- }
- }
- };
- // Domyślne ustawienia (z options.js i settings.js)
- const DEFAULT_SETTINGS = {
- action: 'block',
- enabledPages: {
- timeline: true,
- followers: true,
- following: true,
- search: true,
- communities: true,
- engagements: true,
- status: true,
- trending: true,
- notifications: true,
- hashtag: true,
- profile: true,
- explore: true
- },
- showSettingsIcon: true,
- stats: {
- totalBlocks: 0,
- totalMutes: 0,
- blockedUsers: [],
- mutedUsers: []
- }
- };
- // Klasa do zarządzania ustawieniami, używająca GM_setValue/GM_getValue (z settings.js)
- class SettingsManager {
- constructor() {
- this.settings = DEFAULT_SETTINGS;
- this.loadSettings();
- }
- async loadSettings() {
- this.settings = await GM_getValue('settings', DEFAULT_SETTINGS);
- // Scalanie w przypadku braku nowych kluczy w zapisanych ustawieniach
- this.settings = {
- ...DEFAULT_SETTINGS,
- ...this.settings,
- enabledPages: { ...DEFAULT_SETTINGS.enabledPages, ...(this.settings.enabledPages || {}) },
- stats: { ...DEFAULT_SETTINGS.stats, ...(this.settings.stats || {}) }
- };
- return this.settings;
- }
- async saveSettings(newSettings) {
- await GM_setValue('settings', newSettings);
- this.settings = newSettings;
- }
- }
- // Klasy interfejsu użytkownika (z index.js)
- class BlockButtonUI {
- constructor(bgColor, color) {
- this.bgColor = bgColor;
- this.color = color;
- }
- create(tweetUsername, menuOptionsButton, action = 'block') {
- const wrapper = this.createWrapper(menuOptionsButton);
- const blockButton = this.createButton(action);
- const background = this.createBackground();
- wrapper.dataset.username = tweetUsername;
- wrapper.dataset.action = action;
- wrapper.appendChild(background);
- wrapper.appendChild(blockButton);
- this.addHoverEffects(blockButton, background, action);
- return wrapper;
- }
- createWrapper(menuOptionsButton) {
- const grokButton = menuOptionsButton.closest("article")
- .querySelector(CONFIG.selectors.grokActions);
- const wrapper = document.createElement("div");
- wrapper.classList.add(CONFIG.classes.blockButtonWrapper);
- Object.assign(wrapper.style, {
- display: "grid",
- position: "absolute",
- borderRadius: "100%",
- top: "-8px",
- right: grokButton ? "45px" : "15px",
- placeItems: "center",
- gridTemplateAreas: '"icon"',
- gridTemplateColumns: "1fr",
- marginRight: "15px"
- });
- return wrapper;
- }
- createButton(action = 'block') {
- const button = document.createElement("div");
- button.classList.add(CONFIG.classes.blockButton);
- Object.assign(button.style, {
- gridArea: "icon",
- cursor: "pointer",
- textAlign: "center",
- borderRadius: "100%",
- backgroundColor: this.bgColor,
- lineHeight: "10px",
- transition: "all 0.1s ease-in-out",
- boxSizing: "border-box",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- placeSelf: "center"
- });
- button.innerHTML = this.createSVGIcon(action);
- return button;
- }
- createBackground() {
- const background = document.createElement("div");
- background.classList.add(CONFIG.classes.blockButtonBackground);
- Object.assign(background.style, {
- gridArea: "icon",
- backgroundColor: this.bgColor,
- borderRadius: "100%",
- height: "33px",
- width: "33px",
- zIndex: "-1",
- transition: "all 0.1s ease-in-out",
- boxSizing: "border-box"
- });
- return background;
- }
- createSVGIcon(action = 'block') {
- return `<svg style="transition: all 0.1s ease-in-out" class="${CONFIG.classes.blockIcon}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 72 72" width="16" height="16" fill="${this.bgColor}">
- <circle cx="36" cy="36" r="29" stroke="${this.color}" stroke-width="7"/>
- <line x1="17" y1="17" x2="55" y2="55" stroke="${this.color}" stroke-width="7" />
- </svg>`;
- }
- addHoverEffects(button, background, action = 'block') {
- const handleEnter = () => {
- Object.assign(button.style, {
- backgroundColor: "rgba(255,0,64, 0.1)",
- transform: "rotate(270deg)"
- });
- Object.assign(background.style, {
- backgroundColor: "rgba(255,0,64, 0.1)"
- });
- const icon = button.querySelector(`.${CONFIG.classes.blockIcon}`);
- const [circle, line] = [icon.querySelector("circle"), icon.querySelector("line")];
- circle.style.stroke = "rgb(255,0,64)";
- line.style.stroke = "rgb(255,0,64)";
- icon.style.fill = "rgb(255,0,64, 0.1)";
- const username = button.closest(`.${CONFIG.classes.blockButtonWrapper}`)?.dataset.username;
- if (username) {
- this.addBlockButtonHoverTitle(button, username, false, action);
- }
- };
- const handleLeave = () => {
- Object.assign(button.style, {
- backgroundColor: this.bgColor,
- transform: "rotate(0deg)"
- });
- Object.assign(background.style, {
- backgroundColor: this.bgColor
- });
- const icon = button.querySelector(`.${CONFIG.classes.blockIcon}`);
- const [circle, line] = [icon.querySelector("circle"), icon.querySelector("line")];
- circle.style.stroke = this.color;
- line.style.stroke = this.color;
- icon.style.fill = this.bgColor;
- const tooltip = document.querySelector(`#${CONFIG.classes.tooltipText}`);
- tooltip?.remove();
- };
- button.addEventListener("mouseenter", handleEnter);
- button.addEventListener("mouseleave", handleLeave);
- background.addEventListener("mouseenter", handleEnter);
- background.addEventListener("mouseleave", handleLeave);
- }
- addBlockButtonHoverTitle(button, username, isProfile = false, action = 'block') {
- let hasAddedBlockText = document.querySelector(`#${CONFIG.classes.tooltipText}`);
- if (hasAddedBlockText) {
- return;
- }
- const actionText = action.charAt(0).toUpperCase() + action.slice(1);
- let titleParent = document.createElement("div");
- titleParent.id = CONFIG.classes.tooltipText;
- Object.assign(titleParent.style, {
- display: "flex",
- paddingInline: "4px",
- position: "fixed",
- fontSize: "11px",
- backgroundColor: "rgba(91, 112, 131)",
- alignItems: "center",
- justifyContent: "center",
- pointerEvents: "none",
- minHeight: "20px",
- borderRadius: "2px",
- zIndex: "10000",
- color: "#fff",
- fontFamily: "'TwitterChirp', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
- });
- titleParent.innerHTML = `<span>${actionText} ${username}</span>`;
- let blockButtonPosition = button.getBoundingClientRect();
- titleParent.style.top = `${blockButtonPosition.top + 42}px`;
- if (isProfile) {
- titleParent.style.left = `calc(50% + ${blockButtonPosition.left}px - 50% + 20px)`;
- } else {
- titleParent.style.left = `calc(50% + ${blockButtonPosition.left}px - 50% + 15px)`;
- }
- titleParent.style.transform = "translateX(-50%)";
- document.body.appendChild(titleParent);
- }
- }
- class ProfileBlockButtonUI extends BlockButtonUI {
- constructor(bgColor, color, settingsManager) {
- super(bgColor, color);
- this.settingsManager = settingsManager;
- }
- create(page) {
- const button = document.createElement("div");
- Object.assign(button.style, {
- transition: "all 0.1s ease-in-out",
- cursor: "pointer",
- textAlign: "center",
- borderRadius: "100%",
- position: "relative",
- backgroundColor: this.bgColor,
- boxSizing: "border-box",
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- placeSelf: "center",
- marginLeft: "5px",
- width: "40px",
- height: "40px"
- });
- if (page !== "engagements-page") {
- button.style.marginBottom = "6px";
- }
- button.innerHTML = this.createSVGIcon();
- this.addHoverEffects(button);
- return button;
- }
- addHoverEffects(button) {
- button.addEventListener("mouseenter", async () => {
- Object.assign(button.style, {
- backgroundColor: "rgba(255,0,64, 0.1)",
- transform: "rotate(270deg)"
- });
- const icon = button.querySelector(`.${CONFIG.classes.blockIcon}`);
- const [circle, line] = [icon.querySelector("circle"), icon.querySelector("line")];
- circle.style.stroke = "rgb(255,0,64)";
- line.style.stroke = "rgb(255,0,64)";
- icon.style.fill = "rgb(255,0,64, 0.1)";
- let username;
- const parentDiv = button.closest('[data-testid="cellInnerDiv"]');
- if (parentDiv) {
- const userLink = parentDiv.querySelector('a[href^="/"]');
- if (userLink) {
- username = userLink.getAttribute('href').substring(1);
- }
- } else {
- username = location.pathname.split("/")[1];
- }
- if (username) {
- const settings = await this.settingsManager.loadSettings();
- const action = settings.action || 'block';
- this.addBlockButtonHoverTitle(button, "@" + username, !parentDiv, action);
- }
- });
- button.addEventListener("mouseleave", () => {
- Object.assign(button.style, {
- backgroundColor: this.bgColor,
- transform: "rotate(0deg)"
- });
- const icon = button.querySelector(`.${CONFIG.classes.blockIcon}`);
- const [circle, line] = [icon.querySelector("circle"), icon.querySelector("line")];
- circle.style.stroke = this.color;
- line.style.stroke = this.color;
- icon.style.fill = this.bgColor;
- const tooltip = document.querySelector(`#${CONFIG.classes.tooltipText}`);
- tooltip?.remove();
- });
- }
- }
- // Główna klasa aplikacji (z index.js)
- class TwitterBlocker {
- constructor() {
- this.isActionClicked = false;
- this.ownUsername = null;
- this.usernameToAction = null;
- this.blockerBgColor = document.querySelector("meta[name=theme-color]")?.content;
- this.blockerColor = CONFIG.colors[this.blockerBgColor];
- this.settingsManager = new SettingsManager();
- this.initMutationObserver();
- this.initCompleteListener();
- }
- initMutationObserver() {
- const observer = new MutationObserver((mutations) => {
- mutations.forEach((mutation) => {
- if (mutation.addedNodes.length > 0) {
- this.handleDOMMutation(mutation);
- }
- });
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true,
- attributes: false,
- });
- }
- handleDOMMutation(mutation) {
- this.setUsername();
- this.handleConfirmUserBlock(mutation);
- this.handleOptionsMenu(mutation);
- this.addBlockButtonsToTimelines(mutation);
- this.handleThankYouMessage(mutation);
- this.handleProfilePageBlockButton(mutation);
- this.handleEngagementPages(mutation);
- this.handleBlockCompletion(mutation);
- this.handleGrokButtonOverlap(mutation);
- this.addBlockButtonsToEngagementsPages(mutation);
- }
- initCompleteListener() {
- GM_addValueChangeListener('oneClickBlockerCompletion', (name, old_value, new_value, remote) => {
- if (remote && new_value) {
- this.removeBlockedUser(new_value.username);
- }
- });
- }
- setUsername() {
- if (!this.ownUsername) {
- this.ownUsername = document
- .querySelector(CONFIG.selectors.username)
- ?.querySelector("[tabindex]")?.textContent;
- }
- }
- handleConfirmUserBlock(mutation) {
- const confirmButton = mutation.target.querySelector(CONFIG.selectors.confirmButton);
- if (!confirmButton?.classList.contains(CONFIG.classes.confirmationClicked)) {
- this.processActionConfirmation(mutation, confirmButton);
- }
- }
- async processActionConfirmation(mutation, confirmButton) {
- if (!confirmButton) return;
- confirmButton.classList.add(CONFIG.classes.confirmationClicked);
- const dialog = mutation.target.querySelector(CONFIG.selectors.confirmationDialog);
- if (this.usernameToAction) {
- const containsUsername = dialog?.textContent?.toLowerCase()?.includes(this.usernameToAction.toLowerCase());
- if (containsUsername) {
- confirmButton.click();
- }
- }
- if (document.querySelector(CONFIG.selectors.userActions)) {
- confirmButton.click();
- }
- }
- handleOptionsMenu(mutation) {
- const menu = mutation.target.querySelector('[role="menu"]');
- if (menu && !menu.classList.contains(CONFIG.classes.menu)) {
- this.processMenuVisibility(menu);
- }
- }
- async processMenuVisibility(menu) {
- menu.classList.add(CONFIG.classes.menu);
- if (this.isActionClicked) {
- const action = await this.getCurrentAction();
- const actionButton = action === 'mute' ?
- this.getMuteButton() :
- document.querySelector(CONFIG.selectors.blockButton);
- menu.style.opacity = actionButton ? 0 : 1;
- if (!actionButton) menu.remove();
- } else {
- menu.style.opacity = 1;
- }
- }
- getMuteButton() {
- const blockButton = document.querySelector(CONFIG.selectors.blockButton);
- if (!blockButton) return null;
- const isStatusPage = location.pathname.includes('/status/');
- const muteButton = isStatusPage ?
- blockButton.previousElementSibling?.previousElementSibling :
- blockButton.previousElementSibling;
- if (muteButton?.textContent?.toLowerCase().includes('@')) {
- return muteButton;
- }
- return null;
- }
- async triggerMenuOptionClick() {
- const action = await this.getCurrentAction();
- setTimeout(() => {
- const actionButton = action === 'mute' ?
- this.getMuteButton() :
- document.querySelector(CONFIG.selectors.blockButton);
- this.isActionClicked = false;
- if (!actionButton) return;
- actionButton.click();
- }, 20);
- }
- async sendActionRequest(username) {
- const action = await this.getCurrentAction();
- GM_openInTab(`https://x.com/${username}?${action}=true`, { active: false, setParent: true });
- }
- async addBlockButtonsToTimelines(mutation) {
- if (!await this.shouldShowButtonOnPage('timeline')) return;
- const wrapper = this.detectTweetUsernameWrapper(mutation);
- if (wrapper) {
- const usernameDiv = wrapper.querySelector(CONFIG.selectors.userName);
- const menuButton = wrapper.querySelector(CONFIG.selectors.menuCaret);
- this.attachActionButton(usernameDiv, menuButton);
- }
- }
- detectTweetUsernameWrapper(mutation) {
- let nodeOfInterest = null;
- const mutationString = Array.from(mutation.addedNodes)
- .map(node => node.outerHTML)
- .join("");
- const hasMenuCaret = mutationString.includes('data-testid="caret"');
- if (hasMenuCaret) {
- mutation.addedNodes.forEach(node => {
- if (node.querySelector && node.querySelector('[data-testid="caret"]') && node.closest("article")) {
- let parent = node.parentElement;
- while (parent && !parent.querySelector('[data-testid="User-Name"]')) {
- if (parent.nodeName.toLowerCase() === "article") break;
- parent = parent.parentElement;
- }
- if (parent && parent.querySelector('[data-testid="User-Name"]')) {
- nodeOfInterest = parent;
- }
- }
- if (node.getAttribute && node.getAttribute("data-testid") === "cellInnerDiv") {
- nodeOfInterest = node;
- }
- });
- }
- return nodeOfInterest;
- }
- handleThankYouMessage(mutation) {
- const mutationString = Array.from(mutation.addedNodes)
- .map(node => node.outerHTML)
- .join("");
- if (mutationString.includes("X will use this to make your timeline better.")) {
- const wrapper = mutation.target.closest(CONFIG.selectors.cellInnerDiv);
- if (wrapper && wrapper.querySelectorAll('button').length < 3) {
- this.hideElement(wrapper);
- }
- }
- }
- hideElement(element) {
- if (!element) return;
- element.style.height = "0px";
- element.style.visibility = "hidden";
- }
- async handleProfilePageBlockButton(mutation) {
- const pageInfo = this.getProfilePageInfo();
- if (!pageInfo.isProfilePage || pageInfo.isOwnProfilePage) {
- return;
- }
- const pageType = this.getPageType();
- if (!await this.shouldShowButtonOnPage(pageType)) {
- return;
- }
- const userActionsButton = this.getUserActionsButton(mutation);
- if (!userActionsButton || this.hasExistingBlockButton(mutation, userActionsButton)) return;
- this.addProfileBlockButton(userActionsButton);
- }
- getProfilePageInfo() {
- const currentUsername = location.pathname.split("/")[1];
- let isProfilePage = () => {
- return !!document.querySelector(CONFIG.selectors.userActions);
- };
- let isOwnProfilePage = () => {
- return (
- document
- .querySelector('[data-testid="SideNav_AccountSwitcher_Button"]')
- ?.querySelector("[tabindex]")
- ?.textContent?.split("@")?.[1] ==
- location.pathname.split("/")[location.pathname.split("/").length - 1]
- );
- };
- return { isProfilePage: isProfilePage(), isOwnProfilePage: isOwnProfilePage() };
- }
- getUserActionsButton(mutation) {
- return mutation.target.querySelector(CONFIG.selectors.userActions);
- }
- hasExistingBlockButton(mutation, userActionsButton) {
- return (
- mutation.target.querySelector(`.${CONFIG.classes.blockButton}`) ||
- userActionsButton.parentElement.querySelector(`.${CONFIG.classes.blockButton}`)
- );
- }
- addProfileBlockButton(userActionsButton) {
- const blockButton = new ProfileBlockButtonUI(this.blockerBgColor, this.blockerColor, this.settingsManager)
- .create();
- userActionsButton.parentElement.appendChild(blockButton);
- blockButton.classList.add(CONFIG.classes.blockButton);
- this.addProfileBlockButtonListeners(blockButton, userActionsButton);
- this.handleAutoBlock(blockButton);
- }
- addProfileBlockButtonListeners(blockButton, userActionsButton) {
- blockButton.addEventListener("click", () => {
- this.usernameToAction = location.pathname.split("/")[1];
- userActionsButton.click();
- this.triggerMenuOptionClick();
- });
- }
- handleAutoBlock(blockButton) {
- const urlParams = new URLSearchParams(window.location.search);
- if (urlParams.get("block") || urlParams.get("mute")) {
- if (this.checkForError()) {
- this.showError("Something went wrong with the action.");
- setTimeout(() => window.close(), 3000);
- return;
- }
- blockButton.click();
- }
- }
- checkForError() {
- return document
- .querySelector(CONFIG.selectors.primaryColumn)
- ?.textContent.includes("Something went wrong");
- }
- handleGrokButtonOverlap(mutation) {
- const grokButton = mutation.target.querySelector(CONFIG.selectors.grokActions);
- if (grokButton) {
- const blockButton = grokButton.closest("article")
- .querySelector(`.${CONFIG.classes.blockButtonWrapper}`);
- if (blockButton) {
- blockButton.style.right = "45px";
- }
- }
- }
- removeBlockedUser(username) {
- const userCells = document.querySelectorAll(`[href="/${username}"]`);
- userCells.forEach(cell => {
- const userCellParent = cell.closest(CONFIG.selectors.userCell);
- userCellParent?.remove();
- const tweetArticle = cell.closest('article');
- tweetArticle?.remove();
- });
- }
- showError(message) {
- let errorDiv = document.getElementById('one-click-blocker-error');
- if (!errorDiv) {
- errorDiv = document.createElement("div");
- errorDiv.id = 'one-click-blocker-error';
- Object.assign(errorDiv.style, CONFIG.styles.errorWrapper);
- document.body.appendChild(errorDiv);
- }
- errorDiv.textContent = message;
- setTimeout(() => errorDiv.remove(), 5000);
- }
- async getCurrentAction() {
- const settings = await this.settingsManager.loadSettings();
- return settings.action || 'block';
- }
- getPageType() {
- const path = window.location.pathname;
- if (path.includes('/search')) return 'search';
- if (path.includes('/retweets') || path.includes('/likes') || path.includes('/quotes')) return 'engagements';
- if (path.includes('/followers') || path.includes('/verified_followers')) return 'followers';
- if (path.includes('/hashtag')) return 'hashtag';
- if (path.includes('/i/trending')) return 'trending';
- if (path.includes('/notifications')) return 'notifications';
- if (path.includes('/following')) return 'following';
- if (path.includes('/i/communities')) return 'communities';
- if (path === '/home' || path === '/') return 'timeline';
- if (path.match(/\/status\/\d+/)) return 'status';
- if (document.querySelector(CONFIG.selectors.userActions)) return 'profile';
- if (path.includes('/explore')) return 'explore';
- return null;
- }
- async shouldShowButtonOnPage(pageType) {
- const settings = await this.settingsManager.loadSettings();
- const currentPageType = this.getPageType();
- if (currentPageType) {
- return settings.enabledPages?.[currentPageType] ?? true;
- }
- return settings.enabledPages?.[pageType] ?? true;
- }
- async attachActionButton(userNameDiv, menuOptionsButton) {
- if (!userNameDiv) return;
- if (menuOptionsButton.parentElement.querySelector(`.${CONFIG.classes.blockButtonWrapper}`)) return;
- const tweetUsername = userNameDiv.querySelectorAll("[href]")?.[1]?.textContent;
- if (!this.shouldAddActionButton(tweetUsername)) return;
- const action = await this.getCurrentAction();
- const blockButton = new BlockButtonUI(this.blockerBgColor, this.blockerColor)
- .create(tweetUsername, menuOptionsButton, action);
- this.addTweetActionButtonListeners(blockButton, menuOptionsButton);
- const menuOptionsWrapper = menuOptionsButton.parentElement;
- menuOptionsWrapper.insertBefore(blockButton, menuOptionsButton);
- }
- shouldAddActionButton(tweetUsername) {
- return tweetUsername !== this.ownUsername;
- }
- addTweetActionButtonListeners(actionButton, menuOptionsButton) {
- actionButton.addEventListener("click", async (e) => {
- this.usernameToAction = actionButton.dataset.username;
- e.stopPropagation();
- this.triggerMenuClick(menuOptionsButton);
- // this.hideTweetDiv(menuOptionsButton);
- await this.triggerMenuOptionClick();
- });
- }
- triggerMenuClick(menuOptionsButton) {
- this.isActionClicked = true;
- menuOptionsButton.click();
- }
- async handleEngagementPages(mutation) {
- if (!await this.shouldShowButtonOnPage('engagements')) return;
- const userCell = mutation.target.querySelector(CONFIG.selectors.userCell);
- if (!userCell) return;
- const userActions = userCell.querySelector(CONFIG.selectors.userActions);
- if (!userActions || userActions.parentElement.querySelector(`.${CONFIG.classes.blockButton}`)) return;
- const blockButton = new ProfileBlockButtonUI(this.blockerBgColor, this.blockerColor, this.settingsManager)
- .create('engagements-page');
- userActions.parentElement.appendChild(blockButton);
- blockButton.classList.add(CONFIG.classes.blockButton);
- blockButton.addEventListener('click', () => {
- const username = userCell.querySelector('a[href^="/"]')?.getAttribute('href')?.substring(1);
- if (username) {
- this.usernameToAction = username;
- userActions.click();
- this.triggerMenuOptionClick();
- }
- });
- }
- handleBlockCompletion(mutation) {
- const mutationString = Array.from(mutation.addedNodes)
- .map(node => node.outerHTML)
- .join("");
- const isBlocked = mutationString.includes("blocked");
- const isMuted = mutationString.includes("muted");
- if (isBlocked || isMuted) {
- let username = this.usernameToAction;
- const urlParams = new URLSearchParams(window.location.search);
- const isFromUrl = urlParams.get("block") || urlParams.get("mute");
- if (!username && isFromUrl) {
- username = location.pathname.split("/")[1];
- }
- if (username) {
- username = username.replace("@", "");
- this.saveUserToStorage(username, isBlocked ? 'block' : 'mute');
- this.usernameToAction = null;
- if (isFromUrl) {
- GM_setValue('oneClickBlockerCompletion', {
- username: username,
- action: isBlocked ? 'block' : 'mute',
- timestamp: Date.now()
- });
- setTimeout(() => window.close(), 500);
- }
- }
- }
- }
- async saveUserToStorage(username, action) {
- try {
- const settings = await this.settingsManager.loadSettings();
- if (!settings.stats) settings.stats = { ...DEFAULT_SETTINGS.stats };
- if (!settings.stats.blockedUsers) settings.stats.blockedUsers = [];
- if (!settings.stats.mutedUsers) settings.stats.mutedUsers = [];
- if (action === 'block') {
- if (!settings.stats.blockedUsers.includes(username)) {
- settings.stats.blockedUsers.push(username);
- settings.stats.totalBlocks = (settings.stats.totalBlocks || 0) + 1;
- }
- } else if (action === 'mute') {
- if (!settings.stats.mutedUsers.includes(username)) {
- settings.stats.mutedUsers.push(username);
- settings.stats.totalMutes = (settings.stats.totalMutes || 0) + 1;
- }
- }
- await this.settingsManager.saveSettings(settings);
- } catch (error) {
- console.error('Error saving user to storage:', error);
- }
- }
- async addBlockButtonsToEngagementsPages(mutation) {
- let isAUserProfilePage = document.querySelector('[data-testid="userActions"]');
- const pageType = this.getPageType();
- if (!await this.shouldShowButtonOnPage(pageType) || isAUserProfilePage) return;
- if (!mutation.target.querySelector || !mutation.target.querySelector('[data-testid="cellInnerDiv"]')?.textContent) {
- return;
- }
- const followButtons = mutation.target.querySelectorAll(CONFIG.selectors.followButton);
- followButtons.forEach((followButton) => {
- if (followButton.closest(CONFIG.selectors.hoverCard) || followButton.parentElement.querySelector(".sw-block-button")) return;
- const followButtonParentTweet = followButton.closest(CONFIG.selectors.tweet);
- if (followButtonParentTweet) return;
- const blockButton = new ProfileBlockButtonUI(this.blockerBgColor, this.blockerColor, this.settingsManager)
- .create("engagements-page");
- followButton.parentElement.appendChild(blockButton);
- followButton.parentElement.style.flexDirection = "row";
- blockButton.classList.add("sw-block-button");
- blockButton.addEventListener("click", (e) => {
- e.preventDefault();
- e.stopPropagation();
- const username = followButton?.closest('[data-testid="UserCell"]')?.querySelector('a[href^="/"]')?.getAttribute('href')?.substring(1);
- if (username) {
- this.usernameToAction = username;
- this.sendActionRequest(username);
- this.removeBlockedUser(username);
- }
- });
- });
- }
- }
- // --- Panel Ustawień ---
- const settingsPanelCSS = `
- :root { --twitter-blue: rgb(29, 155, 240); --twitter-background: rgb(239, 243, 244); --twitter-border: rgb(207, 217, 222); --twitter-text: rgb(15, 20, 25); --twitter-hover: rgba(15, 20, 25, 0.1); }
- #ocb-settings-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 680px; max-height: 90vh; background-color: white; border-radius: 16px; z-index: 10000; display: flex; flex-direction: column; box-shadow: rgba(101, 119, 134, 0.2) 0px 0px 15px, rgba(101, 119, 134, 0.15) 0px 0px 3px 1px; }
- #ocb-settings-overlay { position: fixed; inset: 0px; background-color: rgba(91, 112, 131, 0.4); z-index: 9999; }
- #ocb-settings-panel .header { display: flex; align-items: center; padding: 16px; border-bottom: 1px solid var(--twitter-border); }
- #ocb-settings-panel h1 { font-size: 20px; font-weight: bold; margin: 0; flex-grow: 1; }
- #ocb-settings-panel .close-btn { cursor: pointer; font-size: 24px; padding: 0 8px; }
- #ocb-settings-panel .nav { display: flex; gap: 8px; padding: 0 16px; border-bottom: 1px solid var(--twitter-border); flex-shrink: 0; overflow-x: auto; }
- #ocb-settings-panel .nav-item { padding: 12px 16px; cursor: pointer; font-weight: bold; transition: background-color 0.2s; white-space: nowrap; border-bottom: 2px solid transparent;}
- #ocb-settings-panel .nav-item:hover { background-color: var(--twitter-hover); }
- #ocb-settings-panel .nav-item.active { color: var(--twitter-blue); border-bottom-color: var(--twitter-blue); }
- #ocb-settings-panel .container { padding: 20px; overflow-y: auto; }
- #ocb-settings-panel .section { padding-bottom: 20px; border-bottom: 1px solid var(--twitter-border); margin-bottom: 20px; }
- #ocb-settings-panel .section-title { font-size: 18px; font-weight: bold; margin-bottom: 16px; }
- #ocb-settings-panel .option-group { display: flex; flex-direction: column; gap: 12px; }
- #ocb-settings-panel .option { display: flex; align-items: center; }
- #ocb-settings-panel .option label { cursor: pointer; padding: 8px; flex-grow: 1; }
- #ocb-settings-panel .button { background-color: var(--twitter-blue); color: white; border: none; padding: 12px 24px; border-radius: 20px; font-weight: bold; cursor: pointer; }
- #ocb-settings-panel .button:hover { background-color: rgb(26, 140, 216); }
- #ocb-settings-panel .button.danger { background-color: rgb(231, 76, 60); }
- #ocb-settings-panel .button.danger:hover { background-color: rgb(192, 57, 43); }
- #ocb-settings-panel .tab-content { display: none; }
- #ocb-settings-panel .tab-content.active { display: block; }
- #ocb-settings-panel .blocked-user { display: block; background-color: var(--twitter-background); border-radius: 8px; margin-bottom: 8px; }
- #ocb-settings-panel .user-link { color: var(--twitter-text); text-decoration: none; display: block; padding: 16px; }
- #ocb-settings-panel .user-link:hover { color: var(--twitter-blue); }
- #ocb-settings-panel .save-notice { text-align: center; color: var(--twitter-blue); padding-top: 10px; opacity: 0; transition: opacity 0.3s; }
- #ocb-settings-panel .save-notice.visible { opacity: 1; }
- #ocb-settings-panel .stats { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
- #ocb-settings-panel .stat-card { background-color: var(--twitter-background); padding: 16px; border-radius: 16px; text-align: center; }
- #ocb-settings-panel .stat-number { font-size: 24px; font-weight: bold; }
- #ocb-settings-panel .stat-label { color: rgb(83, 100, 113); }
- #ocb-settings-panel .button-group { display: flex; gap: 8px; flex-wrap: wrap; }
- `;
- const settingsPanelHTML = `
- <div id="ocb-settings-overlay"></div>
- <div id="ocb-settings-panel">
- <div class="header">
- <h1>One-Click Blocker Settings</h1>
- <span class="close-btn">×</span>
- </div>
- <div class="nav">
- <div class="nav-item active" data-tab="settings">Settings</div>
- <div class="nav-item" data-tab="blocked">Blocked</div>
- <div class="nav-item" data-tab="muted">Muted</div>
- <div class="nav-item" data-tab="stats">Statistics</div>
- <div class="nav-item" data-tab="backup">Backup</div>
- </div>
- <div class="container">
- <div id="settings" class="tab-content active">
- <!-- Zawartość z options.html -->
- </div>
- <div id="blocked" class="tab-content"></div>
- <div id="muted" class="tab-content"></div>
- <div id="stats" class="tab-content"></div>
- <div id="backup" class="tab-content"></div>
- <div class="save-notice">Settings saved!</div>
- </div>
- </div>
- `;
- class OptionsManager {
- constructor(settingsManager) {
- this.settingsManager = settingsManager;
- this.createPanel();
- this.loadSettings();
- this.initializeUI();
- }
- createPanel() {
- GM_addStyle(settingsPanelCSS);
- const panelContainer = document.createElement('div');
- panelContainer.innerHTML = settingsPanelHTML;
- document.body.appendChild(panelContainer);
- this.panel = document.getElementById('ocb-settings-panel');
- this.overlay = document.getElementById('ocb-settings-overlay');
- }
- async loadSettings() {
- const settings = await this.settingsManager.loadSettings();
- this.updateUI(settings);
- }
- async saveSettings() {
- const currentSettings = await this.settingsManager.loadSettings();
- const settings = {
- action: document.querySelector('#ocb-settings-panel input[name="action"]:checked').value,
- enabledPages: Object.fromEntries(
- Array.from(document.querySelectorAll('#ocb-settings-panel input[name="pages"]'))
- .map(checkbox => [checkbox.value, checkbox.checked])
- ),
- showSettingsIcon: document.getElementById('show-settings-icon').checked,
- stats: currentSettings.stats
- };
- await this.settingsManager.saveSettings(settings);
- this.showSaveNotice();
- }
- updateUI(settings) {
- this.panel.querySelector('#settings').innerHTML = `
- <div class="section">
- <div class="section-title">Default Action</div>
- <div class="option-group">
- <div class="option"><label><input type="radio" name="action" value="block" ${settings.action === 'block' ? 'checked' : ''}> Block</label></div>
- <div class="option"><label><input type="radio" name="action" value="mute" ${settings.action === 'mute' ? 'checked' : ''}> Mute</label></div>
- </div>
- </div>
- <div class="section">
- <div class="section-title">Interface Settings</div>
- <div class="option-group"><div class="option"><label><input type="checkbox" id="show-settings-icon" ${settings.showSettingsIcon ? 'checked' : ''}> Show settings icon</label></div></div>
- </div>
- <div class="section">
- <div class="section-title">Show Button On</div>
- <div class="option-group">
- ${Object.entries(DEFAULT_SETTINGS.enabledPages).map(([key, value]) => `
- <div class="option"><label><input type="checkbox" name="pages" value="${key}" ${settings.enabledPages[key] ? 'checked' : ''}> ${key.charAt(0).toUpperCase() + key.slice(1)}</label></div>
- `).join('')}
- </div>
- </div>
- `;
- this.panel.querySelector('#blocked').innerHTML = `<div class="section"><div class="section-title">Blocked Users</div><div id="blocked-users-list">${this.renderUserList(settings.stats.blockedUsers)}</div></div>`;
- this.panel.querySelector('#muted').innerHTML = `<div class="section"><div class="section-title">Muted Users</div><div id="muted-users-list">${this.renderUserList(settings.stats.mutedUsers)}</div></div>`;
- this.panel.querySelector('#stats').innerHTML = `<div class="section"><div class="section-title">Action Statistics</div><div class="stats"><div class="stat-card"><div class="stat-number">${settings.stats.totalBlocks}</div><div class="stat-label">Total Blocks</div></div><div class="stat-card"><div class="stat-number">${settings.stats.totalMutes}</div><div class="stat-label">Total Mutes</div></div></div></div>`;
- this.panel.querySelector('#backup').innerHTML = `<div class="section"><div class="section-title">Backup & Restore</div><div class="button-group"><button class="button" id="export-data">Export</button><button class="button" id="import-data">Import</button><button class="button danger" id="clear-data">Reset</button></div></div>`;
- }
- renderUserList(users) {
- return users.length ? users.map(user => `<div class="blocked-user"><a href="https://x.com/${user}" target="_blank" class="user-link">@${user}</a></div>`).join('') : '<p>No users yet.</p>';
- }
- showSaveNotice() {
- const notice = this.panel.querySelector('.save-notice');
- notice.classList.add('visible');
- setTimeout(() => notice.classList.remove('visible'), 2000);
- }
- initializeUI() {
- this.panel.addEventListener('change', e => {
- if (e.target.matches('input[type="radio"], input[type="checkbox"]')) {
- this.saveSettings();
- }
- });
- this.panel.querySelector('.close-btn').addEventListener('click', () => this.hide());
- this.overlay.addEventListener('click', () => this.hide());
- this.panel.querySelectorAll('.nav-item').forEach(item => {
- item.addEventListener('click', () => {
- this.panel.querySelector('.nav-item.active').classList.remove('active');
- item.classList.add('active');
- this.panel.querySelector('.tab-content.active').classList.remove('active');
- this.panel.querySelector(`#${item.dataset.tab}`).classList.add('active');
- });
- });
- this.panel.querySelector('#export-data').addEventListener('click', () => this.exportData());
- this.panel.querySelector('#import-data').addEventListener('click', () => this.importData());
- this.panel.querySelector('#clear-data').addEventListener('click', () => this.clearData());
- }
- async exportData() {
- const settings = await this.settingsManager.loadSettings();
- const blob = new Blob([JSON.stringify(settings, null, 2)], { type: 'application/json' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = `one-click-blocker-backup.json`;
- a.click();
- URL.revokeObjectURL(url);
- }
- importData() {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = 'application/json';
- input.onchange = async (e) => {
- const file = e.target.files[0];
- if (!file) return;
- const text = await file.text();
- const importedData = JSON.parse(text);
- await this.settingsManager.saveSettings(importedData);
- this.updateUI(importedData);
- alert('Settings imported successfully!');
- };
- input.click();
- }
- async clearData() {
- if (confirm('Are you sure you want to reset all settings and data? This cannot be undone.')) {
- await this.settingsManager.saveSettings(DEFAULT_SETTINGS);
- this.updateUI(DEFAULT_SETTINGS);
- alert('Settings have been reset.');
- }
- }
- show() { this.panel.style.display = 'flex'; this.overlay.style.display = 'block'; }
- hide() { this.panel.style.display = 'none'; this.overlay.style.display = 'none'; }
- }
- // --- Inicjalizacja ---
- const twitterBlocker = new TwitterBlocker();
- let optionsManagerInstance = null;
- function showSettingsPanel() {
- if (!optionsManagerInstance) {
- optionsManagerInstance = new OptionsManager(twitterBlocker.settingsManager);
- }
- optionsManagerInstance.show();
- }
- GM_registerMenuCommand('1-Click Blocker Settings', showSettingsPanel);
- })();
Advertisement
Add Comment
Please, Sign In to add comment