Black Belt

Finds useful links and displays a top bar with various of links from contact details to media documents, including Metalinks, Podcasts, Syndication Feeds (Atom, JSON & RSS), Torrents and Userscripts. Also supports Chat, Email, Geoposition, IPFS, Magnet links of eXact Topic (xt), VoIP, Wallet schemes and more.

目前為 2023-05-17 提交的版本,檢視 最新版本

// ==UserScript== 
// @name        Black Belt 
// @author      Schimon Jehudah, Adv.
// @namespace   i2p.schimon.blackbelt
// @homepageURL https://openuserjs.org/scripts/sjehuda/Black_Belt
// @supportURL  https://openuserjs.org/scripts/sjehuda/Black_Belt/issues
// @copyright   2023, Schimon Jehudah (http://schimon.i2p)
// @license     MIT; https://opensource.org/licenses/MIT
// @description Finds useful links and displays a top bar with various of links from contact details to media documents, including Metalinks, Podcasts, Syndication Feeds (Atom, JSON & RSS), Torrents and Userscripts.  Also supports Chat, Email, Geoposition, IPFS, Magnet links of eXact Topic (xt), VoIP, Wallet schemes and more.
// @include     *
// @version     23.05.18
// @run-at      document-end
// @noframes
// @icon        data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48dGV4dCB5PSIuOWVtIiBmb250LXNpemU9IjkwIj7wn6WLPC90ZXh0Pjwvc3ZnPg==
// ==/UserScript==

// NOTE
// Robe icons (Sauna pack) created by Freepik
// https://www.flaticon.com/free-icon/robe_2520932
// https://www.flaticon.com/authors/freepik
// https://www.freepik.com/

// TODO
//
// 0) Bar like https://www.croxyproxy.com/
//
// 0) Decode string for magnet links
//    https://btdig.com/9fe6281eaf39f8bee656f27cacf48713c0608c3e/20-birds-&-animals-books-collection-pack-4
//
// 0) Tooltip
//    https://www.w3schools.com/howto/howto_css_tooltip.asp
//    or DIV on the middle or center of screen
//    https://web.archive.org/web/20050423235409/http://karmatics.com/aardvark/
//    https://css-tricks.com/quick-css-trick-how-to-center-an-object-exactly-in-the-center/
//
// 1) Brand: Access Bar, Alt Bar, Black Bar, Black Robe, Distribar, Distributed Bar, Distribution Bar, Easy Access Bar, Free Bar, Freenet Bar, Handler Bar, Harvest Bar, IETF Bar, IETF Black Bar, IETF ToolBar, Instant Media Bar, Media Bar, Power Bar, Power Download Bar, Reaping Bar, Simple Access Bar, Simple Bar, Super Bar
//
// 2) Recognize btih of 32 and convert it to 40
//
// 3) Check cache links for none 200 code
//    https://bookshelf.theanarchistlibrary.org/library/librarian-previous-announcements-en
//
// 4) FIXME feedx
//    http://freebase.be/db/software.rss.xml
//
// 5) Case insensitive (XPath)
//    String.prototype.toLowerCase()
//    See 'Magnet:' heperlink at https://ddosecrets.com/wiki/Rosatom
//    See getPathTo()
//
// 6) Fetch button (guess on demand) instead of auto-guess
//    Find file by Hash or ID (Hint: find duplicate chars/strings)
//
// 7) Display software for IPFS, GPS, Monero, RSS, SIP, Tribler, XMPP
//
// 8) Market diaspora*, Linux, Mastodon, ownCloud, RetroShare
//
// 9) TODO cancel *:after "open in new tab" https://cookidoo.thermomix.com/foundation/en-US

