SuperAPI AI Directory

userscripts

Frontend & websites

View repository

Updated 10-15 21:08

About this skill

Userscript Development

Guide for writing Tampermonkey/Violentmonkey userscripts.

Workflow

Phase 1: Requirements

Ask one at a time:

  1. Target site - URL pattern (e.g., https://example.com/*)
  2. Goal - CSS styling, add feature, remove element, keyboard shortcut, etc.
  3. Output location - Where to save the .user.js file

Phase 2: DOM Inspection

Option A: Fetch URL directly (try first for public sites)

  • Use WebFetch to retrieve page HTML
  • Search for relevant selectors, CSS variables, data attributes
  • Works for public sites not behind login

Option B: User provides MHTML/HTML file

  • If fetch fails (login required, dynamic content), instruct: "Save the page (Cmd+S / Ctrl+S) as MHTML or 'Webpage, Complete'"
  • Grep for patterns: class=, data-, id=, -- (CSS variables)
  • Prefer stable selectors: data-testid, data-*, semantic IDs over generated class names

Option C: User provides selectors

  • Guide user to inspect element in browser devtools
  • Ask for: element selector, parent structure, relevant CSS variables

Phase 3: Implementation

  1. Choose @run-at timing based on task type
  2. Choose @grant level (start with none)
  3. Write script using metadata template below
  4. Save to user-specified location

Phase 4: Testing

  • Instruct: "Install in Tampermonkey/Violentmonkey and reload the page"
  • If issues: iterate on selectors or timing

Metadata Template

// ==UserScript==
// @name            Script Name
// @namespace       your-namespace
// @version         0.0.1
// @description     Brief description
// @icon            https://www.google.com/s2/favicons?sz=64&domain=example.com
// @homepage        https://github.com/username/repo
// @match           https://example.com/*
// @exclude         https://example.com/admin/*
// @author          Author Name
// @run-at          document-end
// @noframes
// @grant           none
// ==/UserScript==

Full Templates

CSS Override (copy-paste ready)

// ==UserScript==
// @name            Site Name - Custom Styles
// @namespace       your-namespace
// @version         0.0.1
// @description     Override styles on example.com
// @icon            https://www.google.com/s2/favicons?sz=64&domain=example.com
// @match           https://example.com/*
// @author          Your Name
// @run-at          document-start
// @grant           none
// ==/UserScript==

;(() => {
  'use strict'

  const style = document.createElement('style')
  style.textContent = `
    /* Hide element */
    .annoying-banner {
      display: none !important;
    }

    /* Override CSS variable */
    :root {
      --site-font: -apple-system, BlinkMacSystemFont, system-ui, sans-serif !important;
    }
  `
  document.documentElement.appendChild(style)
})()

DOM Manipulation (copy-paste ready)

// ==UserScript==
// @name            Site Name - DOM Tweaks
// @namespace       your-namespace
// @version         0.0.1
// @description     Modify DOM on example.com
// @icon            https://www.google.com/s2/favicons?sz=64&domain=example.com
// @match           https://example.com/*
// @author          Your Name
// @run-at          document-end
// @noframes
// @grant           none
// ==/UserScript==

;(() => {
  'use strict'

  // Remove elements
  document.querySelectorAll('.unwanted').forEach(el => el.remove())

  // Modify elements
  document.querySelectorAll('a.external').forEach(link => {
    link.setAttribute('target', '_blank')
  })

  // Add keyboard shortcut
  document.addEventListener('keydown', e => {
    if (e.target.matches('input, textarea, [contenteditable]')) return

    if (e.key === 'j' && !e.ctrlKey && !e.metaKey && !e.altKey) {
      e.preventDefault()
      // action here
    }
  })
})()

Dynamic Content (copy-paste ready)

// ==UserScript==
// @name            Site Name - Dynamic Handler
// @namespace       your-namespace
// @version         0.0.1
// @description     Handle dynamically loaded content on example.com
// @icon            https://www.google.com/s2/favicons?sz=64&domain=example.com
// @match           https://example.com/*
// @author          Your Name
// @run-at          document-end
// @noframes
// @grant           none
// ==/UserScript==

;(() => {
  'use strict'

  const processElement = el => {
    // modify element
  }

  // Process existing elements
  document.querySelectorAll('.target').forEach(processElement)

  // Watch for new elements
  const observer = new MutationObserver(mutations => {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        if (node.nodeType !== Node.ELEMENT_NODE) continue
        if (node.matches('.target')) processElement(node)
        node.querySelectorAll?.('.target').forEach(processElement)
      }
    }
  })

  observer.observe(document.body, { childList: true, subtree: true })
})()

