Spaces:
Sleeping
Sleeping
| package main | |
| import ( | |
| "fmt" | |
| "io" | |
| "log" | |
| "net/http" | |
| "strings" | |
| "time" | |
| ) | |
| func main() { | |
| // Handler untuk proxy video | |
| http.HandleFunc("/proxy", proxyHandler) | |
| // Serve HTML generator | |
| http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { | |
| http.ServeFile(w, r, "index.html") | |
| }) | |
| log.Println("π Server running on :7860") | |
| log.Println("π‘ Proxy endpoint: http://localhost:7860/proxy") | |
| log.Println("π CORS enabled for all origins") | |
| log.Fatal(http.ListenAndServe(":7860", nil)) | |
| } | |
| func proxyHandler(w http.ResponseWriter, r *http.Request) { | |
| // ============ CORS HEADERS ============ | |
| // Allow all origins untuk remote upload | |
| w.Header().Set("Access-Control-Allow-Origin", "*") | |
| w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD") | |
| w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, Range") | |
| w.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Range, Content-Disposition, Accept-Ranges") | |
| w.Header().Set("Access-Control-Allow-Credentials", "true") | |
| w.Header().Set("Access-Control-Max-Age", "86400") // 24 jam cache preflight | |
| // Handle preflight OPTIONS request | |
| if r.Method == "OPTIONS" { | |
| w.WriteHeader(http.StatusOK) | |
| return | |
| } | |
| // ============ PARSE PARAMETERS ============ | |
| query := r.URL.Query() | |
| videoParam := query.Get("video") | |
| exp := query.Get("exp") | |
| sig := query.Get("sig") | |
| videoType := query.Get("type") // "mp4" atau "null" | |
| domain := query.Get("d") // domain CDN, misal: cdn.xvdhsy.pro | |
| if videoParam == "" || exp == "" || sig == "" || domain == "" { | |
| http.Error(w, "Missing parameters: video, exp, sig, d required", http.StatusBadRequest) | |
| return | |
| } | |
| // ============ REKONSTRUKSI URL ============ | |
| // Hapus .mp4 dari video parameter | |
| videoId := strings.TrimSuffix(videoParam, ".mp4") | |
| // Pastikan domain tidak memiliki protocol | |
| domain = strings.TrimPrefix(domain, "http://") | |
| domain = strings.TrimPrefix(domain, "https://") | |
| domain = strings.TrimSuffix(domain, "/") | |
| // Rekonstruksi URL asli | |
| // Format: https://{domain}/{videoId}?exp={exp}&sig={sig} | |
| originalURL := fmt.Sprintf("https://%s/%s?exp=%s&sig=%s", domain, videoId, exp, sig) | |
| log.Printf("π₯ Proxy request: %s -> %s", r.URL.String(), originalURL) | |
| // ============ BUAT REQUEST ============ | |
| client := &http.Client{ | |
| Timeout: 120 * time.Second, // Timeout lebih lama untuk video besar | |
| } | |
| req, err := http.NewRequest("GET", originalURL, nil) | |
| if err != nil { | |
| http.Error(w, "Failed to create request", http.StatusInternalServerError) | |
| return | |
| } | |
| // Set headers untuk request ke CDN | |
| req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") | |
| req.Header.Set("Accept", "*/*") | |
| req.Header.Set("Accept-Encoding", "gzip, deflate, br") | |
| req.Header.Set("Accept-Language", "en-US,en;q=0.9") | |
| req.Header.Set("Connection", "keep-alive") | |
| req.Header.Set("Sec-Fetch-Dest", "video") | |
| req.Header.Set("Sec-Fetch-Mode", "cors") | |
| req.Header.Set("Sec-Fetch-Site", "cross-site") | |
| // Copy Range header untuk support streaming | |
| if rangeHeader := r.Header.Get("Range"); rangeHeader != "" { | |
| req.Header.Set("Range", rangeHeader) | |
| log.Printf("π Range request: %s", rangeHeader) | |
| } | |
| // ============ EKSEKUSI REQUEST ============ | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| log.Printf("β Error fetching video: %v", err) | |
| http.Error(w, "Failed to fetch video", http.StatusInternalServerError) | |
| return | |
| } | |
| defer resp.Body.Close() | |
| log.Printf("β Response status: %s, Content-Length: %s", resp.Status, resp.Header.Get("Content-Length")) | |
| // ============ SET RESPONSE HEADERS ============ | |
| // CORS headers (already set above) | |
| // Content Type untuk video | |
| contentType := resp.Header.Get("Content-Type") | |
| if contentType == "" || !strings.Contains(contentType, "video") { | |
| contentType = "video/mp4" | |
| } | |
| w.Header().Set("Content-Type", contentType) | |
| // Support range requests (streaming & seeking) | |
| w.Header().Set("Accept-Ranges", "bytes") | |
| // Copy Content-Length jika ada | |
| if resp.Header.Get("Content-Length") != "" { | |
| w.Header().Set("Content-Length", resp.Header.Get("Content-Length")) | |
| } | |
| // Set Content-Disposition berdasarkan parameter type | |
| filename := videoId | |
| if videoType == "mp4" { | |
| filename = videoId + ".mp4" | |
| } | |
| // Gunakan attachment agar bisa di-download oleh remote upload | |
| w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename)) | |
| // Cache control untuk performance | |
| w.Header().Set("Cache-Control", "public, max-age=86400") | |
| // Support untuk Doodstream & platform sejenis | |
| w.Header().Set("X-Content-Type-Options", "nosniff") | |
| w.Header().Set("X-Frame-Options", "SAMEORIGIN") | |
| // ============ KIRIM RESPONSE ============ | |
| // Copy status code | |
| w.WriteHeader(resp.StatusCode) | |
| // Copy body dengan buffering untuk performance | |
| buffer := make([]byte, 32*1024) // 32KB buffer | |
| io.CopyBuffer(w, resp.Body, buffer) | |
| log.Printf("β Stream completed for: %s", videoId) | |
| } |