fuckingfixes
Some checks are pending
CI Pipeline / Run Tests (push) Waiting to run
CI Pipeline / Lint Code (push) Waiting to run
CI Pipeline / Security Scan (push) Waiting to run
CI Pipeline / E2E Tests (push) Blocked by required conditions

This commit is contained in:
Enki 2025-08-29 10:30:19 -07:00
parent 3b9bf95247
commit 24d45a139e
5 changed files with 130 additions and 38 deletions

View File

@ -2,6 +2,7 @@ package api
import ( import (
"context" "context"
"crypto/sha1"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -335,6 +336,7 @@ type UploadResponse struct {
FileHash string `json:"file_hash"` FileHash string `json:"file_hash"`
Message string `json:"message"` Message string `json:"message"`
TorrentHash string `json:"torrent_hash,omitempty"` TorrentHash string `json:"torrent_hash,omitempty"`
TorrentInfoHash string `json:"torrent_info_hash,omitempty"`
MagnetLink string `json:"magnet_link,omitempty"` MagnetLink string `json:"magnet_link,omitempty"`
NostrEventID string `json:"nostr_event_id,omitempty"` NostrEventID string `json:"nostr_event_id,omitempty"`
NIP71EventID string `json:"nip71_event_id,omitempty"` NIP71EventID string `json:"nip71_event_id,omitempty"`
@ -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)) pieces := make([]torrent.PieceInfo, len(chunkHashes))
for i, chunkHash := range chunkHashes { for i, chunkHash := range chunkHashes {
// Convert hex string to bytes for torrent hash // Get actual chunk data and calculate SHA-1 hash
hashBytes := make([]byte, 20) chunkData, err := g.blossomClient.Get(chunkHash)
copy(hashBytes, []byte(chunkHash)[:20]) 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{ pieces[i] = torrent.PieceInfo{
Index: i, Index: i,
Hash: [20]byte(hashBytes), Hash: sha1Hash, // Actual SHA-1 hash
SHA256: chunkHash, SHA256: chunkHash,
Length: int(g.config.GetChunkSize()), Length: len(chunkData), // Actual chunk size
} }
} }
@ -1168,10 +1177,11 @@ func (g *Gateway) handleTorrentUpload(w http.ResponseWriter, r *http.Request, fi
// Send success response for torrent // Send success response for torrent
response := UploadResponse{ response := UploadResponse{
FileHash: metadata.Hash, FileHash: metadata.Hash, // SHA-256 file hash
Message: "File uploaded successfully as torrent", Message: "File uploaded successfully as torrent",
TorrentHash: torrentInfo.InfoHash, TorrentHash: torrentInfo.InfoHash, // Same as TorrentInfoHash (legacy)
MagnetLink: torrentInfo.Magnet, TorrentInfoHash: torrentInfo.InfoHash, // SHA-1 info hash for magnets
MagnetLink: torrentInfo.Magnet, // Already correct
NostrEventID: nostrEventID, NostrEventID: nostrEventID,
NIP71EventID: nip71EventID, NIP71EventID: nip71EventID,
} }

View File

@ -11,6 +11,7 @@ import (
"time" "time"
"torrentGateway/internal/config" "torrentGateway/internal/config"
"github.com/anacrolix/torrent/bencode"
) )
const ( 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 // sendPing sends a ping message to a node
func (d *DHT) sendPing(addr *net.UDPAddr) error { func (d *DHT) sendPing(addr *net.UDPAddr) error {
// Simplified ping message (in real implementation, would use bencode) // Create proper DHT ping message
message := []byte("ping") 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 { if err == nil {
d.statsMu.Lock() d.statsMu.Lock()
d.stats.PacketsSent++ d.stats.PacketsSent++
d.statsMu.Unlock() d.statsMu.Unlock()
log.Printf("Sent DHT ping to %s", addr)
} }
return err return err

View File

@ -30,8 +30,13 @@ type PieceInfo struct {
} }
func CreateTorrent(fileInfo FileInfo, trackers []string, gatewayURL string, dhtNodes [][]interface{}) (*TorrentInfo, error) { func CreateTorrent(fileInfo FileInfo, trackers []string, gatewayURL string, dhtNodes [][]interface{}) (*TorrentInfo, error) {
// Calculate piece length based on file size (following BUD-10 spec) // Use actual piece size from first piece instead of calculated
pieceLength := calculatePieceLength(fileInfo.Size) 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 // Create pieces buffer - concatenated SHA-1 hashes
var pieces []byte var pieces []byte
@ -43,7 +48,7 @@ func CreateTorrent(fileInfo FileInfo, trackers []string, gatewayURL string, dhtN
info := metainfo.Info{ info := metainfo.Info{
Name: fileInfo.Name, Name: fileInfo.Name,
Length: fileInfo.Size, Length: fileInfo.Size,
PieceLength: pieceLength, PieceLength: actualPieceLength, // Use ACTUAL size
Pieces: pieces, Pieces: pieces,
} }

View File

@ -1388,6 +1388,43 @@ button:hover, .action-btn:hover {
color: var(--accent-primary); 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 Details Modal */
.file-preview-large { .file-preview-large {
text-align: center; text-align: center;

View File

@ -616,8 +616,8 @@ class GatewayUI {
// Use torrent info hash for magnet link if available, otherwise use file hash // Use torrent info hash for magnet link if available, otherwise use file hash
let magnetHash = hash; let magnetHash = hash;
if (fileData && fileData.torrent_info && fileData.torrent_info.InfoHash) { if (fileData && fileData.torrent_hash) {
magnetHash = fileData.torrent_info.InfoHash; magnetHash = fileData.torrent_hash;
console.log('Using torrent InfoHash for magnet:', magnetHash); console.log('Using torrent InfoHash for magnet:', magnetHash);
} else { } else {
console.log('No torrent InfoHash found, using file hash:', magnetHash); console.log('No torrent InfoHash found, using file hash:', magnetHash);
@ -635,6 +635,11 @@ class GatewayUI {
links.hls = `${baseUrl}/api/stream/${hash}/playlist.m3u8`; 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); this.showShareModal(name, links);
} catch (error) { } catch (error) {
console.error('Failed to get file metadata:', error); console.error('Failed to get file metadata:', error);
@ -671,14 +676,29 @@ class GatewayUI {
const linkTypes = [ const linkTypes = [
{ key: 'direct', label: 'Direct Download', icon: '⬇️' }, { key: 'direct', label: 'Direct Download', icon: '⬇️' },
{ key: 'torrent', label: 'Torrent File', 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: '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 linksContainer.innerHTML = linkTypes
.filter(type => links[type.key]) .filter(type => links[type.key])
.map(type => ` .map(type => {
if (type.key === 'nostr') {
return `
<div class="share-link nostr-link">
<label>${type.icon} ${type.label}</label>
<div class="input-group">
<input type="text" value="${links[type.key]}" readonly>
<button onclick="copyToClipboard('${links[type.key].replace(/'/g, '\\\'')}')" class="copy-btn">Copy</button>
<button onclick="window.open('https://njump.me/${links[type.key]}', '_blank')" class="open-btn">Open</button>
</div>
<small>Opens in Nostr clients or web viewer</small>
</div>
`;
}
return `
<div class="share-link"> <div class="share-link">
<label>${type.icon} ${type.label}</label> <label>${type.icon} ${type.label}</label>
<div class="input-group"> <div class="input-group">
@ -686,7 +706,8 @@ class GatewayUI {
<button onclick="copyToClipboard('${links[type.key].replace(/'/g, '\\\'')}')" class="copy-btn">Copy</button> <button onclick="copyToClipboard('${links[type.key].replace(/'/g, '\\\'')}')" class="copy-btn">Copy</button>
</div> </div>
</div> </div>
`).join(''); `;
}).join('');
modal.style.display = 'block'; modal.style.display = 'block';
} }