penguin/golifehk

@golifehk bot in Telegram

commit cda11cef2e69305e83e0e4ace8d54b87d3d6eff7

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-02-27T06:43:02Z
subjectMimic browser requests because kmb blocked Go httpGet clients
commit cda11cef2e69305e83e0e4ace8d54b87d3d6eff7
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-02-27T06:43:02Z

    Mimic browser requests because kmb blocked Go httpGet clients
---
 datasources/kmb/routestops.go | 258 ++++++++++++++++++++----------------------
 utils/shortcuts.go            |  27 +++++
 2 files changed, 152 insertions(+), 133 deletions(-)

diff --git a/datasources/kmb/routestops.go b/datasources/kmb/routestops.go
index 26b97f4..b5bb91d 100644
--- a/datasources/kmb/routestops.go
+++ b/datasources/kmb/routestops.go
@@ -1,147 +1,139 @@
 package kmb
 
 import (
-    "bytes"
-    "encoding/json"
-    "fmt"
-    "io"
-    "net/http"
-    "path/filepath"
-    // "strings"
-
-    query "github.com/tgckpg/golifehk/query"
-    "github.com/tgckpg/golifehk/utils"
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"log"
+	"path/filepath"
+
+	query "github.com/tgckpg/golifehk/query"
+	"github.com/tgckpg/golifehk/utils"
 )
 
