From 4a0d1a288b2d6fe8e3bdb77cac1762ef69dd91bf Mon Sep 17 00:00:00 2001 From: firebadnofire Date: Sun, 4 May 2025 13:30:55 -0400 Subject: [PATCH] RC1 --- main.go | 77 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/main.go b/main.go index 58ea3ef..ab4bc93 100644 --- a/main.go +++ b/main.go @@ -1,31 +1,47 @@ package main import ( + "context" + "flag" "fmt" "log" "os" - "strings" + "path/filepath" + "regexp" "time" "github.com/anacrolix/torrent" ) +var invalidFilenameChars = regexp.MustCompile(`[<>:"/\\|?*\x00-\x1F]`) + +// sanitizeFilename replaces invalid filesystem characters with underscores. +func sanitizeFilename(name string) string { + return invalidFilenameChars.ReplaceAllString(name, "_") +} + func main() { - if len(os.Args) < 2 { - fmt.Println("Usage: ./magnet-to-torrent \"magnet_link\" [output.torrent]") + // Command-line flags + output := flag.String("o", "", "Output .torrent filename (optional)") + timeout := flag.Duration("timeout", 5*time.Minute, "Timeout for metadata retrieval") + upload := flag.Bool("upload", false, "Allow uploading (seeding) after metadata retrieval") + + flag.Usage = func() { + fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [options] magnet_link\nOptions:\n", os.Args[0]) + flag.PrintDefaults() + } + flag.Parse() + + if flag.NArg() < 1 { + flag.Usage() os.Exit(1) } + magnetLink := flag.Arg(0) - magnetLink := os.Args[1] - outputFile := "" - - if len(os.Args) == 3 { - outputFile = os.Args[2] - } - + // Configure torrent client clientConfig := torrent.NewDefaultClientConfig() clientConfig.DataDir = os.TempDir() - clientConfig.NoUpload = true + clientConfig.NoUpload = !*upload client, err := torrent.NewClient(clientConfig) if err != nil { @@ -33,35 +49,46 @@ func main() { } defer client.Close() + // Add magnet link t, err := client.AddMagnet(magnetLink) if err != nil { log.Fatalf("Error adding magnet link: %v", err) } - <-t.GotInfo() - info := t.Metainfo() + // Wait for metadata with timeout + ctx, cancel := context.WithTimeout(context.Background(), *timeout) + defer cancel() - if outputFile == "" { - outputFile = strings.ReplaceAll(t.Name(), " ", "_") + ".torrent" + select { + case <-t.GotInfo(): + // Metadata received + case <-ctx.Done(): + log.Fatalf("Timeout waiting for metadata after %v", *timeout) } - f, err := os.Create(outputFile) + info := t.Metainfo() + + // Determine output filename + outFile := *output + if outFile == "" { + base := sanitizeFilename(t.Name()) + outFile = base + ".torrent" + } + outFile = filepath.Clean(outFile) + + // Write .torrent file + f, err := os.Create(outFile) if err != nil { - log.Fatalf("Error creating file: %v", err) + log.Fatalf("Error creating file %s: %v", outFile, err) } defer f.Close() - err = info.Write(f) - if err != nil { + if err := info.Write(f); err != nil { log.Fatalf("Error writing torrent file: %v", err) } - fmt.Printf("Torrent metadata saved to %s\n", outputFile) + fmt.Printf("Torrent metadata saved to %s\n", outFile) + // Drop the torrent handle; client will close via defer t.Drop() - client.Close() - - // Give client some time to gracefully close - time.Sleep(2 * time.Second) } -