@run-at Timing

Value Use Case
document-start CSS injection, intercept before render
document-end DOM manipulation (DOM ready, images loading)
document-idle Non-critical, after page fully loads

@grant Options

Value When to Use
none Simple scripts, no sandbox, direct page access
GM_addStyle CSS injection helper function
GM_getValue / GM_setValue Persistent storage across sessions
GM_xmlhttpRequest Cross-origin requests (also needs @connect)
GM_registerMenuCommand Add items to extension popup menu
GM_setClipboard Copy to clipboard
unsafeWindow Access page's JavaScript context

Common Patterns

CSS Injection

// @run-at document-start
// @grant none

const style = document.createElement('style')
style.textContent = `
  .selector {
    display: none !important;
  }
`
document.documentElement.appendChild(style)

CSS Variable Override

// @run-at document-start

const style = document.createElement('style')
style.textContent = `
  :root {
    --custom-property: value !important;
  }
`
document.documentElement.appendChild(style)

DOM Query + Modify

// @run-at document-end

document.querySelectorAll('.selector').forEach(el => {
  el.style.display = 'none'
})

Keyboard Shortcut

// @run-at document-end

document.addEventListener('keydown', e => {
  // Skip if typing in input/textarea
  if (e.target.matches('input, textarea, [contenteditable]')) return

  if (e.key === 'j' && !e.ctrlKey && !e.metaKey && !e.altKey) {
    e.preventDefault()
    // action here
  }
})

MutationObserver (Dynamic Content)

// @run-at document-end

const observer = new MutationObserver(mutations => {
  for (const mutation of mutations) {
    for (const node of mutation.addedNodes) {
      if (node.matches?.('.target-selector')) {
        // handle new element
      }
    }
  }
})

observer.observe(document.body, { childList: true, subtree: true })

GM_addStyle (with grant)

// @grant GM_addStyle

GM_addStyle(`
  .selector {
    display: none !important;
  }
`)

Advanced Patterns

See @references/advanced-patterns.md for full implementations:

Pattern Use Case
Wait for element Poll with requestAnimationFrame until element exists
isTyping helper Skip shortcuts when user is in input/textarea
Scroll with offset Smooth scroll accounting for fixed headers
Dark mode CSS media query, meta tag, or CSS variables
URL parsing Extract domain/path with new URL()
GM_addElement Programmatic element creation
SPA navigation Handle YouTube events, History API
Debug logging Toggleable console output
Focus management Track selection with keyboard nav
TreeWalker Iterate text nodes for search/highlight
CSS Highlight API Highlight without DOM modification
Canvas overlay Draw scroll position markers

Selector Strategy

Prefer (stable):

  • data-testid="value" - test IDs rarely change
  • data-* attributes - semantic, intentional
  • Semantic IDs - #main-content, #sidebar
  • Tag + role - button[role="tab"]

Avoid (fragile):

  • Generated classes - .css-1a2b3c, .sc-abc123
  • Deep nesting - div > div > div > span
  • Index-based - :nth-child(3)

Debugging Tips

  1. Test selectors in console first - document.querySelectorAll('.selector')
  2. Check timing - if elements missing, try document-idle or MutationObserver
  3. Inspect network - for dynamic content, elements may load after initial render
  4. CSS specificity - use !important to override site styles
  5. Console errors - check devtools console for script errors

Reference

  • @references/api-reference.md - All metadata headers, @match patterns, GM_* functions
  • @references/advanced-patterns.md - SPA handling, dark mode, scroll markers, TreeWalker, etc.