diff --git a/internal/api/handlers.go b/internal/api/handlers.go index 52fa675..869b24d 100644 --- a/internal/api/handlers.go +++ b/internal/api/handlers.go @@ -2,6 +2,7 @@ package api import ( "context" + "crypto/sha1" "database/sql" "encoding/json" "fmt" @@ -332,14 +333,15 @@ type ChunkInfo struct { } type UploadResponse struct { - FileHash string `json:"file_hash"` - Message string `json:"message"` - TorrentHash string `json:"torrent_hash,omitempty"` - MagnetLink string `json:"magnet_link,omitempty"` - NostrEventID string `json:"nostr_event_id,omitempty"` - NIP71EventID string `json:"nip71_event_id,omitempty"` - StreamingURL string `json:"streaming_url,omitempty"` - HLSURL string `json:"hls_url,omitempty"` + FileHash string `json:"file_hash"` + Message string `json:"message"` + TorrentHash string `json:"torrent_hash,omitempty"` + TorrentInfoHash string `json:"torrent_info_hash,omitempty"` + MagnetLink string `json:"magnet_link,omitempty"` + NostrEventID string `json:"nostr_event_id,omitempty"` + NIP71EventID string `json:"nip71_event_id,omitempty"` + StreamingURL string `json:"streaming_url,omitempty"` + HLSURL string `json:"hls_url,omitempty"` } func NewGateway(cfg *config.Config, storage *storage.Backend) *Gateway { @@ -977,18 +979,25 @@ func (g *Gateway) handleTorrentUpload(w http.ResponseWriter, r *http.Request, fi }) } - // Create torrent pieces from chunk hashes + // Create torrent pieces from actual chunk data with proper SHA-1 hashes pieces := make([]torrent.PieceInfo, len(chunkHashes)) for i, chunkHash := range chunkHashes { - // Convert hex string to bytes for torrent hash - hashBytes := make([]byte, 20) - copy(hashBytes, []byte(chunkHash)[:20]) + // Get actual chunk data and calculate SHA-1 hash + chunkData, err := g.blossomClient.Get(chunkHash) + if err != nil { + g.writeError(w, http.StatusInternalServerError, "Failed to get chunk data", ErrorTypeInternal, + fmt.Sprintf("Failed to get chunk %s: %v", chunkHash, err)) + return + } + + // Calculate actual SHA-1 hash of chunk data + sha1Hash := sha1.Sum(chunkData) pieces[i] = torrent.PieceInfo{ Index: i, - Hash: [20]byte(hashBytes), + Hash: sha1Hash, // Actual SHA-1 hash SHA256: chunkHash, - Length: int(g.config.GetChunkSize()), + Length: len(chunkData), // Actual chunk size } } @@ -1168,12 +1177,13 @@ func (g *Gateway) handleTorrentUpload(w http.ResponseWriter, r *http.Request, fi // Send success response for torrent response := UploadResponse{ - FileHash: metadata.Hash, - Message: "File uploaded successfully as torrent", - TorrentHash: torrentInfo.InfoHash, - MagnetLink: torrentInfo.Magnet, - NostrEventID: nostrEventID, - NIP71EventID: nip71EventID, + FileHash: metadata.Hash, // SHA-256 file hash + Message: "File uploaded successfully as torrent", + TorrentHash: torrentInfo.InfoHash, // Same as TorrentInfoHash (legacy) + TorrentInfoHash: torrentInfo.InfoHash, // SHA-1 info hash for magnets + MagnetLink: torrentInfo.Magnet, // Already correct + NostrEventID: nostrEventID, + NIP71EventID: nip71EventID, } // Add streaming URL if it's a video diff --git a/internal/dht/node.go b/internal/dht/node.go index 70928ec..57a4c74 100644 --- a/internal/dht/node.go +++ b/internal/dht/node.go @@ -11,6 +11,7 @@ import ( "time" "torrentGateway/internal/config" + "github.com/anacrolix/torrent/bencode" ) const ( @@ -219,16 +220,34 @@ func (d *DHT) bootstrap() { } } +// generateTransactionID generates a transaction ID for DHT messages +func (d *DHT) generateTransactionID() string { + return fmt.Sprintf("%d", time.Now().UnixNano()%10000) +} + // sendPing sends a ping message to a node func (d *DHT) sendPing(addr *net.UDPAddr) error { - // Simplified ping message (in real implementation, would use bencode) - message := []byte("ping") + // Create proper DHT ping message + msg := map[string]interface{}{ + "t": d.generateTransactionID(), + "y": "q", + "q": "ping", + "a": map[string]interface{}{ + "id": string(d.nodeID[:]), + }, + } - _, err := d.conn.WriteToUDP(message, addr) + data, err := bencode.Marshal(msg) + if err != nil { + return err + } + + _, err = d.conn.WriteToUDP(data, addr) if err == nil { d.statsMu.Lock() d.stats.PacketsSent++ d.statsMu.Unlock() + log.Printf("Sent DHT ping to %s", addr) } return err diff --git a/internal/torrent/creator.go b/internal/torrent/creator.go index 1d322d8..cfe36fb 100644 --- a/internal/torrent/creator.go +++ b/internal/torrent/creator.go @@ -30,8 +30,13 @@ type PieceInfo struct { } func CreateTorrent(fileInfo FileInfo, trackers []string, gatewayURL string, dhtNodes [][]interface{}) (*TorrentInfo, error) { - // Calculate piece length based on file size (following BUD-10 spec) - pieceLength := calculatePieceLength(fileInfo.Size) + // Use actual piece size from first piece instead of calculated + var actualPieceLength int64 + if len(fileInfo.Pieces) > 0 { + actualPieceLength = int64(fileInfo.Pieces[0].Length) + } else { + return nil, fmt.Errorf("no pieces provided") + } // Create pieces buffer - concatenated SHA-1 hashes var pieces []byte @@ -43,7 +48,7 @@ func CreateTorrent(fileInfo FileInfo, trackers []string, gatewayURL string, dhtN info := metainfo.Info{ Name: fileInfo.Name, Length: fileInfo.Size, - PieceLength: pieceLength, + PieceLength: actualPieceLength, // Use ACTUAL size Pieces: pieces, } diff --git a/internal/web/static/style.css b/internal/web/static/style.css index 01dc586..5466a9a 100644 --- a/internal/web/static/style.css +++ b/internal/web/static/style.css @@ -1388,6 +1388,43 @@ button:hover, .action-btn:hover { color: var(--accent-primary); } +/* Nostr share link styles */ +.share-link.nostr-link { + background: linear-gradient(135deg, #8b5cf6 0%, #6366f1 100%); + padding: 15px; + border-radius: 8px; + margin-top: 10px; +} + +.share-link.nostr-link label { + color: white; + font-weight: bold; +} + +.share-link.nostr-link small { + color: rgba(255, 255, 255, 0.8); + font-size: 0.85em; + display: block; + margin-top: 5px; +} + +.share-link .open-btn { + background: #6366f1; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + margin-left: 5px; + font-family: inherit; + font-size: 10px; + text-transform: uppercase; +} + +.share-link .open-btn:hover { + background: #4f46e5; +} + /* File Details Modal */ .file-preview-large { text-align: center; diff --git a/internal/web/static/upload.js b/internal/web/static/upload.js index 3074eef..f12dcac 100644 --- a/internal/web/static/upload.js +++ b/internal/web/static/upload.js @@ -616,8 +616,8 @@ class GatewayUI { // Use torrent info hash for magnet link if available, otherwise use file hash let magnetHash = hash; - if (fileData && fileData.torrent_info && fileData.torrent_info.InfoHash) { - magnetHash = fileData.torrent_info.InfoHash; + if (fileData && fileData.torrent_hash) { + magnetHash = fileData.torrent_hash; console.log('Using torrent InfoHash for magnet:', magnetHash); } else { console.log('No torrent InfoHash found, using file hash:', magnetHash); @@ -635,6 +635,11 @@ class GatewayUI { links.hls = `${baseUrl}/api/stream/${hash}/playlist.m3u8`; } + // Add NIP-71 Nostr link if available + if (fileData && fileData.nip71_share_link) { + links.nostr = fileData.nip71_share_link; + } + this.showShareModal(name, links); } catch (error) { console.error('Failed to get file metadata:', error); @@ -671,22 +676,38 @@ class GatewayUI { const linkTypes = [ { key: 'direct', label: 'Direct Download', icon: '⬇️' }, { key: 'torrent', label: 'Torrent File', icon: '🧲' }, - { key: 'magnet', label: 'Magnet Link', icon: '🧲' }, + { key: 'magnet', label: 'Magnet Link', icon: '🔗' }, { key: 'stream', label: 'Stream Video', icon: '▶️' }, - { key: 'hls', label: 'HLS Playlist', icon: '📺' } + { key: 'hls', label: 'HLS Playlist', icon: '📺' }, + { key: 'nostr', label: 'Share on Nostr', icon: '⚡' } ]; linksContainer.innerHTML = linkTypes .filter(type => links[type.key]) - .map(type => ` -