您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Detect legal acts/rules, dedupe mentions, find PDFs (or fallback to Google). Smooth slide-out sidebar, draggable (vertical), batch open PDFs, keyboard toggle (Alt+Shift+L). Theme-aware.
// ==UserScript== // @name Legal Acts Finder — Polished Slide Sidebar // @namespace http://tampermonkey.net/ // @version 1.7 // @description Detect legal acts/rules, dedupe mentions, find PDFs (or fallback to Google). Smooth slide-out sidebar, draggable (vertical), batch open PDFs, keyboard toggle (Alt+Shift+L). Theme-aware. // @author iamnobody // @license MIT // @match *://*/* // @grant GM_xmlhttpRequest // @icon https://greasyfork.org/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTg5MTc1LCJwdXIiOiJibG9iX2lkIn19--c218824699773e9e6d58fe11cc76cdbb165a2e65/1000031087.jpg?locale=en // @banner https://greasyfork.org/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsiZGF0YSI6MTg5MTczLCJwdXIiOiJibG9iX2lkIn19--77a89502797ffc05cd152a04c877a3b3de4c24be/1000031086.jpg?locale=en // ==/UserScript== (function () { 'use strict'; /* ---------- Config ---------- */ const MIN_WIDTH_PX = 250; const MAX_WIDTH_PCT = 30; // max width as % of viewport width const TRANS_MS = 320; const FETCH_CONCURRENCY = 4; const OPEN_DELAY_MS = 350; // delay between opening tabs const TOGGLE_SHORTCUT = { altKey: true, shiftKey: true, key: 'L' }; // Alt+Shift+L /* ---------- Regexes ---------- */ const actRegex = /(\b[A-Z]?[a-zA-Z&\-\s]{2,}?\s+act\s+of\s+\d{4}\b)|(\b[A-Z]?[a-zA-Z&\-\s]{2,}?\s+act,\s+\d{4}\b)|(\b[A-Z]?[a-zA-Z&\-\s]{2,}?\s+act\s+of\s+year\s+\d{4}\b)/gi; const ruleRegex = /\bsection\s+\w+\s+of\s+\w+\s+act,\s+\d{4}\b/gi; /* ---------- Helpers ---------- */ const clamp = (v, a, b) => Math.max(a, Math.min(b, v)); const escapeHtml = s => String(s).replace(/[&<>"']/g, m => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); /* ---------- Extract & Dedupe ---------- */ function extractUniqueMatches() { const text = (document.body.innerText || document.body.textContent || '').replace(/\s+/g, ' '); const acts = Array.from(text.matchAll(actRegex)).map(m => (m[0] || '').trim()); const rules = Array.from(text.matchAll(ruleRegex)).map(m => (m[0] || '').trim()); const merged = acts.concat(rules); const seen = new Map(); for (const raw of merged) { const key = raw.toLowerCase(); if (!seen.has(key)) seen.set(key, raw); } return Array.from(seen.values()); } /* ---------- Fetch queue (limited concurrency) ---------- */ function createQueue(concurrency = FETCH_CONCURRENCY) { const q = []; let running = 0; const runNext = () => { if (running >= concurrency || q.length === 0) return; const { query, resolve } = q.shift(); running++; fetchPdfForQuery(query).then(res => { running--; resolve(res); runNext(); }).catch(() => { running--; resolve({ url: `https://www.google.com/search?q=${encodeURIComponent(query + ' pdf')}`, type: 'search' }); runNext(); }); }; return query => new Promise(res => { q.push({ query, resolve: res }); runNext(); }); } async function fetchPdfForQuery(query) { return new Promise(resolve => { const googleUrl = `https://www.google.com/search?q=${encodeURIComponent(query + ' pdf')}`; try { GM_xmlhttpRequest({ method: 'GET', url: googleUrl, headers: { 'User-Agent': navigator.userAgent }, onload(response) { const html = response && response.responseText ? response.responseText : ''; const pdfMatch = html.match(/https?:\/\/[^"'>\s]+?\.pdf\b/gi); if (pdfMatch && pdfMatch.length) { const pdfUrl = pdfMatch[0].replace(/\\u0026/g, '&'); resolve({ url: pdfUrl, type: 'pdf' }); } else { resolve({ url: googleUrl, type: 'search' }); } }, onerror() { resolve({ url: googleUrl, type: 'search' }); }, timeout: 15000 }); } catch (err) { resolve({ url: googleUrl, type: 'search' }); } }); } const queuedFetch = createQueue(); /* ---------- Build UI ---------- */ function createSidebar(matches) { // container const container = document.createElement('div'); container.id = 'la-container'; container.style.position = 'fixed'; container.style.top = '50%'; container.style.right = '0'; container.style.transform = 'translateY(-50%)'; container.style.zIndex = '2147483647'; document.body.appendChild(container); // style const css = document.createElement('style'); css.textContent = ` :root{--la-bg:#ffffff;--la-fg:#0b1220;--la-accent:#ff8a00;--la-muted:rgba(11,18,32,0.6);--la-shadow:rgba(12,16,20,0.12)} @media(prefers-color-scheme:dark){:root{--la-bg:#07101a;--la-fg:#e6eef8;--la-accent:#ffb86b;--la-muted:rgba(230,238,248,0.7);--la-shadow:rgba(0,0,0,0.6)}} #la-sidebar{position:fixed;right:0;top:50%;transform:translate(100%,-50%);transition:transform ${TRANS_MS}ms cubic-bezier(.2,.9,.2,1),opacity ${TRANS_MS}ms;opacity:0;width:min(${MAX_WIDTH_PCT}vw,100%);max-width:calc(${MAX_WIDTH_PCT}vw);min-width:${MIN_WIDTH_PX}px;border-radius:12px 0 0 12px;box-shadow:0 12px 30px var(--la-shadow);background:linear-gradient(180deg,var(--la-bg),var(--la-bg));color:var(--la-fg);backdrop-filter:blur(6px);display:flex;flex-direction:column;max-height:80vh;overflow:hidden} #la-sidebar.open{transform:translate(0,-50%);opacity:1} #la-header{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-bottom:1px solid rgba(0,0,0,0.06);cursor:grab} #la-title{display:flex;gap:8px;align-items:center;font-weight:600;font-size:15px} .la-dot{width:10px;height:10px;border-radius:50%;background:var(--la-accent);display:inline-block} #la-controls{display:flex;gap:8px;align-items:center} #la-openall{background:transparent;border:1px solid rgba(0,0,0,0.08);padding:6px 10px;border-radius:8px;color:var(--la-fg);cursor:pointer;transition:transform 120ms} #la-openall[disabled]{opacity:0.45;cursor:not-allowed} #la-close{background:transparent;border:0;font-size:18px;cursor:pointer;padding:6px 8px;border-radius:6px;color:var(--la-fg)} #la-list{padding:10px;overflow-y:auto;flex:1 1 auto} .la-item{padding:8px 10px;border-radius:8px;margin-bottom:8px;transition:background 160ms} .la-item:hover{background:rgba(0,0,0,0.03)} .la-item a{color:var(--la-accent);text-decoration:none;font-weight:600;display:block} .la-meta{font-size:12px;color:var(--la-muted);margin-top:6px} #la-footer{padding:8px 12px;border-top:1px solid rgba(0,0,0,0.05);font-size:12px;color:var(--la-muted);display:flex;justify-content:space-between;align-items:center} #la-tab{position:absolute;right:0;top:50%;transform:translateY(-50%);width:36px;height:76px;border-radius:8px 0 0 8px;background:var(--la-accent);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;cursor:pointer;box-shadow:0 8px 20px var(--la-shadow)} #la-tab:hover{filter:brightness(1.03)} #la-accordion{padding:10px;border-top:1px solid rgba(0,0,0,0.04)} #la-accordion-toggle{display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none} #la-accordion-arrow{transition:transform ${TRANS_MS}ms} #la-accordion-content{overflow:hidden;max-height:0;transition:max-height ${TRANS_MS}ms cubic-bezier(.2,.9,.2,1);padding-top:8px} .la-pdf-item{padding:8px;border-radius:8px;margin-bottom:8px;background:transparent} @media(max-width:520px){#la-sidebar{min-width:220px;max-height:70vh}#la-tab{height:64px;width:34px}}`; document.head.appendChild(css); // sidebar const panel = document.createElement('aside'); panel.id = 'la-sidebar'; container.appendChild(panel); // header const header = document.createElement('div'); header.id = 'la-header'; panel.appendChild(header); const title = document.createElement('div'); title.id = 'la-title'; title.innerHTML = `<span class="la-dot" aria-hidden="true"></span><span>Acts & Rules</span>`; header.appendChild(title); const controls = document.createElement('div'); controls.id = 'la-controls'; header.appendChild(controls); const openAllBtn = document.createElement('button'); openAllBtn.id = 'la-openall'; openAllBtn.textContent = 'Open All PDFs (0)'; openAllBtn.disabled = true; controls.appendChild(openAllBtn); const closeBtn = document.createElement('button'); closeBtn.id = 'la-close'; closeBtn.title = 'Close'; closeBtn.textContent = '✕'; controls.appendChild(closeBtn); // list area (all matches) const listWrap = document.createElement('div'); listWrap.id = 'la-list'; panel.appendChild(listWrap); // accordion for individual PDFs const accordionWrap = document.createElement('div'); accordionWrap.id = 'la-accordion'; panel.appendChild(accordionWrap); const accToggle = document.createElement('div'); accToggle.id = 'la-accordion-toggle'; accToggle.innerHTML = `<span id="la-accordion-arrow">►</span><span>View individual PDFs</span>`; accordionWrap.appendChild(accToggle); const accContent = document.createElement('div'); accContent.id = 'la-accordion-content'; accordionWrap.appendChild(accContent); // footer const footer = document.createElement('div'); footer.id = 'la-footer'; footer.innerHTML = `<div style="opacity:0.85">Tip: Press Alt+Shift+L to toggle</div><div style="opacity:0.6;font-size:11px">Polite fetch queue</div>`; panel.appendChild(footer); // collapsed tab const tab = document.createElement('div'); tab.id = 'la-tab'; tab.textContent = '‹'; container.appendChild(tab); /* ---------- Populate & fetch ---------- */ const pdfEntries = []; // { query, url } const allItems = []; // DOM mapping function showNoMatches() { listWrap.innerHTML = ''; const none = document.createElement('div'); none.className = 'la-item'; none.innerHTML = `<div class="title">No legal acts or rules detected on this page.</div><div class="la-meta">Try a web search for relevant PDFs: <a href="${`https://www.google.com/search?q=${encodeURIComponent((document.title||location.hostname)+' law act pdf')}`}" target="_blank" rel="noopener noreferrer">Search</a></div>`; listWrap.appendChild(none); } async function processMatches(matches) { listWrap.innerHTML = ''; if (!matches || matches.length === 0) { showNoMatches(); return; } // placeholder items (loading) for (const m of matches) { const item = document.createElement('div'); item.className = 'la-item'; item.innerHTML = `<div class="title">${escapeHtml(m)}</div><div class="la-meta">Looking up PDF…</div>`; listWrap.appendChild(item); allItems.push({ query: m, el: item }); } // fetch each via queue for (const it of allItems) { try { const res = await queuedFetch(it.query); it.el.innerHTML = ''; const a = document.createElement('a'); a.href = res.url; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.textContent = it.query; it.el.appendChild(a); const meta = document.createElement('div'); meta.className = 'la-meta'; if (res.type === 'pdf') { meta.textContent = 'Direct PDF found — opens in new tab'; // save pdf entry for batch and accordion pdfEntries.push({ query: it.query, url: res.url }); } else { meta.innerHTML = `No direct PDF found — opens Google search. Click to refine search.`; } it.el.appendChild(meta); } catch (err) { it.el.innerHTML = `<div class="title">${escapeHtml(it.query)}</div><div class="la-meta">Lookup failed — <a href="${`https://www.google.com/search?q=${encodeURIComponent(it.query+' pdf')}`}" target="_blank" rel="noopener noreferrer">Search</a></div>`; } updatePdfControls(); } buildAccordion(); } function updatePdfControls() { const n = pdfEntries.length; openAllBtn.textContent = `Open All PDFs (${n})`; openAllBtn.disabled = n === 0; } function buildAccordion() { accContent.innerHTML = ''; if (pdfEntries.length === 0) { const hint = document.createElement('div'); hint.className = 'la-meta'; hint.style.padding = '6px 0'; hint.textContent = 'No direct PDFs found. Use the Act links above to search.'; accContent.appendChild(hint); return; } for (const p of pdfEntries) { const row = document.createElement('div'); row.className = 'la-pdf-item'; const a = document.createElement('a'); a.href = p.url; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.textContent = p.query; row.appendChild(a); accContent.appendChild(row); } } /* ---------- Toggle, Drag, Accordion, Keyboard ---------- */ let isOpen = false; let dragging = false; let dragStartY = 0; let currentCenterY = null; // px function computeWidth() { const vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0); const px = Math.max(MIN_WIDTH_PX, Math.floor((MAX_WIDTH_PCT / 100) * vw)); panel.style.width = Math.min(px + 'px', '100%'); } computeWidth(); window.addEventListener('resize', computeWidth); function openPanel() { isOpen = true; panel.classList.add('open'); tab.style.display = 'none'; // position panel; if we have a custom center use it, else center if (currentCenterY !== null) { container.style.top = currentCenterY + 'px'; container.style.transform = 'none'; panel.style.transform = 'translate(0, -50%)'; } else { container.style.top = '50%'; container.style.transform = 'translateY(-50%)'; panel.style.transform = 'translate(0, -50%)'; } } function closePanel() { isOpen = false; panel.classList.remove('open'); tab.style.display = 'flex'; currentCenterY = null; container.style.top = '50%'; container.style.transform = 'translateY(-50%)'; panel.style.transform = 'translate(100%,-50%)'; } tab.addEventListener('click', e => { e.stopPropagation(); openPanel(); }); closeBtn.addEventListener('click', e => { e.stopPropagation(); closePanel(); }); // drag only when open header.addEventListener('mousedown', e => { if (!isOpen) return; dragging = true; dragStartY = e.clientY; const rect = container.getBoundingClientRect(); currentCenterY = rect.top + rect.height / 2; container.style.transform = 'none'; container.style.top = currentCenterY + 'px'; header.style.cursor = 'grabbing'; document.body.style.userSelect = 'none'; }); window.addEventListener('mousemove', e => { if (!dragging) return; const dy = e.clientY - dragStartY; const newCenter = currentCenterY + dy; const panelRect = panel.getBoundingClientRect(); const half = panelRect.height / 2; const min = half + 8; const max = window.innerHeight - half - 8; const clamped = clamp(newCenter, min, max); currentCenterY = clamped; container.style.top = clamped + 'px'; dragStartY = e.clientY; }); window.addEventListener('mouseup', () => { if (!dragging) return; dragging = false; header.style.cursor = 'grab'; document.body.style.userSelect = ''; }); // accordion toggle let accOpen = false; const accArrow = document.getElementById ? null : null; // placeholder to avoid lint warnings accToggle.addEventListener('click', () => { accOpen = !accOpen; const arrow = accToggle.querySelector('#la-accordion-arrow'); if (accOpen) { arrow.style.transform = 'rotate(90deg)'; accContent.style.maxHeight = Math.min(accContent.scrollHeight + 40, window.innerHeight * 0.4) + 'px'; } else { arrow.style.transform = 'rotate(0deg)'; accContent.style.maxHeight = '0'; } }); // keyboard toggle Alt+Shift+L (ignore when typing in inputs/textareas or contentEditable) window.addEventListener('keydown', e => { if (!(e.altKey && e.shiftKey && (e.key.toUpperCase() === TOGGLE_SHORTCUT.key))) return; const active = document.activeElement; const editable = active && (active.tagName === 'INPUT' || active.tagName === 'TEXTAREA' || active.isContentEditable); if (editable) return; e.preventDefault(); if (isOpen) closePanel(); else openPanel(); }); /* ---------- Batch open and per-PDF open ---------- */ openAllBtn.addEventListener('click', async () => { const n = pdfEntries.length; if (!n) return; const confirmMsg = `Open ${n} PDF(s) in new tabs? (This will open ${n} tabs)`; if (!confirm(confirmMsg)) return; for (let i = 0; i < pdfEntries.length; i++) { const url = pdfEntries[i].url; window.open(url, '_blank', 'noopener'); // delay to reduce popup-blocker risk await new Promise(res => setTimeout(res, OPEN_DELAY_MS)); } }); // per-PDF items already link to PDFs in accContent; no extra handlers needed. return { panel, container, tab, processMatches, openPanel, closePanel, updatePdfControls }; } /* ---------- Init ---------- */ function init() { try { const matches = extractUniqueMatches(); const sidebar = createSidebar(matches); sidebar.processMatches(matches); // nothing else; user interacts } catch (err) { console.error('Legal Acts Finder error:', err); } } if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(init, 400); } else { window.addEventListener('load', () => setTimeout(init, 300)); } // Instead of firing all requests at once, use a queue with delays function processActsWithDelay(acts, delayMs = 5000) { // default 5 sec delay let i = 0; function next() { if (i >= acts.length) return; let act = acts[i]; // Call your function that handles one act at a time fetchActReference(act, function () { // Once finished, move to next with delay i++; setTimeout(next, delayMs); }); } next(); } // Example fetch function wrapper function fetchActReference(act, callback) { let query = encodeURIComponent(`${act} site:indiacode.nic.in`); let url = `https://www.google.com/search?q=${query}`; GM_xmlhttpRequest({ method: "GET", url: url, onload: function (response) { // parse response, add links to sidebar... console.log("Processed:", act); if (typeof callback === "function") callback(); }, onerror: function () { console.warn("Failed:", act); if (typeof callback === "function") callback(); } }); } (function() { 'use strict'; // Function to create a random delay (300–800 ms) function randomDelay() { return new Promise(resolve => { const delay = Math.floor(Math.random() * 500) + 300; // 300–800 ms setTimeout(resolve, delay); }); } async function runWithDelay() { console.log("Script started..."); // Example action 1: click a button await randomDelay(); let button = document.querySelector("button"); if (button) { button.click(); console.log("Clicked button after delay"); } // Example action 2: fill input await randomDelay(); let input = document.querySelector("input[type='text']"); if (input) { input.value = "Hello World"; console.log("Filled input after delay"); } // Example action 3: press Enter await randomDelay(); let evt = new KeyboardEvent("keydown", { key: "Enter", bubbles: true }); input?.dispatchEvent(evt); console.log("Pressed Enter after delay"); } runWithDelay(); })(); })();