您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hides Instagram posts that are suggested, sponsored, or prompt for "Follow" using a flexible configuration.
// ==UserScript== // @name CleanGram // @namespace http://tampermonkey.net/ // @version 0.0.2 // @description Hides Instagram posts that are suggested, sponsored, or prompt for "Follow" using a flexible configuration. // @author JJJ // @match https://www.instagram.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=instagram.com // @license MIT // @run-at document-end // ==/UserScript== (function () { 'use strict'; // Add logger configuration const Logger = { styles: { info: 'color: #2196F3; font-weight: bold', warning: 'color: #FFC107; font-weight: bold', success: 'color: #4CAF50; font-weight: bold', error: 'color: #F44336; font-weight: bold' }, prefix: '[CleanGram]', getTimestamp() { return new Date().toISOString().split('T')[1].slice(0, -1); }, info(msg) { console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.info); }, warning(msg) { console.warn(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.warning); }, success(msg) { console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.success); }, error(msg) { console.error(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.error); } }; // Configuration: adjust wait time, target tags, and blacklist phrases as needed. const CONFIG = { waitLength: 500, // delay after a click to re-run cleanup // Targeting ARTICLE elements (Instagram posts) is usually safest, // but you can add additional tag names (e.g., 'DIV', 'SPAN') if desired. elementsToClean: ['ARTICLE'], // Updated selectors with more precise targeting selectors: { sponsored: '[data-ad-preview="message"]', suggested: 'div[role="button"] span[dir="auto"]:only-child', followButton: 'div[role="button"]:not([aria-disabled="true"]) div[class*="x1i10hfl"]', suggestedLabel: 'span[dir="auto"]:first-child' } }; /** * More precise check for unwanted content */ const containsBannedContent = (element) => { // Check for sponsored content if (element.querySelector(CONFIG.selectors.sponsored)) { Logger.warning(`Found sponsored content: "${element.textContent.trim().slice(0, 30)}..."`); return true; } // Check for suggested posts with more precise targeting const suggestedHeader = element.querySelector(CONFIG.selectors.suggested); if (suggestedHeader) { const headerText = suggestedHeader.textContent.trim().toLowerCase(); if (headerText === 'suggested for you') { return true; } } // Check for standard suggested post structure const suggestedLabel = element.querySelector(CONFIG.selectors.suggestedLabel); if (suggestedLabel && suggestedLabel.textContent.trim().toLowerCase() === 'suggested for you' && element.querySelector(CONFIG.selectors.followButton)) { return true; } return false; }; /** * Instead of removing an element (which might disrupt layout), * we hide it non-destructively. * * @param {Element} element */ const hideElement = (element) => { element.style.visibility = 'hidden'; element.style.height = '0px'; element.style.overflow = 'hidden'; Logger.success(`Hidden element: ${element.tagName} (${element.textContent.trim().slice(0, 30)}...)`); }; /** * Check all elements specified in CONFIG.elementsToClean for banned text. * If banned text is found, the element is hidden. */ const cleanElements = () => { Logger.info('Starting cleanup scan...'); let hiddenCount = 0; CONFIG.elementsToClean.forEach(tag => { const elements = document.querySelectorAll(tag); Logger.info(`Scanning ${elements.length} ${tag} elements`); elements.forEach(element => { if (containsBannedContent(element)) { hideElement(element); hiddenCount++; } }); }); if (hiddenCount > 0) { Logger.success(`Cleanup complete: ${hiddenCount} elements hidden`); } }; /** * MutationObserver callback: * Checks for newly added elements matching the target tags and hides them if needed. * * @param {MutationRecord[]} mutationList */ const observerCallback = (mutationList) => { let newElements = 0; let hiddenElements = 0; mutationList.forEach(mutation => { if (mutation.type === 'childList' && mutation.addedNodes.length) { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && CONFIG.elementsToClean.includes(node.tagName)) { newElements++; if (containsBannedContent(node)) { hideElement(node); hiddenElements++; } } }); } }); if (newElements > 0) { Logger.info(`Processed ${newElements} new elements, hidden ${hiddenElements}`); } }; // Wait for the page to load so we can start cleaning up. window.addEventListener('load', () => { Logger.info('CleanGram initialized'); cleanElements(); // Setup MutationObserver on the main content area if available; fallback to document.body. const targetNode = document.querySelector('main') || document.body; const observer = new MutationObserver(observerCallback); observer.observe(targetNode, { childList: true, subtree: true }); Logger.success('MutationObserver activated'); // Optional: re-run cleanup a short time after any click event // (useful when dynamic content loads after user interaction). document.addEventListener('click', () => { Logger.info('Click detected, scheduling cleanup...'); setTimeout(cleanElements, CONFIG.waitLength); }); }); })();