const types = [ 
  'alt,📰,feed_a,Follow,Subscribe to News Feed', //索 //參
  'ext,📱,app_android,Android', //App Installer (Android)
  'ext,🗝️,asc,ASC Key,Additional Sense Code',
  'ext,🔵️,chromium,Chromium',
  'ext,🐧️,debian,Debian',
  'ext,📰,feed_x,Follow,Subscribe to News Feed',
  'ext,🐧️,flatpak,Linux',
  'ext,📍️,geo_x,Location',
  'ext,🐯️,mac,OSX',
  'ext,♾️,metalink,Metalink', //∞
  'ext,📦️,torrent,Torrent',
  'ext,📜,userjs,Userscript', //🐒
  'ext,📥️,reactos,React OS',
  'ext,🐺️,xpi,LibreWolf', //🦊️
  'uri,🫐️,adc,DC', //⚛️
  'uri,🛍️,appstream,AppStream', //👜️
  // if (type[5]) { ele.style.transform = 'rotate(90deg)' }
  'uri,🔽,cabal,Cabal', //︾ //🔽 //⧩ //➤
  'uri,👁️‍🗨️️,chateye,Chat,WARNING: This chat service logs your conversations to its records.  Use XMPP instead',
  'uri,♈,ed2k,eDonkey',
  'uri,✉️,email,Email,Send an Email Message',
  'uri,📰,feed,Follow,Subscribe to News Feed',
  'uri,📍️,geo,Location',
  'uri,💠,ipfs,IPFS', //📁 //📂 //🗃️ //⚛️ //💎️ //🕸️
  'uri,🗨️,irc,IRC',
  'uri,🎙️,itpc,Podcast',
  // (Supports VoIP and OMEMO & OpenPGP encryption methods)
  'uri,💡️,jid,Jabber,Chat securely over the XMPP network.',
  //'uri,🧲,magnet,Magnet',
  'uri,#️,matrix,Matrix,We recommend using XMPP instead, for better privacy.', //#️⃣️ //# //⌗ //濫
  'uri,🐧️,openstore,Ubuntu Touch',
  'uri,☎️,telephone,Call',
  'uri,📶,udp,Tracker',
  'uri,📞️,voip,VoIP',
  'uri,🪙️,wallet,Wallet',
  'url,㊙️,i2p,i2p', //㊣
  'url,🧅️,onion,Onion',
  'urn,♈,aich,eDonkey',
  'urn,🪩,bitprint,Gnutella2',
  'urn,🌊️,btih,BitTorrent',
  'urn,⛲️,btmh,BitTorrent2', //💧️
  'urn,♈,ed2k_u,eDonkey',
  'urn,⏭️,kzhash,Fasttrack',
  'urn,❤️‍🔥️,md5,Shareaza',
  'urn,❄️,sha1,Frostwire',
  'urn,🌬️,tiger,DC',
  'web,🟢️,app_android_w,Market', //📱
  'web,🟤️,app_gallery_w,AppGallery', //📱
  'web,🟣️,app_kaios_w,KaiStore',
  'web,⚪️,app_ios_w,AppStore', //🍎️
  'web,👁️‍🗨️️,chateye_w,Chat,WARNING: This chat service logs your conversations to its records.  Use Use XMPP instead',
  'web,🗨️,chat_w,Chat',
  'web,🔵️,chromium_w,Chrome Store',
  'web,🦅️,falkon,Falkon Store',
  'web,🧊,flathub,Flatpak',
  'web,🐲️,kde,KDE Store', //🪟️🏪️
  'web,🔴️,mozilla,Mozilla Store',
  'web,⚆,app_ubports_w,Open Store',
  'web,🌕,palemoon,PaleMoon Store',
  'web,🟠️,snapcraft,Snap Store', //🪶️
  'web,🟡️,windows,Windows Store']; //🦎️
//  'pge,✉️,contact,Contact'

// ADC aka DC (Advanced Direct Connect)
const adc = [
  'adc:',
  'adcs:',
  'dchub:'];

const aich = [
  'aich'];

const asc = [
  '.asc',
  '.asc.txt',
  '.gpg',
  '.gpg.txt',
  '.pgp',
  '.pgp.txt'];

const app_android = [
  '.apk'];

const app_android_w = [
  '://f-droid.org/packages/',
  '://apt.izzysoft.de/fdroid/index/apk/',
  '://play.google.com/store/apps/details?id='];

