dongsii commited on
Commit
14e8051
Β·
verified Β·
1 Parent(s): 05a470b

Update main.go

Browse files
Files changed (1) hide show
  1. main.go +308 -138
main.go CHANGED
@@ -1,151 +1,321 @@
1
  package main
2
 
3
  import (
4
- "fmt"
5
- "io"
6
- "log"
7
- "net/http"
8
- "strings"
9
- "time"
 
 
10
  )
11
 
12
- func main() {
13
- // Handler untuk proxy video
14
- http.HandleFunc("/proxy", proxyHandler)
15
-
16
- // Serve HTML generator
17
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
18
- http.ServeFile(w, r, "index.html")
19
- })
20
 
21
- log.Println("πŸš€ Server running on :7860")
22
- log.Println("πŸ“‘ Proxy endpoint: http://localhost:7860/proxy")
23
- log.Println("🌐 CORS enabled for all origins")
24
- log.Fatal(http.ListenAndServe(":7860", nil))
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
 
27
- func proxyHandler(w http.ResponseWriter, r *http.Request) {
28
- // ============ CORS HEADERS ============
29
- // Allow all origins untuk remote upload
30
- w.Header().Set("Access-Control-Allow-Origin", "*")
31
- w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD")
32
- 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")
33
- w.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Range, Content-Disposition, Accept-Ranges")
34
- w.Header().Set("Access-Control-Allow-Credentials", "true")
35
- w.Header().Set("Access-Control-Max-Age", "86400") // 24 jam cache preflight
36
-
37
- // Handle preflight OPTIONS request
38
- if r.Method == "OPTIONS" {
39
- w.WriteHeader(http.StatusOK)
40
- return
41
- }
42
-
43
- // ============ PARSE PARAMETERS ============
44
- query := r.URL.Query()
45
- videoParam := query.Get("video")
46
- exp := query.Get("exp")
47
- sig := query.Get("sig")
48
- videoType := query.Get("type") // "mp4" atau "null"
49
- domain := query.Get("d") // domain CDN, misal: cdn.xvdhsy.pro
50
-
51
- if videoParam == "" || exp == "" || sig == "" || domain == "" {
52
- http.Error(w, "Missing parameters: video, exp, sig, d required", http.StatusBadRequest)
53
- return
54
- }
55
-
56
- // ============ REKONSTRUKSI URL ============
57
- // Hapus .mp4 dari video parameter
58
- videoId := strings.TrimSuffix(videoParam, ".mp4")
59
-
60
- // Pastikan domain tidak memiliki protocol
61
- domain = strings.TrimPrefix(domain, "http://")
62
- domain = strings.TrimPrefix(domain, "https://")
63
- domain = strings.TrimSuffix(domain, "/")
64
-
65
- // Rekonstruksi URL asli
66
- // Format: https://{domain}/{videoId}?exp={exp}&sig={sig}
67
- originalURL := fmt.Sprintf("https://%s/%s?exp=%s&sig=%s", domain, videoId, exp, sig)
68
-
69
- log.Printf("πŸ“₯ Proxy request: %s -> %s", r.URL.String(), originalURL)
70
-
71
- // ============ BUAT REQUEST ============
72
- client := &http.Client{
73
- Timeout: 120 * time.Second, // Timeout lebih lama untuk video besar
74
- }
75
-
76
- req, err := http.NewRequest("GET", originalURL, nil)
77
- if err != nil {
78
- http.Error(w, "Failed to create request", http.StatusInternalServerError)
79
- return
80
- }
81
 
82
- // Set headers untuk request ke CDN
83
- 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")
84
- req.Header.Set("Accept", "*/*")
85
- req.Header.Set("Accept-Encoding", "gzip, deflate, br")
86
- req.Header.Set("Accept-Language", "en-US,en;q=0.9")
87
- req.Header.Set("Connection", "keep-alive")
88
- req.Header.Set("Sec-Fetch-Dest", "video")
89
- req.Header.Set("Sec-Fetch-Mode", "cors")
90
- req.Header.Set("Sec-Fetch-Site", "cross-site")
91
-
92
- // Copy Range header untuk support streaming
93
- if rangeHeader := r.Header.Get("Range"); rangeHeader != "" {
94
- req.Header.Set("Range", rangeHeader)
95
- log.Printf("πŸ“Š Range request: %s", rangeHeader)
96
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- // ============ EKSEKUSI REQUEST ============
99
- resp, err := client.Do(req)
100
- if err != nil {
101
- log.Printf("❌ Error fetching video: %v", err)
102
- http.Error(w, "Failed to fetch video", http.StatusInternalServerError)
103
- return
104
- }
105
- defer resp.Body.Close()
 
 
 
 
 
 
 
 
 
106
 
107
- log.Printf("βœ… Response status: %s, Content-Length: %s", resp.Status, resp.Header.Get("Content-Length"))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
- // ============ SET RESPONSE HEADERS ============
110
- // CORS headers (already set above)
111
-
112
- // Content Type untuk video
113
- contentType := resp.Header.Get("Content-Type")
114
- if contentType == "" || !strings.Contains(contentType, "video") {
115
- contentType = "video/mp4"
116
- }
117
- w.Header().Set("Content-Type", contentType)
118
-
119
- // Support range requests (streaming & seeking)
120
- w.Header().Set("Accept-Ranges", "bytes")
121
-
122
- // Copy Content-Length jika ada
123
- if resp.Header.Get("Content-Length") != "" {
124
- w.Header().Set("Content-Length", resp.Header.Get("Content-Length"))
125
- }
126
-
127
- // Set Content-Disposition berdasarkan parameter type
128
- filename := videoId
129
- if videoType == "mp4" {
130
- filename = videoId + ".mp4"
131
- }
132
- // Gunakan attachment agar bisa di-download oleh remote upload
133
- w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
134
-
135
- // Cache control untuk performance
136
- w.Header().Set("Cache-Control", "public, max-age=86400")
137
-
138
- // Support untuk Doodstream & platform sejenis
139
- w.Header().Set("X-Content-Type-Options", "nosniff")
140
- w.Header().Set("X-Frame-Options", "SAMEORIGIN")
141
-
142
- // ============ KIRIM RESPONSE ============
143
- // Copy status code
144
- w.WriteHeader(resp.StatusCode)
145
-
146
- // Copy body dengan buffering untuk performance
147
- buffer := make([]byte, 32*1024) // 32KB buffer
148
- io.CopyBuffer(w, resp.Body, buffer)
149
-
150
- log.Printf("βœ… Stream completed for: %s", videoId)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  }
 
1
  package main
2
 
3
  import (
4
+ "fmt"
5
+ "io"
6
+ "log"
7
+ "net/http"
8
+ "net/url"
9
+ "path"
10
+ "strings"
11
+ "time"
12
  )
13
 
 
 
 
 
 
 
 
 
14
 
15
+ func main() {
16
+ // Handler untuk download dengan path
17
+ http.HandleFunc("/download/", downloadPathHandler)
18
+
19
+ // Handler untuk download dengan query parameter (backward compatibility)
20
+ http.HandleFunc("/download", downloadQueryHandler)
21
+
22
+ // Handler untuk serve HTML generator
23
+ http.HandleFunc("/", serveHTMLHandler)
24
+
25
+ log.Println("πŸš€ Server running on http://localhost:7860")
26
+ log.Println("πŸ“₯ Generator: http://localhost:7860")
27
+ log.Println("πŸ“₯ Download: http://localhost:7860/download/{domain}/{path}")
28
+ log.Println("πŸ“₯ Download (with .mp4): http://localhost:7860/download/mp4/{domain}/{path}")
29
+ log.Fatal(http.ListenAndServe(":7860", nil))
30
  }
31
 
32
+ // Handler untuk serve HTML file
33
+ func serveHTMLHandler(w http.ResponseWriter, r *http.Request) {
34
+ if r.URL.Path != "/" {
35
+ http.NotFound(w, r)
36
+ return
37
+ }
38
+ http.ServeFile(w, r, "index.html")
39
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ // Handler untuk download dengan path /download/{domain}/{path} atau /download/mp4/{domain}/{path}
42
+ func downloadPathHandler(w http.ResponseWriter, r *http.Request) {
43
+ // Ambil path setelah /download/
44
+ pathStr := strings.TrimPrefix(r.URL.Path, "/download/")
45
+
46
+ // Pisahkan berdasarkan slash
47
+ parts := strings.Split(pathStr, "/")
48
+
49
+ if len(parts) < 2 {
50
+ http.Error(w, "Invalid URL format. Use: /download/{domain}/{path} or /download/mp4/{domain}/{path}", http.StatusBadRequest)
51
+ return
52
+ }
53
+
54
+ var targetURL string
55
+ hasMp4Format := false
56
+
57
+ // Cek apakah ada format mp4 di awal (penanda bahwa URL asli sudah ada .mp4)
58
+ if parts[0] == "mp4" {
59
+ // Format: /download/mp4/{domain}/{path} β†’ URL asli sudah ada .mp4
60
+ hasMp4Format = true
61
+ parts = parts[1:] // Hapus "mp4" dari array
62
+ }
63
+
64
+ // Ambil domain (parts[0]) dan path (sisanya)
65
+ if len(parts) < 2 {
66
+ http.Error(w, "Invalid URL format. Need domain and path", http.StatusBadRequest)
67
+ return
68
+ }
69
+
70
+ domain := parts[0]
71
+ pathParts := parts[1:]
72
+ pathStr2 := strings.Join(pathParts, "/")
73
+
74
+ // Bangun URL target berdasarkan format
75
+ if hasMp4Format {
76
+ // Ada /mp4/ di URL β†’ URL asli sudah memiliki .mp4, langsung gunakan
77
+ targetURL = "https://" + domain + "/" + pathStr2
78
+ } else {
79
+ // Tidak ada /mp4/ di URL β†’ URL asli tidak ada .mp4
80
+ // HAPUS .mp4 dari akhir path (karena proxy URL ditambahi .mp4 untuk remote upload)
81
+ if strings.HasSuffix(pathStr2, ".mp4") {
82
+ pathStr2 = strings.TrimSuffix(pathStr2, ".mp4")
83
+ }
84
+ targetURL = "https://" + domain + "/" + pathStr2
85
+ }
86
+
87
+ log.Printf("πŸ“₯ HasMp4Format: %v, Target URL: %s", hasMp4Format, targetURL)
88
+
89
+ // Proxy download STREAMING dengan HEAD request
90
+ downloadProxyStreamWithHead(w, r, targetURL)
91
+ }
92
 
93
+ // Handler untuk download dengan query parameter (backward compatibility)
94
+ func downloadQueryHandler(w http.ResponseWriter, r *http.Request) {
95
+ encodedURL := r.URL.Query().Get("url")
96
+ if encodedURL == "" {
97
+ http.Error(w, "Parameter 'url' required", http.StatusBadRequest)
98
+ return
99
+ }
100
+
101
+ targetURL, err := url.QueryUnescape(encodedURL)
102
+ if err != nil {
103
+ http.Error(w, "Invalid URL", http.StatusBadRequest)
104
+ return
105
+ }
106
+
107
+ log.Printf("πŸ“₯ Downloading: %s", targetURL)
108
+ downloadProxyStreamWithHead(w, r, targetURL)
109
+ }
110
 
111
+ // Fungsi proxy download dengan HEAD request terlebih dahulu
112
+ func downloadProxyStreamWithHead(w http.ResponseWriter, r *http.Request, targetURL string) {
113
+ // Ambil nama file dari URL
114
+ filename := path.Base(targetURL)
115
+ if !strings.HasSuffix(filename, ".mp4") {
116
+ filename = "video.mp4"
117
+ }
118
+
119
+ log.Printf("πŸ“₯ Streaming: %s", targetURL)
120
+
121
+ // STEP 1: HEAD request untuk mendapatkan metadata
122
+ headReq, err := http.NewRequest("HEAD", targetURL, nil)
123
+ if err != nil {
124
+ http.Error(w, "Failed to create HEAD request", http.StatusInternalServerError)
125
+ return
126
+ }
127
+
128
+ // Set headers untuk HEAD request
129
+ headReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36")
130
+ headReq.Header.Set("Referer", targetURL)
131
+ headReq.Header.Set("Accept", "*/*")
132
+ headReq.Header.Set("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8")
133
+ headReq.Header.Set("Accept-Encoding", "gzip, deflate, br")
134
+ headReq.Header.Set("Cache-Control", "no-cache")
135
+ headReq.Header.Set("Pragma", "no-cache")
136
+ headReq.Header.Set("Sec-Fetch-Dest", "video")
137
+ headReq.Header.Set("Sec-Fetch-Mode", "no-cors")
138
+ headReq.Header.Set("Sec-Fetch-Site", "same-origin")
139
+ headReq.Header.Set("Connection", "keep-alive")
140
+
141
+ client := &http.Client{
142
+ Timeout: 30 * time.Second,
143
+ Transport: &http.Transport{
144
+ MaxIdleConns: 100,
145
+ MaxIdleConnsPerHost: 100,
146
+ IdleConnTimeout: 90 * time.Second,
147
+ DisableCompression: false,
148
+ ResponseHeaderTimeout: 30 * time.Second,
149
+ },
150
+ }
151
+
152
+ // Eksekusi HEAD request
153
+ headResp, err := client.Do(headReq)
154
+ if err != nil {
155
+ log.Printf("⚠️ HEAD request failed: %v, fallback to normal GET", err)
156
+ // Fallback ke GET biasa
157
+ downloadProxyStream(w, r, targetURL)
158
+ return
159
+ }
160
+ defer headResp.Body.Close()
161
+
162
+ // Ambil Content-Length dari HEAD
163
+ contentLength := headResp.ContentLength
164
+ log.Printf("πŸ“Š HEAD response - Content-Length: %d bytes (%.2f MB)", contentLength, float64(contentLength)/(1024*1024))
165
+
166
+ // STEP 2: GET request untuk streaming
167
+ req, err := http.NewRequest("GET", targetURL, nil)
168
+ if err != nil {
169
+ http.Error(w, "Failed to create request", http.StatusInternalServerError)
170
+ return
171
+ }
172
+
173
+ // Set headers lengkap (sama seperti sebelumnya)
174
+ req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36")
175
+ req.Header.Set("Referer", targetURL)
176
+ req.Header.Set("Accept", "*/*")
177
+ req.Header.Set("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8")
178
+ req.Header.Set("Accept-Encoding", "gzip, deflate, br")
179
+ req.Header.Set("Cache-Control", "no-cache")
180
+ req.Header.Set("Pragma", "no-cache")
181
+ req.Header.Set("Sec-Fetch-Dest", "video")
182
+ req.Header.Set("Sec-Fetch-Mode", "no-cors")
183
+ req.Header.Set("Sec-Fetch-Site", "same-origin")
184
+ req.Header.Set("Connection", "keep-alive")
185
+
186
+ // Forward Range header jika ada
187
+ if rangeHeader := r.Header.Get("Range"); rangeHeader != "" {
188
+ req.Header.Set("Range", rangeHeader)
189
+ }
190
+
191
+ // Eksekusi GET request
192
+ resp, err := client.Do(req)
193
+ if err != nil {
194
+ http.Error(w, fmt.Sprintf("Download failed: %v", err), http.StatusInternalServerError)
195
+ log.Printf("❌ Error: %v", err)
196
+ return
197
+ }
198
+ defer resp.Body.Close()
199
+
200
+ // Cek status code
201
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
202
+ http.Error(w, fmt.Sprintf("Status: %d", resp.StatusCode), resp.StatusCode)
203
+ log.Printf("❌ Status: %d", resp.StatusCode)
204
+ return
205
+ }
206
+
207
+ log.Printf("βœ… Connected, Status: %d", resp.StatusCode)
208
+
209
+ // Set header untuk download
210
+ w.Header().Set("Content-Type", "video/mp4")
211
+ w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
212
+
213
+ // Gunakan Content-Length dari HEAD (lebih akurat)
214
+ if contentLength > 0 {
215
+ w.Header().Set("Content-Length", fmt.Sprintf("%d", contentLength))
216
+ } else if resp.ContentLength > 0 {
217
+ w.Header().Set("Content-Length", fmt.Sprintf("%d", resp.ContentLength))
218
+ }
219
+
220
+ // Forward header lainnya
221
+ if resp.Header.Get("Accept-Ranges") != "" {
222
+ w.Header().Set("Accept-Ranges", resp.Header.Get("Accept-Ranges"))
223
+ }
224
+ if resp.Header.Get("Content-Range") != "" {
225
+ w.Header().Set("Content-Range", resp.Header.Get("Content-Range"))
226
+ }
227
+
228
+ // Set status code
229
+ w.WriteHeader(resp.StatusCode)
230
+
231
+ // STREAMING: Kirim langsung
232
+ buf := make([]byte, 64*1024) // 64KB buffer (lebih besar)
233
+ written, err := io.CopyBuffer(w, resp.Body, buf)
234
+ if err != nil {
235
+ log.Printf("❌ Error streaming: %v", err)
236
+ return
237
+ }
238
+
239
+ log.Printf("βœ… Stream complete: %s (%.2f MB)", filename, float64(written)/(1024*1024))
240
+ }
241
 
242
+ // Fallback: Proxy streaming tanpa HEAD (untuk backup)
243
+ func downloadProxyStream(w http.ResponseWriter, r *http.Request, targetURL string) {
244
+ filename := path.Base(targetURL)
245
+ if !strings.HasSuffix(filename, ".mp4") {
246
+ filename = "video.mp4"
247
+ }
248
+
249
+ log.Printf("πŸ“₯ Streaming (fallback): %s", targetURL)
250
+
251
+ req, err := http.NewRequest("GET", targetURL, nil)
252
+ if err != nil {
253
+ http.Error(w, "Failed to create request", http.StatusInternalServerError)
254
+ return
255
+ }
256
+
257
+ req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36")
258
+ req.Header.Set("Referer", targetURL)
259
+ req.Header.Set("Accept", "*/*")
260
+ req.Header.Set("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8")
261
+ req.Header.Set("Accept-Encoding", "gzip, deflate, br")
262
+ req.Header.Set("Cache-Control", "no-cache")
263
+ req.Header.Set("Pragma", "no-cache")
264
+ req.Header.Set("Sec-Fetch-Dest", "video")
265
+ req.Header.Set("Sec-Fetch-Mode", "no-cors")
266
+ req.Header.Set("Sec-Fetch-Site", "same-origin")
267
+ req.Header.Set("Connection", "keep-alive")
268
+
269
+ if rangeHeader := r.Header.Get("Range"); rangeHeader != "" {
270
+ req.Header.Set("Range", rangeHeader)
271
+ }
272
+
273
+ client := &http.Client{
274
+ Timeout: 15 * time.Minute,
275
+ Transport: &http.Transport{
276
+ MaxIdleConns: 100,
277
+ MaxIdleConnsPerHost: 100,
278
+ IdleConnTimeout: 90 * time.Second,
279
+ DisableCompression: false,
280
+ ResponseHeaderTimeout: 30 * time.Second,
281
+ },
282
+ }
283
+
284
+ resp, err := client.Do(req)
285
+ if err != nil {
286
+ http.Error(w, fmt.Sprintf("Download failed: %v", err), http.StatusInternalServerError)
287
+ log.Printf("❌ Error: %v", err)
288
+ return
289
+ }
290
+ defer resp.Body.Close()
291
+
292
+ if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
293
+ http.Error(w, fmt.Sprintf("Status: %d", resp.StatusCode), resp.StatusCode)
294
+ log.Printf("❌ Status: %d", resp.StatusCode)
295
+ return
296
+ }
297
+
298
+ w.Header().Set("Content-Type", "video/mp4")
299
+ w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
300
+
301
+ if resp.ContentLength > 0 {
302
+ w.Header().Set("Content-Length", fmt.Sprintf("%d", resp.ContentLength))
303
+ }
304
+ if resp.Header.Get("Accept-Ranges") != "" {
305
+ w.Header().Set("Accept-Ranges", resp.Header.Get("Accept-Ranges"))
306
+ }
307
+ if resp.Header.Get("Content-Range") != "" {
308
+ w.Header().Set("Content-Range", resp.Header.Get("Content-Range"))
309
+ }
310
+
311
+ w.WriteHeader(resp.StatusCode)
312
+
313
+ buf := make([]byte, 64*1024)
314
+ written, err := io.CopyBuffer(w, resp.Body, buf)
315
+ if err != nil {
316
+ log.Printf("❌ Error streaming: %v", err)
317
+ return
318
+ }
319
+
320
+ log.Printf("βœ… Stream complete (fallback): %s (%.2f MB)", filename, float64(written)/(1024*1024))
321
  }