URL Sniffer

Sniff URLs in HTML

Per 20-06-2022. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name              URL Sniffer
// @name:zh-CN        URL 嗅探器
// @namespace         https://gera2ld.space/
// @description       Sniff URLs in HTML
// @description:zh-CN 从 HTML 中嗅探 URL
// @match             *://*/*
// @version           0.2.0
// @author            Gerald <[email protected]>
// @require           https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@2,npm/@violentmonkey/[email protected]
// @supportURL        https://github.com/intellilab/url-sniffer.user.js
// @grant             GM_addStyle
// @grant             GM_registerMenuCommand
// @grant             GM_setClipboard
// @grant             GM_unregisterMenuCommand
// ==/UserScript==

(function () {
'use strict';

function _extends() {
  _extends = Object.assign ? Object.assign.bind() : function (target) {
    for (var i = 1; i < arguments.length; i++) {
      var source = arguments[i];

      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          target[key] = source[key];
        }
      }
    }

    return target;
  };
  return _extends.apply(this, arguments);
}

function _objectWithoutPropertiesLoose(source, excluded) {
  if (source == null) return {};
  var target = {};
  var sourceKeys = Object.keys(source);
  var key, i;

  for (i = 0; i < sourceKeys.length; i++) {
    key = sourceKeys[i];
    if (excluded.indexOf(key) >= 0) continue;
    target[key] = source[key];
  }

  return target;
}

var styles = {"root":"style-module_root__1vyw2","toast":"style-module_toast__OcS5G","image":"style-module_image__1P0bD"};
var stylesheet=".style-module_root__1vyw2{background:#0008;inset:0;position:fixed;z-index:10000}.style-module_root__1vyw2:before{background:#0008;color:#bbb;content:\"Double click anywhere on the mask to exit\";font-size:12px;left:50%;padding:8px 16px;position:absolute;top:0;transform:translateX(-50%)}.style-module_root__1vyw2>*{border:2px solid;left:0;position:absolute;top:0}.style-module_toast__OcS5G{z-index:10001!important}.style-module_image__1P0bD{background:#0008;inset:80px;overflow:auto;position:absolute}.style-module_image__1P0bD>img{position:absolute;transform-origin:top left}";

const _excluded = ["elements", "getItem"];
const STYLE_CURRENT = {
  stroke: '#0f08',
  fill: '#0f02'
};
const STYLE_SELECTION = {
  stroke: '#bbf8',
  fill: '#bbf2'
};
const STYLE_SELECTED = {
  stroke: '#ff08',
  fill: '#ff02'
};
const STYLE_TO_DESELECT = {
  stroke: '#88d8',
  fill: '#88d2'
};
const STYLE_TO_SELECT = {
  stroke: '#bb08',
  fill: '#bb02'
};
const MODE_SINGLE = 0;
const MODE_MULTIPLE = 1;
let rendering = false;
const mask = VM.getHostElement(false);
mask.addStyle(stylesheet);
mask.root.className = styles.root;
mask.root.addEventListener('mousedown', handleMouseDown);
mask.root.addEventListener('mouseup', handleMouseUp);
mask.root.addEventListener('mousemove', handleMouseMove);
mask.root.addEventListener('click', handleClick);
mask.root.addEventListener('dblclick', handleCallback);
GM_registerMenuCommand('Sniff links', sniffLinks);
GM_registerMenuCommand('Sniff images', sniffImages);
let context;

