Ultima attività 1 month ago

香港商业电台转发直播流

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

Nessuna modifica

imyip's Avatar imyip ha revisionato questo gist 11 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