2022-09-06 20:44:58 +02:00
<!DOCTYPE html>
< html lang = "en" >
< head >
2023-09-23 16:50:18 +02:00
< title > 🧲 HashyMagnet< / title >
< meta name = "description" content = "Generate full BitTorrent Magnet Links from Info Hashes!" / >
< meta property = "og:title" content = "🧲 HashyMagnet" / >
< meta property = "og:description" content = "Generate full BitTorrent Magnet Links from Info Hashes!" / >
2024-03-19 13:22:38 +01:00
< meta property = "og:url" content = "https://hub.octt.eu.org/HashyMagnet/" / >
< link rel = "canonical" href = "https://hub.octt.eu.org/HashyMagnet/" / >
2023-09-23 16:50:18 +02:00
< meta charset = "UTF-8" / >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" / >
< link href = "Bubbles.css" rel = "stylesheet" / >
2024-04-27 20:29:04 +02:00
< link rel = "shortcut icon" type = "image/x-icon" href = "https://cdn.jsdelivr.net/gh/octospacc/twemoji-astonishing@gh-pages/i/1f9f2.svg" / >
< link rel = "apple-touch-icon" href = "https://cdn.jsdelivr.net/gh/octospacc/twemoji-astonishing@gh-pages/i/1f9f2.svg" / >
2023-09-23 16:50:18 +02:00
< link rel = "manifest" href = "./manifest.json" / >
2024-04-17 01:30:25 +02:00
< script src = "../../shared/OctoHub-Global.js" > < / script >
2023-01-18 21:41:34 +01:00
< style >
2022-09-07 00:20:11 +02:00
Body {
2022-09-07 16:13:28 +02:00
Color: #FFFFFF;
Background-Color: #254070;
2022-09-07 00:20:11 +02:00
Margin: 0px;
Padding: 16px;
2022-09-07 16:13:28 +02:00
Overflow: Hidden;
Text-Align: Center;
Font-Family: Sans-Serif;
2022-09-07 00:20:11 +02:00
}
2022-09-07 20:25:23 +02:00
Body:Not(Input, TextArea) {
User-Select: None;
}
A {
Color: #17C3EA;
}
A:Active {
Color: #0B69BC;
}
A:Hover {
Background: #101060;
Color: #E9FFEE;
}
2023-09-21 17:29:40 +02:00
NoScript, NoScript P, .NoScript {
2022-09-07 20:25:23 +02:00
Font-Size: XX-Large;
}
Input {
2022-09-07 16:13:28 +02:00
Height: 2em;
2022-09-07 20:25:23 +02:00
}
Button {
Height: 2.25em;
}
Input, Button {
2022-09-07 16:13:28 +02:00
Font-Size: Initial;
}
Input, TextArea {
Width: Calc(90% - 8px);
Margin-Top: 8px;
Margin-Bottom: 8px;
}
2022-09-07 20:25:23 +02:00
Input:Disabled, TextArea:Disabled {
Color: #000000;
Background-Color: #EEEEEE;
}
Input, TextArea, Button {
Color: #000000;
Background-Color: #EEFFFF;
Border: Solid #808080 1px;
Border-Radius: 4px;
}
Button {
Color: #202020;
Padding-Left: 0.5em;
Padding-Right: 0.5em;
}
Button:Hover {
Color: #101010;
}
Button:Active {
Color: #000000;
Background-Color: #DDEEEE;
}
Button:Disabled {
Color: #808080;
Background-Color: RGBA(16,16,48,0.5);
}
2023-01-18 21:41:34 +01:00
.Section { Margin: 4px; }
.Smaller { Font-Size: Smaller; }
.NoWrap { White-Space: NoWrap; }
2022-09-07 20:25:23 +02:00
.LeftAlign {
Text-Align: Left;
Margin-Left: Calc(5% + 8px);
}
2023-01-18 21:41:34 +01:00
.ClickPointer, #TitleTitle { Cursor: Pointer; }
2022-09-07 20:25:23 +02:00
2022-09-07 16:13:28 +02:00
#Main {
2022-09-07 20:25:23 +02:00
Overflow-X: Hidden;
2022-10-29 16:40:35 +02:00
/*Overflow-Y: Scroll;*/
2022-09-07 16:13:28 +02:00
Max-Height: 100vh;
Position: Absolute;
Top: 50%;
Transform: TranslateY(-50%);
Left: 0px;
Right: 0px;
Z-Index: 4;
}
2023-01-18 21:41:34 +01:00
#LicenseText {
Overflow-Y: Auto;
2022-09-07 16:13:28 +02:00
}
2022-09-07 20:25:23 +02:00
2022-09-07 22:04:00 +02:00
#HomeBtn {
Position: Absolute;
Top: 0px;
Left: 0px;
Margin: 0px;
Padding: 8px;
}
#HomeBtn A {
Color: #19D5FF;
}
2022-09-07 20:25:23 +02:00
#ResetBtn {
Color: #FF0000;
Padding-Bottom: 4px;
Width: 2em;
}
#ResetBtn:Active, #ResetBtn:Hover {
Color: #FFFFFF;
Background-Color: #FF0000;
2022-09-07 00:20:11 +02:00
}
2023-01-18 21:41:34 +01:00
.ocean { Overflow: Hidden; }
< / style >
2022-09-06 20:44:58 +02:00
< / head >
< body >
2022-09-07 16:13:28 +02:00
< div id = "Main" >
2022-09-07 22:04:00 +02:00
< h5 id = "HomeBtn" > [< a href = ".." > 🔼 Home< / a > ]< / h5 >
2022-09-07 20:25:23 +02:00
< div id = "Title" class = "Section" > < div >
2023-01-18 21:41:34 +01:00
< div id = "TitleTitle" title = "Click for more info!" >
< h3 > HashyMagnet< / h3 >
< p > Generate a full Bit< span style = "Color:#AAFFFF;" > Torrent< / span > Magnet Link from an Info Hash...< / p >
2022-09-07 20:25:23 +02:00
< / div >
2023-09-21 17:29:40 +02:00
< noscript > < p class = "NoScript" >
2022-09-07 16:13:28 +02:00
This is an actual app, not a badly-made website.
2023-09-23 16:50:18 +02:00
< br / >
2022-09-07 20:25:23 +02:00
It needs JavaScript to work, so you need to enable it.
2023-09-23 16:50:18 +02:00
< br / >
2022-09-07 16:13:28 +02:00
The code is fully open, and you can review it with "View Page Source".
< / p > < / noscript >
2022-09-07 20:25:23 +02:00
< div id = "Info" >
< h4 > Info< / h4 >
< p > With this app you can generate full < a href = "https://en.m.wikipedia.org/wiki/Magnet_URI_scheme" target = "_blank" rel = "noopener nofollow" > BitTorrent Magnet links< / a > , complete with trackers, from a simple Info Hash.< / p >
< p > You can input your own list of trackers, or you can use the one that's autogenerated by the app from up-to-date lists.< / p >
2023-01-18 21:41:34 +01:00
< p class = "Smaller ClickPointer" onclick = "alert('{{ Changelog }}\n\n' + Changelog);" > Last updated on < span id = "LastUpdatedDate" > < / span > .< / p >
2022-09-07 20:25:23 +02:00
< h4 > Special Thanks and Credits< / h4 >
< p >
Tracker lists providers:
< ul id = "ListsProviders" >
< li > < a href = "https://github.com/ngosang/trackerslist" target = "_blank" rel = "noopener nofollow" > https://github.com/ngosang/trackerslist< / a > < / li >
< li > < a href = "https://newtrackon.com" target = "_blank" rel = "noopener nofollow" > https://newtrackon.com< / a > < / li >
< / ul >
< / p >
< p >
Background animations:
2023-09-23 16:50:18 +02:00
< br / >
2022-09-07 20:25:23 +02:00
< a href = "https://codinhood.com/micro/animate-octocat-sprite-css" target = "_blank" rel = "noopener nofollow" > https://codinhood.com/micro/animate-octocat-sprite-css< / a > < / p >
< h4 > License< / h4 >
2023-01-18 21:41:34 +01:00
< pre id = "LicenseText" > HashyMagnet
Copyright (C) 2022-2023, < a href = "https://hub.octt.eu.org" > OctoSpacc< / a >
2022-09-07 20:25:23 +02:00
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see < a href = "https://www.gnu.org/licenses" target = "_blank" rel = "noopener nofollow" > https://www.gnu.org/licenses< / a > .
< / pre >
2023-09-23 16:50:18 +02:00
< br / >
2022-09-07 20:25:23 +02:00
< / div >
< / div > < / div >
2023-09-23 16:50:18 +02:00
< br / >
2022-09-07 16:13:28 +02:00
< div class = "Section" >
< div class = "NoWrap" >
2022-09-07 20:25:23 +02:00
< button id = "ResetBtn" > < big > < b > x< / b > < / big > < / button >
< input id = "TextBox" placeholder = "📝 Paste Hash here..." >
2022-09-07 16:13:28 +02:00
< / div >
< div class = "NoWrap" >
2022-09-07 20:25:23 +02:00
< button id = "GenerateBtn" > 🧲 Generate Magnet!< / button >
< button id = "CopyBtn" > 📋 Copy< / button >
< button id = "OpenBtn" > 🔗 Open< / button >
2022-09-07 16:13:28 +02:00
< / div >
< / div >
2023-09-23 16:50:18 +02:00
< br / >
2022-09-07 16:13:28 +02:00
< div class = "Section" >
2022-09-07 20:25:23 +02:00
< div class = "NoWrap LeftAlign" >
2022-09-07 16:13:28 +02:00
< label for = "TrackersBtn" > Trackers list:< / label >
< button id = "TrackersBtn" > < / button >
< / div >
< textarea id = "TrackersArea" placeholder = "📜 Paste Trackers here..." rows = "8" cols = "60" > < / textarea >
< / div >
2023-09-23 16:50:18 +02:00
< br / >
2022-09-07 16:13:28 +02:00
< / div >
< div class = "ocean" > < div class = "bubble bubble-1" > < / div > < div class = "bubble bubble-2" > < / div > < div class = "bubble bubble-3" > < / div > < div class = "bubble bubble-4" > < / div > < div class = "bubble bubble-5" > < / div > < div class = "bubble bubble-6" > < / div > < div class = "bubble bubble-7" > < / div > < div class = "bubble bubble-8" > < / div > < div class = "bubble bubble-9" > < / div > < div class = "bubble bubble-10" > < / div > < div class = "bubble bubble-11" > < / div > < / div >
2023-01-18 21:41:34 +01:00
< script >
// https://stackoverflow.com/a/63958411
String.prototype.replaceAllTxt = function replaceAll(search, replace) {
return this.split(search).join(replace);
};
2023-04-21 16:13:27 +02:00
var OnInputChangePaste = ['onchange', 'oninput', 'onpaste'];
2023-01-18 21:41:34 +01:00
2022-09-07 16:13:28 +02:00
var Generated = false;
2022-09-07 20:25:23 +02:00
var Trackers = [],
FetchTrackers = [];
var MaxFetchTrackers = 40;
2023-04-21 16:13:27 +02:00
var TrackersSources = [
2022-09-07 00:20:11 +02:00
"https://ngosang.github.io/trackerslist/trackers_best.txt",
2022-09-07 16:13:28 +02:00
"https://newtrackon.com/api/stable",
2022-09-07 00:20:11 +02:00
];
2022-09-07 20:25:23 +02:00
function SplitLines(Text) {
if (Text == "") {
return [];
} else {
2023-01-18 21:41:34 +01:00
return Text.trim().replaceAllTxt('\r', '').replace(/\n\s*\n/g, '\n').split('\n');
};
2022-09-07 20:25:23 +02:00
}
async function DoFetchTrackers() {
2023-04-21 16:13:27 +02:00
for (var i = 0; i < TrackersSources.length ; i + + ) {
var Req = await fetch(TrackersSources[i]);
var Data = await Req.text();
2022-09-07 20:25:23 +02:00
FetchTrackers = FetchTrackers.concat(SplitLines(Data));
2022-09-07 16:13:28 +02:00
}
2022-09-07 20:25:23 +02:00
FetchTrackers = FetchTrackers.slice(0,MaxFetchTrackers);
2022-09-07 00:20:11 +02:00
}
2022-09-07 20:25:23 +02:00
function WriteFetchTrackers() {
2023-04-21 16:13:27 +02:00
for (var i = 0; i < FetchTrackers.length ; i + + ) {
2023-01-18 21:41:34 +01:00
TrackersArea.value += FetchTrackers[i] + '\n\n';
2022-09-07 00:20:11 +02:00
}
2022-09-07 20:25:23 +02:00
StoreTrackers();
2022-09-07 16:13:28 +02:00
DoRedraw();
}
function StoreTrackers() {
2022-09-07 20:25:23 +02:00
Trackers = SplitLines(TrackersArea.value);
2022-09-07 16:13:28 +02:00
}
2023-04-21 16:13:27 +02:00
async function InitialFill() {
2022-09-07 20:25:23 +02:00
await DoFetchTrackers();
WriteFetchTrackers();
2023-04-21 16:13:27 +02:00
if (new URLSearchParams(window.location.hash).get('#Hash')) {
GenerateBtn.onclick();
};
2022-09-07 00:20:11 +02:00
}
2022-09-07 16:13:28 +02:00
GenerateBtn.onclick = function() {
2023-01-18 21:41:34 +01:00
['magnet:', '?xt=urn:', 'btih:'].forEach(function(i) {
if (TextBox.value.toLowerCase().startsWith(i)) {
TextBox.value = TextBox.value.substring(i.length);
};
});
TextBox.value = 'magnet:?xt=urn:btih:' + TextBox.value;
2023-04-21 16:13:27 +02:00
for (var i = 0; i < Trackers.length ; i + + ) {
2023-01-18 21:41:34 +01:00
TextBox.value += '& tr=' + encodeURIComponent(Trackers[i]);
};
2022-09-07 16:13:28 +02:00
Generated = true;
2023-04-21 16:13:27 +02:00
// Do it two times because of a strange bug with the ResetBtn
DoRedraw(); DoRedraw();
2022-09-07 16:13:28 +02:00
};
function DoRedraw() {
2023-01-18 21:41:34 +01:00
TextBox.style = '';
2023-04-21 16:13:27 +02:00
var TextBoxWidth = TextBox.offsetWidth;
2022-09-07 16:13:28 +02:00
if (Generated) {
2023-01-18 21:41:34 +01:00
TextBox.disabled = GenerateBtn.disabled = GenerateBtn.hidden = true;
TextBox.style = 'Width:' + (TextBoxWidth - ResetBtn.offsetWidth - 12) + 'px;'
ResetBtn.hidden = CopyBtn.hidden = OpenBtn.hidden = false;
2022-09-07 16:13:28 +02:00
} else {
2023-01-18 21:41:34 +01:00
TextBox.disabled = GenerateBtn.disabled = GenerateBtn.hidden = false;
ResetBtn.hidden = CopyBtn.hidden = OpenBtn.hidden = true;
};
GenerateBtn.disabled = !TextBox.value;
TrackersBtn.textContent = (TrackersArea.value ? '🗑️ Clear' : '🔄 Restore');
2022-09-07 00:20:11 +02:00
}
2022-09-07 16:13:28 +02:00
ResetBtn.onclick = function() {
2023-01-18 21:41:34 +01:00
TextBox.value = '';
2022-09-07 16:13:28 +02:00
Generated = false;
DoRedraw();
};
CopyBtn.onclick = function() {
navigator.clipboard.writeText(TextBox.value);
};
OpenBtn.onclick = function() {
window.open(TextBox.value);
};
TrackersBtn.onclick = function() {
if (TrackersArea.value) {
2023-01-18 21:41:34 +01:00
TrackersArea.value = '';
2022-09-07 20:25:23 +02:00
StoreTrackers();
2022-09-07 16:13:28 +02:00
} else {
2022-09-07 20:25:23 +02:00
WriteFetchTrackers();
2023-01-18 21:41:34 +01:00
};
2022-09-07 16:13:28 +02:00
DoRedraw();
};
2023-01-18 21:41:34 +01:00
OnInputChangePaste.forEach(function(i) {
TrackersArea[i] = function TrackersAreaOnChange() {
StoreTrackers();
DoRedraw();
};
});
2022-09-07 20:25:23 +02:00
TextBox.onkeydown = function(e) {
if (e.keyCode == 13 & & !Generated) {
GenerateBtn.click();
2023-01-18 21:41:34 +01:00
};
2022-09-07 20:25:23 +02:00
};
2023-01-18 21:41:34 +01:00
OnInputChangePaste.forEach(function(i) {
TextBox[i] = DoRedraw;
});
2022-09-07 20:25:23 +02:00
window.onresize = DoRedraw;
Info.hidden = true;
TitleTitle.onclick = function() {
Info.hidden = !Info.hidden;
};
2022-09-07 16:13:28 +02:00
2023-01-18 21:41:34 +01:00
TextBox.value = new URLSearchParams(window.location.hash).get('#Hash');
2022-09-13 00:17:38 +02:00
2022-09-07 16:13:28 +02:00
DoRedraw();
2023-04-21 16:13:27 +02:00
InitialFill();
var Changelog = `\
[ 2023-04-21 ]
- Various UX fixes
2023-01-18 21:41:34 +01:00
[ 2023-01-18 ]
- Fix handling of torrent hash input
- Improve support for older browsers
- Improve minor UI/UX aspects
`;
2023-04-21 16:13:27 +02:00
LastUpdatedDate.innerHTML = Changelog.split('\n')[0].split('[')[1].split(']')[0].trim();
2023-01-18 21:41:34 +01:00
< / script >
2022-09-06 20:44:58 +02:00
< / body >
< / html >