function sniffLinks() {
  if (context) close();
  start({
    elements: document.querySelectorAll('a[href]'),

    getItem(el) {
      const href = el.tagName.toLowerCase() === 'a' && el.getAttribute('href');
      if (href && !/^(?:#|javascript:)/.test(href)) return {
        el
      };
    },

    mode: MODE_MULTIPLE,

    callback(selectedItems) {
      copy(selectedItems);
      close();
    }

  });
}

function sniffImages() {
  if (context) close();
  const imageViewer = VM.hm("div", {
    className: styles.image,
    onClick: e => e.stopPropagation()
  });

  const showImage = img => {
    mask.root.append(imageViewer);
    const {
      naturalWidth,
      naturalHeight
    } = img;
    const containerWidth = imageViewer.clientWidth;
    const containerHeight = imageViewer.clientHeight;
    const scale = Math.min(1, containerWidth / naturalWidth);
    const width = naturalWidth * scale;
    const height = naturalHeight * scale;
    const x = Math.max(0, (containerWidth - width) / 2);
    const y = Math.max(0, (containerHeight - height) / 2);
    imageViewer.innerHTML = '';
    imageViewer.append(img);
    img.style.transform = `scale(${scale}) translate(${x}px,${y}px)`;
    context.paused = true;
    mask.root.addEventListener('click', closeViewer);
  };

  const closeViewer = () => {
    imageViewer.innerHTML = '';
    imageViewer.remove();
    context.paused = false;
    mask.root.removeEventListener('click', closeViewer);
  };

  start({
    getItem(el) {
      let url;

      if (el.tagName.toLowerCase() === 'img') {
        url = el.src;
      } else {
        const bgImg = el.style.backgroundImage.match(/^url\((['"]?)(.*?)\1\)/);
        url = bgImg == null ? void 0 : bgImg[2];
      }

      return url && {
        el,
        url
      };
    },

    mode: MODE_SINGLE,

    callback([item]) {
      if (!item) return close();
      const img = new Image();
      img.src = item.url;

      img.onload = () => {
        showImage(img);
      };
    }

  });
}

function start(opts) {
  if (context) throw new Error('Context already exists');

  const {
    elements,
    getItem
  } = opts,
        rest = _objectWithoutPropertiesLoose(opts, _excluded);

  const items = Array.from(elements || document.querySelectorAll('*')).map(getItem).filter(Boolean);
  context = _extends({}, rest, {
    items,
    index: -1,
    active: null,
    disconnect: VM.observe(document.body, mutations => {
      mutations.forEach(mut => {
        if (mut.type === 'childList') {
          const newItems = Array.from(mut.addedNodes).filter(el => !mask.root.contains(el)).map(getItem).filter(Boolean);
          context.items.push(...newItems);
        }
      });
    })
  });
  update();
  mask.show();
  document.addEventListener('scroll', update);
  document.addEventListener('resize', update);
}

function close() {
  if (!context) return;
  context.disconnect == null ? void 0 : context.disconnect();
  mask.root.innerHTML = '';
  mask.hide();
  context = null;
  document.removeEventListener('scroll', update);
  document.removeEventListener('resize', update);
  GM_unregisterMenuCommand('Copy URLs');
}

function update() {
  if (rendering) return;
  rendering = true;
  requestAnimationFrame(() => {
    context.items.forEach(item => {
      const rect = item.el.getBoundingClientRect();
      item.pos = {
        x: rect.left,
        y: rect.top,
        w: rect.width,
        h: rect.height
      };
    });
    render();
    rendering = false;
  });
}

function render() {
  renderActive();
  renderSelected();
}

function updateStyle(el, style) {
  el.style.borderColor = style.stroke;
  el.style.background = style.fill;
}

function updatePosition(el, pos, padding = 2) {
  Object.assign(el.style, {
    width: `${pos.w + padding * 2}px`,
    height: `${pos.h + padding * 2}px`,
    transform: `translate(${pos.x - padding}px,${pos.y - padding}px)`
  });
}

function renderActive() {
  const activeItem = !context.dragging && context.items[context.index];

  if (!activeItem) {
    if (context.active) {
      context.active.remove();
      context.active = null;
    }
  } else {
    if (!context.active) {
      context.active = VM.hm(mask.id, null);
      updateStyle(context.active, STYLE_CURRENT);
      mask.root.append(context.active);
    }

    updatePosition(context.active, activeItem.pos);
  }
}

function renderSelected() {
  context.items.forEach(item => {
    if (item.rect) updatePosition(item.rect, item.pos);
  });
}

function setItemRect(item, style) {
  if (style) {
    if (!item.rect) {
      item.rect = VM.hm(mask.id, null);
      mask.root.append(item.rect);
    }

    updateStyle(item.rect, style);
    updatePosition(item.rect, item.pos);
  } else if (item.rect) {
    item.rect.remove();
    item.rect = null;
  }
}

function handleClick() {
  if (context.paused) return;
  const activeItem = context.items[context.index];

  if (activeItem) {
    if (context.mode === MODE_SINGLE) {
      context.callback([activeItem]);
    } else {
      activeItem.selected = !activeItem.selected;
      setItemRect(activeItem, activeItem.selected && STYLE_SELECTED);
    }
  }
}

function handleMouseDown(e) {
  if (context.dragging || context.mode === MODE_SINGLE || context.paused) return;
  const x = e.clientX;
  const y = e.clientY;
  context.dragging = {
    x,
    y
  };
}

function handleMouseMove(e) {
  if (context.paused) return;
  const x = e.clientX;
  const y = e.clientY;

  if (context.dragging) {
    if (!context.dragging.rect) {
      const rect = VM.hm(mask.id, null);
      updateStyle(rect, STYLE_SELECTION);
      mask.root.append(rect);
      context.dragging.rect = rect;
    }

    context.index = -1;
    let x0 = context.dragging.x;
    let y0 = context.dragging.y;
    const w = Math.abs(x - x0);
    const h = Math.abs(y - y0);
    x0 = Math.min(x0, x);
    y0 = Math.min(y0, y);
    updatePosition(context.dragging.rect, {
      x: x0,
      y: y0,
      w,
      h
    }, 0);
    context.items.forEach(item => {
      item.inSelection = item.pos.x >= x0 && item.pos.x + item.pos.w <= x0 + w && item.pos.y >= y0 && item.pos.y + item.pos.h <= y0 + h;
      const state = (item.inSelection ? 2 : 0) + (item.selected ? 1 : 0);
      setItemRect(item, {
        1: STYLE_SELECTED,
        2: STYLE_TO_SELECT,
        3: STYLE_TO_DESELECT
      }[state]);
    });
  } else {
    context.index = context.items.findIndex(({
      pos
    }) => x >= pos.x && x <= pos.x + pos.w && y >= pos.y && y <= pos.y + pos.h);
  }

  render();
}

function handleMouseUp() {
  if (!context.dragging) return;

  if (context.dragging.rect) {
    context.dragging.rect.remove();
    context.items.forEach(item => {
      if (item.inSelection) {
        item.inSelection = false;
        item.selected = !item.selected;
        setItemRect(item, item.selected && STYLE_SELECTED);
      }
    });
  }

  context.dragging = null;
}

function handleCallback() {
  const selectedItems = context.items.filter(item => item.selected);
  context.callback(selectedItems);
}

function copy(selectedItems) {
  const urls = selectedItems.map(item => item.el.href);
  if (!urls.length) return;
  GM_setClipboard(urls.join('\r\n'));
  VM.showToast('URLs copied', {
    shadow: false,
    className: styles.toast
  });
}

})();