const app_gallery_w = [
  '://appgallery.huawei.com/app/'];

const app_ios_w = [
  '://apps.apple.com/app/',
  '://apps.apple.com/us/app/'];

const app_kaios_w = [
  '://store.bananahackers.net/',
  '://www.kaiostech.com/store/apps/'];

const app_ubports_w = [
  '://open-store.io/app/'];

const appstream = [
  'appstream:'];

const bitprint = [
  'bitprint'];

const btih = [
  'btih'];

const btmh = [
  'btmh'];

const cabal = [
  'cabal:'];

const chateye = [
  'viber:',
  'tencent:',
  'tg:',
  'whatsapp:'];

const chateye_w = [
  '://discord.com',
  '://discord.gg',
  '://t.me',
  '://telegram.me',
  '://chat.whatsapp.com',
  '://wa.me',
  '://api.whatsapp.com/send?phone=',
  '://web.whatsapp.com/send?phone=',
  '://m.me'];

const chat_w = [
  '://join.jabber.network/#',
  '://anonymous.cheogram.com',
  '://magicbroccoli.de/i/',
  '://webchat.disroot.org/#converse/room?jid=',
  '://xmpp.org/chat#converse/room?jid=',
  '://yaxim.org/chat/#converse/room?jid=',
  '://yax.im/i/'];

const chromium = [
  '.chrome.zip',
  '.chromium.zip',
  '.crx'];

const chromium_w = [
  '://chrome.google.com/webstore/detail/'];

const debian = [
  '.deb'];

const ed2k = [
  'ed2k:'];

const ed2k_u = [
  'ed2k',
  'ed2khash'];

const email = [
  'mailto:'];

const falkon = [
  '://store.falkon.org/p/'];

const feed = [
  'feed:',
  'news:'];

const feed_a = [
  'atom',
  'rss',
  'stream',
  'rdf',
  'feed',
  'feed+json'];

const feed_x = [
  '.atom', '.atom.php', '.atom.xml',
  '.rss', '.rss.php', '.rss.xml',
  '.rdf', '.rdf.php', '.rdf.xml'];

const flathub = [
  '://flathub.org/apps/details/'];

const flatpak = [
  '.flatpakref'];

const geo = [
  'geo:',
  'waze:'];

const geo_x = [
  '.gpx',
  '.geojson',
  '.kml',
  '.kmx'];

const i2p = [
  '.i2p',
  '.i2p:'];

const ipfs = [
  'ipfs:',
  'ipns:',
  'dweb:'];

const irc = [
  'ircs:',
  'irc:'];

const itpc = [
  'itpc:'];

// TODO handle ?join and ?message
const jid = [
  'xmpp:'];

const kde = [
  '://store.kde.org/p/'];

const kzhash = [
  'kzhash'];

const mac = [
  '.pkg',
  '.dmg'];

const matrix = [
  'element:',
  'matrix:'];

const md5 = [
  'md5'];

const metalink = [
  '.meta4',
  '.metalink'];

const windows = [
  '://apps.microsoft.com/store/detail/',
  '://microsoftedge.microsoft.com/addons/detail/',
  '://www.microsoft.com/store/apps/'];

const onion = [
  '.onion',
  '.onion:'];

const openstore = [
  'openstore'];

const sha1 = [
  'sha1'];

// TODO ask snapcraft for path /app/
const snapcraft = [
  '://snapcraft.io/'];

const telephone = [
  'tel:'];

const tiger = [
  'tree:tiger'];

const torrent = [
  '.torrent'];

const udp = [
  'udp:'];

const userjs = [
  '.user.js'];

const voip = [
  'sip:',
  'weixin:',
  'skype:'];

const wallet = [
  'monero:',
  'ethereum:',
  'litecoin:',
  'bitcoin:'];

const reactos = [
  '.exe',
  '.msi'];

const xpi = [
  '.xpi',
  '.firefox.zip'];

const mozilla = [
  '://addons.mozilla.org'];

