// ==UserScript==
// @name Black Belt
// @author Schimon Jehudah, Adv.
// @namespace i2p.schimon.blackbelt
// @homepageURL https://gf.qytechs.cn/en/scripts/466113-black-belt
// @supportURL https://gf.qytechs.cn/en/scripts/466113-black-belt/feedback
// @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.21
// @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
// 'uri,🧲,magnet,Magnet',
// 'pge,✉️,contact,Contact'
const types = {
"adc" : {
"name" : "🫐️ DC", // Advanced Direct Connect
"description" : "File transfer link",
"urn" : ['tree:tiger'],
"uri" : [
"adc:",
"adcs:",
"dchub:"]
},
"apk" : {
"name" : "📥️ Package",
"description" : "Android package",
"condition" : ["android"],
"extension" : ["apk"],
"web" : [
"://f-droid.org/packages/",
"://apt.izzysoft.de/fdroid/index/apk/",
"://play.google.com/store/apps/details?id=",
"://appgallery.huawei.com/app/"]
},
"asc" : {
"name" : "🗝️ Key", // Additional Sense Code
"description" : "Encryption Key",
"extension" : [
"asc",
".asc.txt",
".gpg",
".gpg.txt",
".pgp",
".pgp.txt"]
},
"appstream" : {
"name" : "📥️ Package",
"description" : "AppStream package",
"condition" : ["linux"],
"uri" : ["appstream:"]
},
"bitprint" : {
"name" : "🪩 Gnutella2",
"description" : "File transfer link",
"urn" : ["bitprint"]
},
"bittorrent" : {
"name" : "🌊️ Torrent", //💧️ //⛲️
"description" : "File transfer link or metadata file",
"extension" : [".torrent"],
"urn" : ["btih", "btmh"]
},
"cabal" : {
"name" : "🔽 Cabal", //︾ //🔽 //⧩ //➤
"description" : "",
"uri" : ["cabal:"]
},
"chromium" : {
"name" : "🧩 Extension",
"description" : "Web browser extension",
"condition" : ["brave", "chrome", "chromium", "vivaldi"],
"extension" : [".crx", ".chromium.zip", ".chrome.zip"],
"web" : ["://chrome.google.com/webstore/detail/"]
},
"debian" : {
"name" : "📥️ Package",
"description" : "🐧️ Debian package",
"condition" : ["debian", "ubuntu"],
"extension" : [".deb"]
},
"ed2k" : {
"name" : "♈ eDonkey",
"description" : "File transfer link", // eDonkey2000
"uri" : ["ed2k:"],
"urn" : [
"aich",
"ed2k",
"ed2khash"]
},
"edge" : {
"name" : "🧩 Extension",
"description" : "Web browser extension",
"condition" : ["edge"],
"web" : ["://microsoftedge.microsoft.com/addons/detail/"]
},
"email" : {
"name" : "✉️ Email",
"description" : "Send an Email Message",
"uri" : ["mailto:"]
},
"fedora" : {
"name" : "📥️ Package",
"description" : "🐧️ Linux package",
"condition" : ["fedora", "redhat"],
"extension" : [".rpm"]
},
"flatpak" : {
"name" : "📥️ Package",
"description" : "Flatpak package",
"condition" : ["linux"],
"extension" : [".flatpakref"],
"web" : ["://flathub.org/apps/details/"]
},
"falkon" : {
"name" : "🧩 Extension", // 🦅️
"description" : "Web browser extension",
"condition" : ["falkon"],
"web" : ["://store.falkon.org/p/"]
},
"frostwire" : {
"name" : "❄️ Frostwire", // LimeWire
"description" : "File transfer link",
"urn" : ["sha1"]
},
"geo" : {
"name" : "📍️ Location",
"description" : "Geographic coordinations",
"extension" : [
".gpx",
".geojson",
".kml",
".kmx"],
"uri" : [
"geo:",
"waze:"]
},
"i2p" : {
"name" : "㊙️ i2p", //㊣
"description" : "Enter the i2p system",
"url" : [".i2p", ".i2p:"]
},
"ios" : {
"name" : "📥️ Package", //🍎️
"description" : "",
"condition" : ["ios", "iphone", "ipad"],
"web" : ["://apps.apple.com/app/",
"://apps.apple.com/us/app/"]
},
"ipfs" : {
"name" : "💠 IPFS", //📁 //📂 //🗃️ //⚛️ //💎️ //🕸️
"description" : "Interplanetary File System",
"uri" : ["ipfs:", "ipns:", "dweb:"]
},
"irc" : {
"name" : "🗨️ IRC",
"description" : "Internet Relay Chat",
"uri" : ["irc:", "ircs:"]
},
"kaios" : {
"name" : "📥️ Package",
"description" : "Kai OS package",
"condition" : ["kai"],
"web" : ["://store.bananahackers.net/",
"://www.kaiostech.com/store/apps/"]
},
"kazzaa" : {
"name" : "⏭️ Fasttrack",
"description" : "File transfer link",
"urn" : ["kzhash"]
},
"kde" : {
"name" : "🐲️ KDE",
"description" : "KDE package",
"condition" : ["linux", "react", "windows"],
"web" : ["://store.kde.org/p/"]
},
"mac" : {
"name" : "📥️ Package",
"description" : "Macintosh package",
"condition" : ["mac"],
"extension" : [",dmg", ".pkg"],
"urn" : [""]
},
"matrix" : {
"name" : "#️ matrix", //#️⃣️ //# //⌗ //濫
"description" : "Compromised messagin system. We recommend using XMPP instead, for better privacy",
"uri" : ["element:", "matrix:"]
},
"metalink" : {
"name" : "♾️ Metalink",
"description" : "Metalink file",
"extension" : [".meta4", ".metalink"]
},
"news" : {
"name" : "📰 Follow", //索 //參
"description" : "Subscribe to News Feed",
"alternate" : [
"atom",
"rss",
"rdf",
"stream",
"feed",
"feed+json"],
"extension" : [
".atom",
".atom.php",
".atom.xml",
".rss",
".rss.php",
".rss.xml",
".rdf",
".rdf.php",
".rdf.xml"],
"path" : [
"/feed",
"/feed/",
".feed.atom",
".feed.rss",
".feed.rdf",
".feed.json"],
"uri" : [
"feed:",
"news:"]
},
"onion" : {
"name" : "🧅️ Tor",
"description" : "Enter the Tor system",
"url" : [".onion", ".onion:"]
},
"podcast" : {
"name" : "🎙️ Podcast",
"description" : "Podcast channel",
"uri" : ["itpc:"]
},
"reactos" : {
"name" : "📥️ Package",
"description" : "React OS package",
"condition" : ["windows", "react"],
"extension" : [".exe", ".msi"],
"web" : ["://apps.microsoft.com/store/detail/",
"://www.microsoft.com/store/apps/"]
},
"shareaza" : {
"name" : "❤️🔥️ Shareaza",
"description" : "File transfer link",
"urn" : ["md5"]
},
// TODO ask snapcraft for path /app/
"snapcraft" : {
"name" : "🪶️ Snapcraft", // 🛍️
"description" : "Snapcraft package",
"condition" : ["linux"],
"web" : ["://snapcraft.io/"]
},
"telephone" : {
"name" : "☎️ Call",
"description" : "Telephone number",
"uri" : ["udp:"]
},
"tracker" : {
"name" : "📶 Tracker",
"description" : "BitTorrent tracker",
"uri" : ["udp:"]
},
"ubports" : {
"name" : "📥️ Package",
"description" : "Ubuntu Touch App",
"condition" : ["ubuntu"],
"uri" : ["openstore:"],
"web" : ["://open-store.io/app/"]
},
"userjs" : {
"name" : "📜 Userscript", //🐒
"description" : "BitTorrent metadata file",
"extension" : [".user.js"]
},
"voip" : {
"name" : "📞️ VoIP",
"description" : "Voice over IP",
"uri" : ["sip:",
"weixin:",
"skype:"]
},
"wallet" : {
"name" : "🪙️ Wallet",
"description" : "Cryptocurrency wallet",
"uri" : ["monero:",
"ethereum:",
"litecoin:",
"bitcoin:"]
},
"xmpp" : {
"name" : "💡️ Jabber",
"description" : "Chat securely over the XMPP network",
"web" : [
"://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/"],
// TODO handle ?join and ?message
"uri" : ["xmpp:"]
},
"xpi" : {
"name" : "🧩 Extension", //🐺️ //🦊️ //🦎️
"description" : "Web browser extension",
"condition" : ["firefox", "librewolf", "waterfox"],
"extension" : [".xpi", ".firefox.zip"],
"web" : ["://addons.mozilla.org"]
},
"xul" : {
"name" : "🧩 Extension", // 🌕
"description" : "Web browser extension",
"web" : ["://addons.palemoon.org",
"://realityripple.com/Software/XUL/",
"://realityripple.com/Software/Mozilla-Extensions/"]
},
"chat" : {
"name" : "👁️🗨️️ Chat",
"description" : "WARNING: This chat service logs your conversations to its records. Use XMPP instead",
"web" : [
"://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"],
"uri" : ["viber:",
"tencent:",
"tg:",
"whatsapp:"]
}
};
const namespace = 'i2p.schimon.blackbelt';
const objectKeys = Object.keys(types);
(function() {
let links = [], accept;
for (let i = 0; i < objectKeys.length; i++) {
let agent = types[objectKeys[i]].condition, accept = true;
if (agent) {
accept = false;
for (let j = 0; j < agent.length; j++) {
if (navigator.userAgent.toLowerCase().includes(agent[j])) {
accept = true;
}
}
}
//if (reject) continue;
let
result,
title = types[objectKeys[i]].name,
//descr = types[objectKeys[i]].description,
array = types[objectKeys[i]];
if (array.alternate) {
result = extractRel(array.alternate);
}
if (!result && array.extension) {
result = extractFile(array.extension);
}
if (!result && array.uri) {
result = extractURI(array.uri);
}
if (!result && array.url) {
result = extractURL(array.url);
}
if (!result && array.urn) {
result = extractURN(array.urn);
}
if (!result && array.web) {
result = extractWeb(array.web);
}
if (accept && result) {
links.push(createLink(result, title, objectKeys[i]));
}
}
if (links.length) {
buildBar(links);
}
})();
// TODO if eles[i].id does not exist
//types.forEach(type => determineType(type));
function buildBar(links) {
if (links.length) {
let bar = document.createElement(namespace);
bar.id = namespace + '-bar';
bar.style.all = 'unset';
bar.style.width = '100%';
bar.style.opacity = 0.5; // 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.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(links);
//bar.append(closeButton(bar));
links.forEach(link => bar.append(link));
//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();
}
}
// NOTE TODO semi-recursive callback
// NOTE TODO typeof
function extractFile(array) {
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) { return result; };
}
}
function extractRel(array) {
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) { return result; };
}
function extractURI(array) {
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) { return result; };
}
}
function extractURL(array) {
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) { return result };
//if (!url) {
// url = url.host.contains(array[i] + ':');
//}
}
}
function extractURN(array) {
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);
return result;
//let bol = url.hostname.startsWith(array[i-1]);
//if (bol) { createLink(result, type) };
}
}
function extractWeb(array) {
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) { return result; };
}
// 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 createLink(uri, title, id) {
//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.id = namespace + '.' + id;
ele.textContent = title;
ele.href = uri;
//ele.title = info;
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.textDecoration = 'none';
ele.style.cursor = 'pointer';
//ele.style.fontWeight = 'bold';
//ele.style.padding = '3px 9px 3px 9px';
//ele.style.margin = '0 9px 0 9px';
ele.style.margin = '2% 9px 2% 9px';
//ele.style.background = 'black';
//ele.style.borderBottomLeftRadius = '9px';
//ele.style.borderBottomRightRadius = '9px';
//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)
return ele;
}
// 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(links) {
for (let i = 0; i < links.length; i++) {
if (links[i].id === 'Torrent_📦️_OUJS') {
return
// TODO generate link else-if onclick
// 404 https://bookshelf.theanarchistlibrary.org/library/librarian-previous-announcements-en#toc1
} else {
if (links[i].id === 'BitTorrent_🌊️_OUJS') {
href = links[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)
}
}
}
}
}
function closeButton(bar) {
let ele = document.createElement('span');
ele.textContent = 'X';
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.textDecoration = 'none';
ele.style.fontWeight = 'bold';
ele.style.padding = '3px 9px 3px 9px';
//ele.style.margin = '0 9px 0 9px';
ele.style.background = 'black';
ele.style.borderBottomLeftRadius = '9px';
ele.style.borderBottomRightRadius = '9px';
//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.onclick = () => { bar.remove(); }
return ele;
}