-var JSON_ROUTESTOPS string = filepath.Join( utils.WORKDIR, "kmb-routestops.json" )
-var JSON_BUSSTOPS string = filepath.Join( utils.WORKDIR, "kmb-busstops.json" )
-
-func readRouteStopsData( busStops *map[string] *BusStop, buff *bytes.Buffer ) ( *[] *RouteStop, error ) {
-
-    routeStopsData := RouteStops{}
-    err := json.Unmarshal( buff.Bytes(), &routeStopsData )
-    if err != nil {
-        return nil, err
-    }
-
-    // routeStops[ Route ][ ServiceType ][ Direction ][ Seq ] = RouteStop
-    routeStops := map[string] *map[string] *map[ string ] *map[ int ] *RouteStop{}
-    allRouteStops := [] *RouteStop{}
-
-    for _, entry := range routeStopsData.RouteStops {
-
-        busStop := (*busStops)[ entry.StationId ]
-        if busStop == nil {
-            busStop = &BusStop {
-                BusStopId: entry.StationId,
-                Name_en: "???", Name_tc: "???", Name_sc: "???",
-            }
-            busStop.Reload()
-
-            (*busStops)[ entry.StationId ] = busStop
-        }
-
-        if busStop.Routes == nil {
-            busStopRoutes := [] *RouteStop{}
-            busStop.Routes = &busStopRoutes
-        }
-
-        (*busStop.Routes) = append( (*busStop.Routes), entry )
-        entry.BusStop = busStop
-
-        route := routeStops[ entry.RouteId ]
-        if route == nil {
-            route = &map[string] *map[ string ] *map[ int ] *RouteStop{} 
-            routeStops[ entry.RouteId ] = route
-        }
-
-        service := (*route)[ entry.ServiceType ]
-        if service == nil {
-            service = &map[ string ] *map[ int ] *RouteStop{}
-            (*route)[ entry.ServiceType ] = service
-        }
-
-        direction := (*service)[ entry.Direction ]
-        if direction == nil {
-            direction = &map[ int ] *RouteStop{}
-            (*service)[ entry.Direction ] = direction
-        }
-        entry.RouteStops = direction
-
-        seq := (*direction)[ entry.StationSeq ]
-        if seq == nil {
-            (*direction)[ entry.StationSeq ] = entry
-        }
-        allRouteStops = append( allRouteStops, entry )
-    }
-
-    return &allRouteStops, nil
+var JSON_ROUTESTOPS string = filepath.Join(utils.WORKDIR, "kmb-routestops.json")
+var JSON_BUSSTOPS string = filepath.Join(utils.WORKDIR, "kmb-busstops.json")
+
+func readRouteStopsData(busStops *map[string]*BusStop, buff *bytes.Buffer) (*[]*RouteStop, error) {
+
+	routeStopsData := RouteStops{}
+	err := json.Unmarshal(buff.Bytes(), &routeStopsData)
+	if err != nil {
+		return nil, err
+	}
+
+	// routeStops[ Route ][ ServiceType ][ Direction ][ Seq ] = RouteStop
+	routeStops := map[string]*map[string]*map[string]*map[int]*RouteStop{}
+	allRouteStops := []*RouteStop{}
+
+	for _, entry := range routeStopsData.RouteStops {
+
+		busStop := (*busStops)[entry.StationId]
+		if busStop == nil {
+			busStop = &BusStop{
+				BusStopId: entry.StationId,
+				Name_en:   "???", Name_tc: "???", Name_sc: "???",
+			}
+			busStop.Reload()
+
+			(*busStops)[entry.StationId] = busStop
+		}
+
+		if busStop.Routes == nil {
+			busStopRoutes := []*RouteStop{}
+			busStop.Routes = &busStopRoutes
+		}
+
+		(*busStop.Routes) = append((*busStop.Routes), entry)
+		entry.BusStop = busStop
+
+		route := routeStops[entry.RouteId]
+		if route == nil {
+			route = &map[string]*map[string]*map[int]*RouteStop{}
+			routeStops[entry.RouteId] = route
+		}
+
+		service := (*route)[entry.ServiceType]
+		if service == nil {
+			service = &map[string]*map[int]*RouteStop{}
+			(*route)[entry.ServiceType] = service
+		}
+
+		direction := (*service)[entry.Direction]
+		if direction == nil {
+			direction = &map[int]*RouteStop{}
+			(*service)[entry.Direction] = direction
+		}
+		entry.RouteStops = direction
+
+		seq := (*direction)[entry.StationSeq]
+		if seq == nil {
+			(*direction)[entry.StationSeq] = entry
+		}
+		allRouteStops = append(allRouteStops, entry)
+	}
+
+	return &allRouteStops, nil
 }
 
-func readBusStopsData( buff *bytes.Buffer ) ( *map[string]*BusStop, error ) {
-    busStopsData := BusStops{}
-    err := json.Unmarshal( buff.Bytes(), &busStopsData )
-    if err != nil {
-        return nil, err
-    }
+func readBusStopsData(buff *bytes.Buffer) (*map[string]*BusStop, error) {
+	busStopsData := BusStops{}
+	err := json.Unmarshal(buff.Bytes(), &busStopsData)
+	if err != nil {
+		return nil, err
+	}
 
-    busStopMap := map[string] *BusStop{}
-    for _, entry := range busStopsData.BusStops {
+	busStopMap := map[string]*BusStop{}
+	for _, entry := range busStopsData.BusStops {
 
-        entry.Reload()
+		entry.Reload()
 
-        if _, ok := busStopMap[ entry.BusStopId ]; ok {
-            return nil, fmt.Errorf( "Duplicated BusStop: %s", entry.BusStopId )
-        }
+		if _, ok := busStopMap[entry.BusStopId]; ok {
+			return nil, fmt.Errorf("Duplicated BusStop: %s", entry.BusStopId)
+		}
 
-        busStopMap[ entry.BusStopId ] = entry
-    }
-    return &busStopMap, nil
+		busStopMap[entry.BusStopId] = entry
+	}
+	return &busStopMap, nil
 }
 
-func getRouteStops() (*[] query.ISearchable, error) {
-
-    QUERY_FUNC := func() ( io.ReadCloser, error ) {
-        resp, err := http.Get( "https://data.etabus.gov.hk/v1/transport/kmb/stop" )
-        if err != nil {
-            return nil, err
-        }
-        return resp.Body, nil
-    }
-
-    buff, err := utils.CacheStream( JSON_BUSSTOPS, QUERY_FUNC, 7 * 24 * 3600 )
-    if err != nil {
-        return nil, err
-    }
-
-    busStopMap, err := readBusStopsData( buff )
-    if err != nil {
-        return nil, err
-    }
-
-    QUERY_FUNC = func() ( io.ReadCloser, error ) {
-        resp, err := http.Get( "https://data.etabus.gov.hk/v1/transport/kmb/route-stop" )
-        if err != nil {
-            return nil, err
-        }
-        return resp.Body, nil
-    }
-
-    buff, err = utils.CacheStream( JSON_ROUTESTOPS, QUERY_FUNC, 7 * 24 * 3600 )
-    if err != nil {
-        return nil, err
-    }
-
-    routeStops, err := readRouteStopsData( busStopMap, buff )
-    if err != nil {
-        return nil, err
-    }
-
-    searchables := [] query.ISearchable{}
-    for _, routeStop := range *routeStops {
-        searchables = append( searchables, routeStop )
-        routeStop.Reload()
-    }
-
-    return &searchables, nil
+func getRouteStops() (*[]query.ISearchable, error) {
+
+	QUERY_FUNC := func() (io.ReadCloser, error) {
+		return utils.HttpGet("https://data.etabus.gov.hk/v1/transport/kmb/stop")
+	}
+
+	buff, err := utils.CacheStream(JSON_BUSSTOPS, QUERY_FUNC, 7*24*3600)
+	if err != nil {
+		return nil, err
+	}
+
+	busStopMap, err := readBusStopsData(buff)
+	if err != nil {
+		return nil, err
+	}
+
+	QUERY_FUNC = func() (io.ReadCloser, error) {
+		return utils.HttpGet("https://data.etabus.gov.hk/v1/transport/kmb/route-stop")
+	}
+
+	buff, err = utils.CacheStream(JSON_ROUTESTOPS, QUERY_FUNC, 7*24*3600)
+	if err != nil {
+		return nil, err
+	}
+
+	routeStops, err := readRouteStopsData(busStopMap, buff)
+	if err != nil {
+		log.Printf("Unable to parse RouteStopsData: %s", err)
+		return nil, err
+	}
+
+	searchables := []query.ISearchable{}
+	for _, routeStop := range *routeStops {
+		searchables = append(searchables, routeStop)
+		routeStop.Reload()
+	}
+
+	return &searchables, nil
 }
diff --git a/utils/shortcuts.go b/utils/shortcuts.go
index be95073..7aa2ab1 100644
--- a/utils/shortcuts.go
+++ b/utils/shortcuts.go
@@ -2,9 +2,12 @@ package utils
 
 import (
 	"bytes"
+	"io"
+	"net/http"
 	"os"
 	"strconv"
 	"strings"
+	"time"
 )
 
 var MARKDOWN_ESC []string = []string{"_", "*", "[", "]", "(", ")", "~", "`", ">", "#", "+", "-", "=", "|", "{", "}", ".", "!"}
@@ -117,3 +120,27 @@ func TryGetEnv[T any](name string, fallback T) T {
 
 	return fallback
 }
+
+func HttpGet(url string) (io.ReadCloser, error) {
+	client := &http.Client{
+		Timeout: 15 * time.Second,
+	}
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
+	req.Header.Set("User-Agent", ua)
+	req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
+	req.Header.Set("Accept-Language", "en-US,en;q=0.9")
+	req.Header.Set("Connection", "keep-alive")
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	return resp.Body, nil
+}