URL Sniffer

Sniff URLs in HTML

Versão de: 20/06/2022. Veja: a última versão.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey to install this script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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
  });
}

})();