const palemoon = [
  '://addons.palemoon.org',
  '://realityripple.com/Software/XUL/',
  '://realityripple.com/Software/Mozilla-Extensions/'];

let eles = [];

// FIXME type to be applied everywhere
let type = [];

const namespace = 'i2p.schimon.blackbelt';

function determineType(type) {
  type = type.split(',');
  // TODO find an alternative way
  // THIS IS NOT GOOD!
  // JSON might be a solution
  array = eval(type[2])
  switch (type[0]) {

    case 'alt':
      extractRel(array, type);
      break;

    case 'ext':
      extractFile(array, type);
      break;

    case 'uri':
      extractURI(array, type);
      break;

    case 'url':
      extractURL(array, type);
      break;

    case 'urn':
      extractURN(array, type);
      break;

    case 'web':
      extractWeb(array, type);
      break;
  }
}

// NOTE TODO semi-recursive callback
// NOTE TODO typeof
function extractFile(array, type) {
  if (checkID(type)) {return};
  let i = 0;

  do {
    // FIXME Mainstream to support ends-with
    // fn:ends-with appears to be missing in some engines
    query = [
      '//a[contains(@href, "' + array[i] + '")]/@href',
      '//a[contains(@download, "' + array[i] + '")]/@download'];
//      '//a[ends-with(@href, "' + array[i] + '")]/@href'
//      '//a[ends-with(text(), "' + array[i] + '")]/@href'
    result = executeQuery(query, 'xpath');
    i = i + 1;
  } while (!result && i < array.length);

  if (result) {
  protocol = location.protocol
  hostname = location.hostname
  //console.log(result)
    switch (true) {

      case (result.startsWith('/')):
      result = protocol + '//' + hostname + result;
      break;

      case (!result.includes(':')):
      result = protocol + '//' + hostname + '/' + result;
      break;

      //case (result.startsWith('http')):
      //break;
    }

    //console.log(result)
    let url = new URL(result);
    let bol = url.pathname.endsWith(array[i-1]);
    if (bol) { createLink(result, type) };
  }
}


function extractRel(array, type) {
  if (checkID(type)) {return};
  let i = 0;

  do {
    query = [
     // Also rel="feed". See https://miranda-ng.org/
      '//link[@rel="alternate"\
       and contains(@type, "' + array[i] + '")\
       ]/@href'];
    result = executeQuery(query, 'xpath');
    i = i + 1;
  } while (!result && i < array.length);

  if (result) { createLink(result, type) };
}


function extractURI(array, type) {
  if (checkID(type)) {return};
  let i = 0;

  do {
    query = [
      '//a[starts-with(@href, "' + array[i] + '")\
      and not(starts-with(@href, "tg://msg_url?"))\
      and not(starts-with(@href, "mailto:?"))\
      and not(contains(@href, "/send?"))\
      ]/@href'];
    result = executeQuery(query, 'xpath');
    i = i + 1;
  } while (!result && i < array.length);

  if (result) {
    let url = new URL(result);
    let bol = url.protocol.match(array[i-1]);
    if (bol) { createLink(result, type) };
  }
}


function extractURL(array, type) {
  if (checkID(type)) {return};
  let i = 0;

  do {
    query = [
      '//a[starts-with(@href, "http")\
       and contains(@href, "' + array[i] + '")\
       ]/@href'];
      // FIXME mainstream
      //'//a[starts-with(@href, "http") and ends-with(@href, "' + array[i] + '")]/@href'
    result = executeQuery(query, 'xpath');
    i = i + 1;
  } while (!result && i < array.length);

  if (result) {
    let url = new URL(result);
    let bol = url.hostname.endsWith(array[i-1]);
    if (bol) { createLink(result, type) };
    //if (!url) {
    //  url = url.host.contains(array[i] + ':');
    //}
  }
}


