package main import ( "context" "flag" "fmt" "log" "os" "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() { // 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) // Configure torrent client clientConfig := torrent.NewDefaultClientConfig() clientConfig.DataDir = os.TempDir() clientConfig.NoUpload = !*upload client, err := torrent.NewClient(clientConfig) if err != nil { log.Fatalf("Error creating torrent client: %v", err) } defer client.Close() // Add magnet link t, err := client.AddMagnet(magnetLink) if err != nil { log.Fatalf("Error adding magnet link: %v", err) } // Wait for metadata with timeout ctx, cancel := context.WithTimeout(context.Background(), *timeout) defer cancel() select { case <-t.GotInfo(): // Metadata received case <-ctx.Done(): log.Fatalf("Timeout waiting for metadata after %v", *timeout) } 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 %s: %v", outFile, err) } defer f.Close() if err := info.Write(f); err != nil { log.Fatalf("Error writing torrent file: %v", err) } fmt.Printf("Torrent metadata saved to %s\n", outFile) // Drop the torrent handle; client will close via defer t.Drop() }