Ultima attività 5 months ago

香港商业电台转发直播流

imyip's Avatar imyip ha revisionato questo gist 5 months ago. Vai alla revisione

Nessuna modifica

imyip's Avatar imyip ha revisionato questo gist 5 months ago. Vai alla revisione

1 file changed, 249 insertions

main.go(file creato)

@@ -0,0 +1,249 @@
1 + package main
2 +
3 + import (
4 + "fmt"
5 + "io"
6 + "log"
7 + "net/http"
8 + "net/http/cookiejar"
9 + "net/url"
10 + "regexp"
11 + "strings"
12 + "sync"
13 + "time"
14 + )
15 +
16 + // 配置常量
17 + const (
18 + BasePath = "/edge-aac/903hd"
19 + ServerPort = ":8080"
20 + )
21 +
22 + // ProxyServer 代理服务器结构 (帶狀態管理)
23 + type ProxyServer struct {
24 + jar *cookiejar.Jar
25 + cookieExpiration time.Time
26 + upstreamHost string
27 + mu sync.RWMutex
28 + }
29 +
30 + // NewProxyServer 创建新的代理服务器
31 + func NewProxyServer() *ProxyServer {
32 + jar, _ := cookiejar.New(nil)
33 + return &ProxyServer{
34 + jar: jar,
35 + cookieExpiration: time.Now(), // 設置為當前時間,以觸發首次請求時立即獲取
36 + }
37 + }
38 +
39 + // ensureValidCookies 確保 Cookie 有效,如果過期則刷新
40 + func (ps *ProxyServer) ensureValidCookies() error {
41 + ps.mu.RLock()
42 + isExpired := time.Now().After(ps.cookieExpiration)
43 + ps.mu.RUnlock()
44 +
45 + if isExpired {
46 + log.Println("Cookie expired or not yet fetched, refreshing...")
47 + ps.mu.Lock()
48 + defer ps.mu.Unlock()
49 +
50 + if time.Now().After(ps.cookieExpiration) {
51 + if err := ps.refreshCookies(); err != nil {
52 + log.Printf("Failed to refresh cookies: %v", err)
53 + return err
54 + }
55 + log.Println("Cookies refreshed successfully.")
56 + }
57 + }
58 + return nil
59 + }
60 +
61 + // refreshCookies 執行完整的 Cookie 獲取和解析流程 (已按新邏輯修改)
62 + func (ps *ProxyServer) refreshCookies() error {
63 + // --- 步驟 1 & 2: 獲取 liveJsUrl ---
64 + liveJsURL, err := getLiveJsURL("https://www.881903.com/live/903")
65 + if err != nil {
66 + return err
67 + }
68 +
69 + // --- 步驟 3: 獲取 302 回應 ---
70 + resp302, err := get302Response(liveJsURL)
71 + if err != nil {
72 + return err
73 + }
74 + defer resp302.Body.Close()
75 +
76 + // --- 步驟 4: 訪問重定向地址以獲取真正的授權 Cookie ---
77 + redirectURL, err := resp302.Location()
78 + if err != nil {
79 + return fmt.Errorf("failed to get redirect location: %w", err)
80 + }
81 +
82 + respFromRedirect, err := http.Get(redirectURL.String())
83 + if err != nil {
84 + return fmt.Errorf("failed to visit redirect URL: %w", err)
85 + }
86 + defer respFromRedirect.Body.Close()
87 +
88 + cookies := respFromRedirect.Cookies()
89 + if len(cookies) == 0 {
90 + return fmt.Errorf("no cookies found in redirect response")
91 + }
92 +
93 + // --- 按照新邏輯設置狀態 ---
94 +
95 + // 1. 清空舊的 Jar 並設置新的 Cookies
96 + ps.jar, _ = cookiejar.New(nil)
97 + ps.jar.SetCookies(redirectURL, cookies)
98 +
99 + // 2. 將 Upstream Host 設置為 redirectURL 的 Host
100 + ps.upstreamHost = redirectURL.Host
101 + log.Printf("Upstream host set to: %s", ps.upstreamHost)
102 +
103 + // 3. 將過期時間設置為當前時間 + 24 小時
104 + ps.cookieExpiration = time.Now().Add(24 * time.Hour)
105 + log.Printf("New cookie expiration set to: %s", ps.cookieExpiration.Format(time.RFC3339))
106 +
107 + return nil
108 + }
109 +
110 + // proxyRequest 處理代理請求的核心邏輯
111 + func (ps *ProxyServer) proxyRequest(w http.ResponseWriter, r *http.Request) {
112 + if err := ps.ensureValidCookies(); err != nil {
113 + http.Error(w, fmt.Sprintf("Internal Server Error: %v", err), http.StatusInternalServerError)
114 + return
115 + }
116 +
117 + ps.mu.RLock()
118 + upstreamHost := ps.upstreamHost
119 + ps.mu.RUnlock()
120 +
121 + targetURL, err := ps.buildTargetURL(r.URL.Path, upstreamHost)
122 + if err != nil {
123 + http.Error(w, err.Error(), http.StatusNotFound)
124 + return
125 + }
126 +
127 + log.Printf("Proxying request for %s to %s", r.URL.Path, targetURL)
128 +
129 + client := &http.Client{
130 + Jar: ps.jar,
131 + Timeout: 30 * time.Second,
132 + }
133 + proxyReq, err := http.NewRequest(r.Method, targetURL, nil)
134 + if err != nil {
135 + http.Error(w, "Failed to create proxy request", http.StatusInternalServerError)
136 + return
137 + }
138 +
139 + resp, err := client.Do(proxyReq)
140 + if err != nil {
141 + http.Error(w, fmt.Sprintf("Proxy request failed: %v", err), http.StatusBadGateway)
142 + return
143 + }
144 + defer resp.Body.Close()
145 +
146 + enableCORS(w)
147 + for key, values := range resp.Header {
148 + for _, value := range values {
149 + w.Header().Add(key, value)
150 + }
151 + }
152 +
153 + body, err := io.ReadAll(resp.Body)
154 + if err != nil {
155 + http.Error(w, "Failed to read response", http.StatusInternalServerError)
156 + return
157 + }
158 +
159 + w.WriteHeader(resp.StatusCode)
160 + w.Write(body)
161 + }
162 +
163 + // --- ServeHTTP 和其他輔助函數 (與之前版本相同) ---
164 + func (ps *ProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
165 + if r.Method == http.MethodOptions {
166 + enableCORS(w)
167 + w.WriteHeader(http.StatusNoContent)
168 + return
169 + }
170 + ps.proxyRequest(w, r)
171 + }
172 +
173 + func (ps *ProxyServer) buildTargetURL(path string, host string) (string, error) {
174 + switch {
175 + case path == "/" || path == "/playlist.m3u8":
176 + return fmt.Sprintf("https://%s%s/chunks.m3u8", host, BasePath), nil
177 + case strings.HasPrefix(path, "/chunk/"):
178 + return fmt.Sprintf("https://%s%s/%s", host, BasePath, strings.TrimPrefix(path, "/chunk/")), nil
179 + default:
180 + // 增加對 .aac 文件的直接代理
181 + if strings.HasSuffix(path, ".aac") {
182 + return fmt.Sprintf("https://%s%s%s", host, BasePath, path), nil
183 + }
184 + return "", fmt.Errorf("unsupported path: %s", path)
185 + }
186 + }
187 +
188 + func enableCORS(w http.ResponseWriter) {
189 + w.Header().Set("Access-Control-Allow-Origin", "*")
190 + }
191 +
192 + // --- Cookie 獲取輔助函數 (與之前版本相同) ---
193 + func getLiveJsURL(pageURL string) (string, error) {
194 + log.Println("Fetching liveJsUrl from", pageURL)
195 + resp, err := http.Get(pageURL)
196 + if err != nil {
197 + return "", err
198 + }
199 + defer resp.Body.Close()
200 + body, err := io.ReadAll(resp.Body)
201 + if err != nil {
202 + return "", err
203 + }
204 + re := regexp.MustCompile(`"liveJsUrl":"([^"]+)"`)
205 + matches := re.FindStringSubmatch(string(body))
206 + if len(matches) < 2 {
207 + return "", fmt.Errorf("liveJsUrl not found")
208 + }
209 + log.Println("Found liveJsUrl")
210 + return matches[1], nil
211 + }
212 +
213 + func get302Response(jsURL string) (*http.Response, error) {
214 + log.Println("Fetching 302 redirect from", jsURL)
215 + client := &http.Client{
216 + CheckRedirect: func(req *http.Request, via []*http.Request) error {
217 + return http.ErrUseLastResponse
218 + },
219 + }
220 + req, err := http.NewRequest("GET", jsURL, nil)
221 + if err != nil {
222 + return nil, err
223 + }
224 + req.Header.Set("accept", "*/*")
225 + req.Header.Set("referer", "https://www.881903.com/live/903")
226 + req.Header.Set("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36")
227 + resp, err := client.Do(req)
228 + if urlErr, ok := err.(*url.Error); ok && urlErr.Err == http.ErrUseLastResponse {
229 + return resp, nil
230 + }
231 + return resp, err
232 + }
233 +
234 + // --- Main 函數 (與之前版本相同) ---
235 + func main() {
236 + proxy := NewProxyServer()
237 +
238 + log.Println("Performing initial cookie fetch...")
239 + if err := proxy.ensureValidCookies(); err != nil {
240 + log.Fatalf("Initial cookie fetch failed: %v. Server cannot start.", err)
241 + }
242 +
243 + http.Handle("/", proxy)
244 + log.Printf("Starting HLS proxy server on port %s", ServerPort)
245 + log.Printf("Access playlist at: http://localhost%s/playlist.m3u8", ServerPort)
246 + if err := http.ListenAndServe(ServerPort, nil); err != nil {
247 + log.Fatal("Server failed to start:", err)
248 + }
249 + }
Più nuovi Più vecchi