function extractURN(array, type) {
  if (checkID(type)) {return};
  let i = 0;

  do {
    query = [
      '//a[starts-with(@href, "magnet")\
       and contains(@href, "' + array[i] + '")\
       ]/@href'];
    result = executeQuery(query, 'xpath');
    i = i + 1;
  } while (!result && i < array.length);

  if (result) {
    let url = new URL(result);
    url.searchParams.delete('tr');
    result = url.protocol + url.search;
    result = decodeURIComponent(result);
    createLink(result, type)
    //let bol = url.hostname.startsWith(array[i-1]);
    //if (bol) { createLink(result, type) };
  }
}


function extractWeb(array, type) {
  if (checkID(type)) {return};
  let i = 0;

  do {
    query = [
      '//a[starts-with(@href, "http")\
       and contains(@href, "' + array[i] + '")\
       and not(starts-with(@href, "https://wa.me/?text"))\
       and not(starts-with(@href, "https://t.me/share"))\
       and not(starts-with(@href, "https://telegram.me/share"))\
       and not(contains(@href, "com.github.android"))\
       and not(contains(@href, "1477376905"))\
       ]/@href'];
    result = executeQuery(query, 'xpath');
    i = i + 1;
  } while (!result && i < array.length);

  if (result) { createLink(result, type) };
}


// TODO
// String.prototype.toLowerCase()
// href Magnet: (magnet:) is not detected, or
// Set document MIMEType to plain/text
function executeQuery(queries, method) {

  let i = 0;
  do {
    switch(method) {
      case 'css':
      result = document.querySelector(queries[i]);
      //if (result) {result = result.href};
      if (result) {return result.href};
      break;

      case 'xpath':
      // NOTE This may cause 404 error.
      // Use getPathTo()
      // https://stackoverflow.com/questions/2631820/how-do-i-ensure-saved-click-coordinates-can-be-reload-to-the-same-place-even-if/2631931#2631931
      /*
      xhtmlFile = new XMLSerializer().serializeToString(document).toLowerCase()
      //xhtmlFile = '<html>'+document.documentElement.innerHTML.toLowerCase()+'</html>'
      domParser = new DOMParser();
      xhtmlFile = domParser.parseFromString(xhtmlFile, 'text/html');
      result = document.evaluate(
        queries[i], xhtmlFile, null, XPathResult.STRING_TYPE);
      */
      result = document.evaluate(
        queries[i], document, null, XPathResult.STRING_TYPE);
      //if (result) {result = result.stringValue};
      if (result) {return result.stringValue};
    }
  } while (!result && i < queries.length);
}

function checkID(type) {
  for (let i = 0; i < eles.length; i++) {
    if (eles[i].id === type[3] + '_' + type[1] + '_OUJS') {
      return true;
    }
  }
}

function createLink(uri, type) {
  //if (type[4]) { 
  //let tip = document.createElement('spna');
  //tip.class = 'tooltip';
  //tip.append('type[4]');
  //}

  //type = type.split(' ');
  //sym = getUrnProperty(uri, 'sym');
  //net = getUrnProperty(uri, 'net');

  let ele = document.createElement('a');
  ele.append(type[1] + ' ' + type[3]);
  ele.href = uri;
  ele.id = type[3] + '_' + type[1] + '_OUJS';
  if (type[4]) { ele.title = type[4] }
  ele.style.all = 'unset';
  ele.style.color = '#eee';
  ele.style.font = 'caption';
  ele.style.fontFamily = 'arial';
  ele.style.fontSize = '15px'; // 13px
  ele.style.fontVariantCaps = 'all-small-caps';
  ele.style.padding = '3px 9px 3px 9px';
  ele.style.textDecoration = 'none';
  ele.style.cursor = 'pointer';

  //ele.style.forEach (style => style + '!important');
  for (let i = 0; i < ele.style.length; i++) {
    ele.style.setProperty(ele.style[i], ele.style.getPropertyValue(ele.style[i]), 'important');
  }

  //ele.append(tip);

  //console.log(ele)
  //console.log(eles)
  eles.push(ele);
}

// TODO if eles[i].id does not exist
types.forEach(type => determineType(type));


