Google SEO Filter Sidebar

Adds a powerful SEO filter sidebar to Google Search with filetype, date range, site filter, keyword location, and more.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Google SEO Filter Sidebar
// @namespace    https://github.com/seo-sidebar
// @version      1.0.0
// @description  Adds a powerful SEO filter sidebar to Google Search with filetype, date range, site filter, keyword location, and more.
// @author       SEO Sidebar
// @match        https://www.google.com/search*
// @match        https://google.com/search*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  // ─── Prevent double injection ───────────────────────────────────────────────
  if (document.getElementById('seo-sidebar-root')) return;

  // ─── Styles ─────────────────────────────────────────────────────────────────
  const style = document.createElement('style');
  style.textContent = `
    @import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=DM+Sans:wght@400;500;600&display=swap');

    #seo-sidebar-toggle {
      position: fixed;
      top: 50%;
      right: 0;
      transform: translateY(-50%);
      z-index: 99999;
      background: #1a1a2e;
      color: #e0e0ff;
      border: none;
      border-radius: 8px 0 0 8px;
      padding: 10px 6px;
      cursor: pointer;
      font-size: 18px;
      writing-mode: vertical-rl;
      letter-spacing: 2px;
      font-family: 'DM Mono', monospace;
      font-size: 11px;
      font-weight: 500;
      text-transform: uppercase;
      gap: 6px;
      display: flex;
      align-items: center;
      box-shadow: -2px 0 12px rgba(0,0,0,0.3);
      transition: background 0.2s, right 0.3s ease;
    }

    #seo-sidebar-toggle:hover {
      background: #16213e;
    }

    #seo-sidebar-toggle .toggle-arrow {
      writing-mode: horizontal-tb;
      font-size: 14px;
      margin-bottom: 6px;
    }

    #seo-sidebar-root {
      position: fixed;
      top: 0;
      right: -340px;
      width: 320px;
      height: 100vh;
      z-index: 99998;
      background: #0f0f1a;
      color: #e0e0ff;
      font-family: 'DM Sans', sans-serif;
      display: flex;
      flex-direction: column;
      box-shadow: -4px 0 30px rgba(0,0,0,0.5);
      transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
      border-left: 1px solid rgba(255,255,255,0.07);
    }

    #seo-sidebar-root.open {
      right: 0;
    }

    #seo-sidebar-root.open ~ #seo-sidebar-toggle {
      right: 320px;
    }

    .seo-header {
      padding: 18px 20px 14px;
      border-bottom: 1px solid rgba(255,255,255,0.08);
      display: flex;
      align-items: center;
      justify-content: space-between;
    }

    .seo-header h2 {
      font-family: 'DM Mono', monospace;
      font-size: 12px;
      font-weight: 500;
      letter-spacing: 2px;
      text-transform: uppercase;
      color: #7c7cff;
      margin: 0;
    }

    .seo-header span {
      font-size: 10px;
      color: rgba(224,224,255,0.35);
      font-family: 'DM Mono', monospace;
    }

    .seo-body {
      flex: 1;
      overflow-y: auto;
      padding: 16px 20px;
      display: flex;
      flex-direction: column;
      gap: 20px;
    }

    .seo-body::-webkit-scrollbar { width: 4px; }
    .seo-body::-webkit-scrollbar-track { background: transparent; }
    .seo-body::-webkit-scrollbar-thumb { background: rgba(124,124,255,0.3); border-radius: 2px; }

    .seo-section {
      display: flex;
      flex-direction: column;
      gap: 8px;
    }

    .seo-section-label {
      font-family: 'DM Mono', monospace;
      font-size: 10px;
      font-weight: 500;
      letter-spacing: 1.5px;
      text-transform: uppercase;
      color: rgba(255,255,255,0.3);
      display: flex;
      align-items: center;
      gap: 6px;
    }

    .seo-section-label::after {
      content: '';
      flex: 1;
      height: 1px;
      background: rgba(255,255,255,0.07);
    }

    .seo-input, .seo-select {
      width: 100%;
      background: rgba(255,255,255,0.05);
      border: 1px solid rgba(255,255,255,0.1);
      border-radius: 7px;
      color: #e0e0ff;
      font-family: 'DM Sans', sans-serif;
      font-size: 13px;
      padding: 9px 12px;
      outline: none;
      box-sizing: border-box;
      transition: border-color 0.2s, background 0.2s;
      appearance: none;
    }

    .seo-input:focus, .seo-select:focus {
      border-color: #7c7cff;
      background: rgba(124,124,255,0.08);
    }

    .seo-input::placeholder {
      color: rgba(224,224,255,0.25);
    }

    .seo-select {
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%237c7cff' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: right 12px center;
      padding-right: 32px;
      cursor: pointer;
    }

    .seo-select option {
      background: #1a1a2e;
      color: #e0e0ff;
    }

    .seo-date-row {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 8px;
    }

    .seo-date-wrap {
      display: flex;
      flex-direction: column;
      gap: 4px;
    }

    .seo-date-wrap small {
      font-size: 10px;
      color: rgba(224,224,255,0.3);
      font-family: 'DM Mono', monospace;
      padding-left: 2px;
    }

    input[type="date"].seo-input::-webkit-calendar-picker-indicator {
      filter: invert(0.7) sepia(1) saturate(3) hue-rotate(200deg);
      cursor: pointer;
    }

    .seo-chip-row {
      display: flex;
      flex-wrap: wrap;
      gap: 6px;
    }

    .seo-chip {
      background: rgba(255,255,255,0.05);
      border: 1px solid rgba(255,255,255,0.1);
      border-radius: 20px;
      padding: 5px 11px;
      font-size: 12px;
      cursor: pointer;
      color: rgba(224,224,255,0.6);
      font-family: 'DM Mono', monospace;
      transition: all 0.15s;
      user-select: none;
    }

    .seo-chip:hover {
      border-color: #7c7cff;
      color: #7c7cff;
    }

    .seo-chip.active {
      background: rgba(124,124,255,0.15);
      border-color: #7c7cff;
      color: #a0a0ff;
    }

    .seo-footer {
      padding: 16px 20px;
      border-top: 1px solid rgba(255,255,255,0.08);
      display: flex;
      flex-direction: column;
      gap: 8px;
    }

    .seo-btn {
      width: 100%;
      padding: 11px;
      border: none;
      border-radius: 8px;
      font-family: 'DM Sans', sans-serif;
      font-size: 13px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s;
    }

    .seo-btn-primary {
      background: #7c7cff;
      color: #fff;
    }

    .seo-btn-primary:hover {
      background: #9090ff;
      transform: translateY(-1px);
      box-shadow: 0 4px 16px rgba(124,124,255,0.35);
    }

    .seo-btn-secondary {
      background: rgba(255,255,255,0.05);
      color: rgba(224,224,255,0.5);
      border: 1px solid rgba(255,255,255,0.08);
    }

    .seo-btn-secondary:hover {
      background: rgba(255,255,255,0.09);
      color: rgba(224,224,255,0.8);
    }

    .seo-query-preview {
      font-family: 'DM Mono', monospace;
      font-size: 11px;
      color: rgba(124,124,255,0.6);
      background: rgba(124,124,255,0.06);
      border: 1px solid rgba(124,124,255,0.15);
      border-radius: 6px;
      padding: 8px 10px;
      word-break: break-all;
      min-height: 36px;
      line-height: 1.6;
    }

    .seo-query-preview-label {
      font-size: 9px;
      letter-spacing: 1.5px;
      text-transform: uppercase;
      color: rgba(255,255,255,0.2);
      font-family: 'DM Mono', monospace;
      margin-bottom: 4px;
    }

    .seo-toast {
      position: fixed;
      bottom: 24px;
      right: 24px;
      background: #1a1a2e;
      color: #a0a0ff;
      border: 1px solid rgba(124,124,255,0.3);
      border-radius: 8px;
      padding: 10px 16px;
      font-family: 'DM Mono', monospace;
      font-size: 12px;
      z-index: 999999;
      opacity: 0;
      transform: translateY(8px);
      transition: all 0.25s;
      pointer-events: none;
    }
    .seo-toast.show {
      opacity: 1;
      transform: translateY(0);
    }
  `;
  document.head.appendChild(style);

  // ─── Sidebar HTML ────────────────────────────────────────────────────────────
  const sidebar = document.createElement('div');
  sidebar.id = 'seo-sidebar-root';
  sidebar.innerHTML = `
    <div class="seo-header">
      <h2>⬡ SEO Filters</h2>
      <span>google operator builder</span>
    </div>
    <div class="seo-body">

      <div class="seo-section">
        <div class="seo-section-label">Keyword</div>
        <input class="seo-input" id="seo-keyword" type="text" placeholder='e.g. "best laptops 2024"' />
      </div>

      <div class="seo-section">
        <div class="seo-section-label">Keyword Location</div>
        <div class="seo-chip-row" id="seo-kw-location">
          <span class="seo-chip" data-val="intitle">intitle</span>
          <span class="seo-chip" data-val="inurl">inurl</span>
          <span class="seo-chip" data-val="intext">intext</span>
          <span class="seo-chip" data-val="inanchor">inanchor</span>
          <span class="seo-chip" data-val="allintitle">allintitle</span>
          <span class="seo-chip" data-val="allinurl">allinurl</span>
        </div>
      </div>

      <div class="seo-section">
        <div class="seo-section-label">Site / Domain</div>
        <input class="seo-input" id="seo-site" type="text" placeholder="e.g. reddit.com or .gov" />
      </div>

      <div class="seo-section">
        <div class="seo-section-label">Exclude Site</div>
        <input class="seo-input" id="seo-exclude-site" type="text" placeholder="e.g. pinterest.com" />
      </div>

      <div class="seo-section">
        <div class="seo-section-label">File Type</div>
        <div class="seo-chip-row" id="seo-filetype">
          <span class="seo-chip" data-val="pdf">PDF</span>
          <span class="seo-chip" data-val="doc">DOC</span>
          <span class="seo-chip" data-val="docx">DOCX</span>
          <span class="seo-chip" data-val="xls">XLS</span>
          <span class="seo-chip" data-val="xlsx">XLSX</span>
          <span class="seo-chip" data-val="ppt">PPT</span>
          <span class="seo-chip" data-val="csv">CSV</span>
          <span class="seo-chip" data-val="txt">TXT</span>
          <span class="seo-chip" data-val="xml">XML</span>
          <span class="seo-chip" data-val="json">JSON</span>
        </div>
        <input class="seo-input" id="seo-filetype-custom" type="text" placeholder="Or type custom filetype..." style="margin-top:6px" />
      </div>

      <div class="seo-section">
        <div class="seo-section-label">Date Range</div>
        <select class="seo-select" id="seo-date-preset">
          <option value="">— any time —</option>
          <option value="d">Past 24 hours</option>
          <option value="w">Past week</option>
          <option value="m">Past month</option>
          <option value="y">Past year</option>
          <option value="custom">Custom range...</option>
        </select>
        <div class="seo-date-row" id="seo-custom-date" style="display:none;margin-top:6px">
          <div class="seo-date-wrap">
            <small>From</small>
            <input class="seo-input" type="date" id="seo-date-from" />
          </div>
          <div class="seo-date-wrap">
            <small>To</small>
            <input class="seo-input" type="date" id="seo-date-to" />
          </div>
        </div>
      </div>

      <div class="seo-section">
        <div class="seo-section-label">Related / Cache</div>
        <div class="seo-chip-row" id="seo-special">
          <span class="seo-chip" data-val="related">related:</span>
          <span class="seo-chip" data-val="cache">cache:</span>
          <span class="seo-chip" data-val="info">info:</span>
        </div>
        <input class="seo-input" id="seo-special-val" type="text" placeholder="Enter URL or domain..." style="margin-top:6px;display:none" />
      </div>

      <div class="seo-section">
        <div class="seo-section-label">Exclude Words</div>
        <input class="seo-input" id="seo-exclude" type="text" placeholder='e.g. spam, ads, "click here"' />
      </div>

      <div class="seo-section">
        <div class="seo-section-label">Exact Phrase</div>
        <input class="seo-input" id="seo-exact" type="text" placeholder='e.g. content marketing tips' />
      </div>

      <div>
        <div class="seo-query-preview-label">Query Preview</div>
        <div class="seo-query-preview" id="seo-preview">—</div>
      </div>

    </div>
    <div class="seo-footer">
      <button class="seo-btn seo-btn-primary" id="seo-apply">⌕ Apply Filters</button>
      <button class="seo-btn seo-btn-secondary" id="seo-reset">✕ Reset All</button>
    </div>
  `;
  document.body.appendChild(sidebar);

  // ─── Toggle Button ───────────────────────────────────────────────────────────
  const toggle = document.createElement('button');
  toggle.id = 'seo-sidebar-toggle';
  toggle.innerHTML = `<span class="toggle-arrow">◀</span>SEO Filters`;
  document.body.appendChild(toggle);

  // ─── Toast ───────────────────────────────────────────────────────────────────
  const toast = document.createElement('div');
  toast.className = 'seo-toast';
  document.body.appendChild(toast);
  function showToast(msg) {
    toast.textContent = msg;
    toast.classList.add('show');
    setTimeout(() => toast.classList.remove('show'), 2200);
  }

  // ─── State ───────────────────────────────────────────────────────────────────
  let isOpen = false;
  let selectedFiletype = null;
  let selectedKwLocation = null;
  let selectedSpecial = null;

  // ─── Toggle open/close ───────────────────────────────────────────────────────
  toggle.addEventListener('click', () => {
    isOpen = !isOpen;
    sidebar.classList.toggle('open', isOpen);
    toggle.querySelector('.toggle-arrow').textContent = isOpen ? '▶' : '◀';
  });

  // ─── Chip logic ──────────────────────────────────────────────────────────────
  function setupChips(containerId, onSelect) {
    const container = document.getElementById(containerId);
    container.querySelectorAll('.seo-chip').forEach(chip => {
      chip.addEventListener('click', () => {
        const isActive = chip.classList.contains('active');
        container.querySelectorAll('.seo-chip').forEach(c => c.classList.remove('active'));
        if (!isActive) {
          chip.classList.add('active');
          onSelect(chip.dataset.val);
        } else {
          onSelect(null);
        }
        updatePreview();
      });
    });
  }

  setupChips('seo-filetype', v => { selectedFiletype = v; });
  setupChips('seo-kw-location', v => { selectedKwLocation = v; });
  setupChips('seo-special', v => {
    selectedSpecial = v;
    document.getElementById('seo-special-val').style.display = v ? 'block' : 'none';
  });

  // ─── Custom filetype overrides chip ─────────────────────────────────────────
  document.getElementById('seo-filetype-custom').addEventListener('input', () => {
    if (document.getElementById('seo-filetype-custom').value.trim()) {
      document.getElementById('seo-filetype').querySelectorAll('.seo-chip').forEach(c => c.classList.remove('active'));
      selectedFiletype = null;
    }
    updatePreview();
  });

  // ─── Date preset ─────────────────────────────────────────────────────────────
  document.getElementById('seo-date-preset').addEventListener('change', () => {
    const v = document.getElementById('seo-date-preset').value;
    document.getElementById('seo-custom-date').style.display = v === 'custom' ? 'grid' : 'none';
    updatePreview();
  });

  // ─── Live preview on all inputs ──────────────────────────────────────────────
  ['seo-keyword','seo-site','seo-exclude-site','seo-exclude','seo-exact','seo-special-val','seo-date-from','seo-date-to'].forEach(id => {
    const el = document.getElementById(id);
    if (el) el.addEventListener('input', updatePreview);
  });

  // ─── Build query string ──────────────────────────────────────────────────────
  function buildQuery() {
    const parts = [];
    const keyword = document.getElementById('seo-keyword').value.trim();
    const site = document.getElementById('seo-site').value.trim();
    const excludeSite = document.getElementById('seo-exclude-site').value.trim();
    const exact = document.getElementById('seo-exact').value.trim();
    const exclude = document.getElementById('seo-exclude').value.trim();
    const customFT = document.getElementById('seo-filetype-custom').value.trim();
    const specialVal = document.getElementById('seo-special-val').value.trim();
    const datePreset = document.getElementById('seo-date-preset').value;
    const dateFrom = document.getElementById('seo-date-from').value;
    const dateTo = document.getElementById('seo-date-to').value;

    if (exact) parts.push(`"${exact}"`);
    if (keyword) {
      if (selectedKwLocation) {
        parts.push(`${selectedKwLocation}:${keyword}`);
      } else {
        parts.push(keyword);
      }
    }
    if (site) parts.push(`site:${site}`);
    if (excludeSite) parts.push(`-site:${excludeSite}`);

    const ft = customFT || selectedFiletype;
    if (ft) parts.push(`filetype:${ft}`);

    if (exclude) {
      exclude.split(',').map(w => w.trim()).filter(Boolean).forEach(w => parts.push(`-${w}`));
    }
    if (selectedSpecial && specialVal) {
      parts.push(`${selectedSpecial}:${specialVal}`);
    }

    return { query: parts.join(' '), datePreset, dateFrom, dateTo };
  }

  function updatePreview() {
    const { query } = buildQuery();
    document.getElementById('seo-preview').textContent = query || '—';
  }

  // ─── Apply Filters ───────────────────────────────────────────────────────────
  document.getElementById('seo-apply').addEventListener('click', () => {
    const { query, datePreset, dateFrom, dateTo } = buildQuery();
    if (!query && !datePreset) { showToast('Add at least one filter first.'); return; }

    const params = new URLSearchParams(window.location.search);
    params.set('q', query || params.get('q') || '');

    // Date filter via tbs param
    if (datePreset && datePreset !== 'custom') {
      params.set('tbs', `qdr:${datePreset}`);
    } else if (datePreset === 'custom' && dateFrom && dateTo) {
      const fmt = d => d.replace(/-/g, '/');
      params.set('tbs', `cdr:1,cd_min:${fmt(dateFrom)},cd_max:${fmt(dateTo)}`);
    } else {
      params.delete('tbs');
    }

    params.delete('start'); // reset pagination
    window.location.href = `https://www.google.com/search?${params.toString()}`;
  });

  // ─── Reset ───────────────────────────────────────────────────────────────────
  document.getElementById('seo-reset').addEventListener('click', () => {
    ['seo-keyword','seo-site','seo-exclude-site','seo-exclude','seo-exact','seo-filetype-custom','seo-special-val'].forEach(id => {
      document.getElementById(id).value = '';
    });
    document.getElementById('seo-date-preset').value = '';
    document.getElementById('seo-date-from').value = '';
    document.getElementById('seo-date-to').value = '';
    document.getElementById('seo-custom-date').style.display = 'none';
    document.getElementById('seo-special-val').style.display = 'none';
    document.querySelectorAll('.seo-chip.active').forEach(c => c.classList.remove('active'));
    selectedFiletype = null; selectedKwLocation = null; selectedSpecial = null;
    updatePreview();
    showToast('Filters cleared.');
  });

  // ─── Auto-populate from current URL ──────────────────────────────────────────
  const currentQ = new URLSearchParams(window.location.search).get('q') || '';
  if (currentQ) {
    document.getElementById('seo-keyword').value = currentQ;
    updatePreview();
  }

})();