Files
LNXSDK/leenkx/Sources/leenkx/network/torrent/Magnet.hx
2026-10-03 17:40:22 -07:00

116 lines
2.8 KiB
Haxe

package leenkx.network.torrent;
import haxe.io.Bytes;
class Magnet {
public var infoHash:Bytes;
public var infoHashHex:String;
public var infoHashV2:Bytes;
public var infoHashV2Hex:String;
public var name:String = null;
public var trackers:Array<String> = [];
public var webSeeds:Array<String> = [];
public function new() {}
public static function parse(uri:String):Magnet {
if (uri.indexOf("magnet:?") != 0) return null;
var m = new Magnet();
var query = uri.substr(8);
for (param in query.split("&")) {
var eq = param.indexOf("=");
if (eq < 0) continue;
var key = param.substr(0, eq);
var value = StringTools.urlDecode(param.substr(eq + 1));
switch (key) {
case "xt":
if (value.indexOf("urn:btih:") == 0) {
var hash = value.substr(9);
m.infoHash = decodeBtih(hash);
if (m.infoHash != null) {
m.infoHashHex = Crypto.toHex(m.infoHash);
}
} else if (value.indexOf("urn:btmh:") == 0) {
var hash = value.substr(9);
if (hash.length == 68) hash = hash.substr(4);
if (hash.length == 64) {
try {
m.infoHashV2 = Bytes.ofHex(hash);
} catch(e:Dynamic) {}
if (m.infoHashV2 != null) {
m.infoHashV2Hex =
Crypto.toHex(m.infoHashV2);
}
}
}
case "dn":
m.name = value;
case "tr":
m.trackers.push(value);
case "ws":
m.webSeeds.push(value);
default:
}
}
if (m.infoHash == null && m.infoHashV2 == null) return null;
return m;
}
public static function build(infoHash:Bytes, name:String, trackers:Array<String>, ?webSeeds:Array<String>, ?infoHashV2:Bytes):String {
var parts:Array<String> = [];
if (infoHash != null) {
parts.push("xt=urn:btih:" + Crypto.toHex(infoHash));
}
if (infoHashV2 != null) {
parts.push("xt=urn:btmh:1220"
+ Crypto.toHex(infoHashV2));
}
if (name != null) {
parts.push("dn=" + StringTools.urlEncode(name));
}
if (trackers != null) {
for (t in trackers) {
parts.push("tr=" + StringTools.urlEncode(t));
}
}
if (webSeeds != null) {
for (w in webSeeds) {
parts.push("ws=" + StringTools.urlEncode(w));
}
}
return "magnet:?" + parts.join("&");
}
static function decodeBtih(s:String):Bytes {
if (s.length == 40) {
try {
return Bytes.ofHex(s);
} catch(e:Dynamic) {
return null;
}
}
if (s.length == 32) {
return base32Decode(s.toUpperCase());
}
return null;
}
static function base32Decode(s:String):Bytes {
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var out = new haxe.io.BytesBuffer();
var buffer = 0;
var bits = 0;
for (i in 0...s.length) {
var v = alphabet.indexOf(s.charAt(i));
if (v < 0) return null;
buffer = (buffer << 5) | v;
bits += 5;
if (bits >= 8) {
bits -= 8;
out.addByte((buffer >> bits) & 0xFF);
}
}
return out.getBytes();
}
}