// Torrent V1
// TODO handle compressed sha1 http://www.debath.co.uk/MakeAKey.html
// TODO convert base32 to hash
// 32/40 https://linuxtracker.org/?page=torrent-details&id=173a0f61ef92b158547937fa0c01e9dc704779f9
function generateTorrent() {
  for (let i = 0; i < eles.length; i++) {
    if (eles[i].id === 'Torrent_📦️_OUJS') {
      return
      // TODO generate link else-if onclick
      // 404 https://bookshelf.theanarchistlibrary.org/library/librarian-previous-announcements-en#toc1
    } else {
      if (eles[i].id === 'BitTorrent_🌊️_OUJS') {
        href = eles[i].href;
        let url = new URL(href);
        name = url.searchParams.get('dn');
        if (!name) {name = document.title};
        //xt = url.searchParams.get('xt');
        hash = url.searchParams.get('xt').slice(9);
        //if (ha.length === 40 && xt.startsWith('urn:btih'))
        if (hash.length === 40) {
          links = [
            'https://watercache.libertycorp.org/get/' + hash + '/' + name,
            'https://itorrents.org/torrent/' + hash + '.torrent?title=' + name,
            'https://firecache.libertycorp.org/get/' + hash + '/' + name,
            'http://fcache63sakpihd44kxdduy6kgpdhgejgp323wci435zwy6kiylcnfad.onion/get/' + hash + '/' + name,
            ];
          //type = types.findIndex(element => element === 'ext 📦️ torrent TORRENT');
          //type = type.split(' ');
          type = 'ext,📦️,torrent,TORRENT'; //🧧️ //🎁️
          type = type.split(',');
          //console.log('links[1]')
          //console.log(links[1])
          //console.log('result')
          //console.log(result)
          createLink(links[1], type)
        }
      }
    }
  }
}


if (eles.length > 0) {
  let bar = document.createElement(namespace);
  bar.style.all = 'unset';
  bar.style.opacity = 0.75;
  bar.style.backgroundColor = '#000'; //'#2c3e50';
  bar.style.color = '#eee';
  //bar.style.setProperty("color", "#eee", "!important")
  bar.style.fontVariant = 'small-caps';
  bar.style.left = 0;
  bar.style.right = 0;
  bar.style.top = 0;
  bar.style.zIndex = 10000000000;
  bar.style.maxHeight = 'fit-content';
  bar.style.padding = '6px'; //13px //15px //11px //9px //6px //3px //1px
  bar.style.position = 'fixed';
  bar.style.textAlign = 'center';
  bar.style.direction = 'ltr';
  bar.style.opacity = 0.5;
  bar.style.userSelect = 'none';
  bar.onclick = () => { bar.remove(); }
  bar.onmouseover = () => { bar.style.opacity = 0.9; }
  bar.onmouseout = () => {
    var secs = 20;
    function timeOut() {
      bar.onmouseout = () => { secs = 20; }
      secs -= 1;
      if (secs == 17) {
        bar.style.opacity = 0.3;
        setTimeout(timeOut, 1000);
      } else if (secs == 0) {
        bar.remove();
        return;
      } else {
        setTimeout(timeOut, 1000);
      }
    } timeOut();
  }
  for (let i = 0; i < bar.style.length; i++) {
    bar.style.setProperty(bar.style[i], bar.style.getPropertyValue(bar.style[i]), 'important');
  }

  // document.body should be enough
  const top = document.querySelector('body');
  top.prepend(bar);

  generateTorrent()

  eles.forEach(ele => bar.append(ele));
  //console.log("eles.forEach(ele => bar.append(ele));")
  //console.log(eles)

  // Timer from https://stackoverflow.com/questions/27406765/hide-div-after-x-amount-of-seconds

  var secs = 33;
  function timeOut() {
    secs -= 1;
    if (secs == 0) {
      //bar.style.display = 'none';
      bar.style.opacity = 0.2;
      return;
    }
    else {
      setTimeout(timeOut, 1000);
    }
  }
  timeOut();
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址