Files
evening_detective_server/internal/modules/cudy/client_parser.go
T
2026-10-11 14:59:51 +07:00

124 lines
2.8 KiB
Go

package cudy
import (
"net"
"net/netip"
"sort"
"strconv"
"strings"
"golang.org/x/net/html"
)
func parseClients(data []byte) ([]Client, error) {
doc, err := html.Parse(strings.NewReader(string(data)))
if err != nil {
return nil, ErrRead
}
clients := []Client{}
var table *html.Node
walk(doc, func(n *html.Node) {
if n.Data == "th" && nodeText(n) == "IP / MAC Address" {
for parent := n.Parent; parent != nil; parent = parent.Parent {
if parent.Data == "table" {
table = parent
break
}
}
}
})
if table == nil {
return nil, ErrRead
}
invalid := false
seen := map[string]bool{}
walk(table, func(row *html.Node) {
if row.Type != html.ElementNode {
return
}
if row.Data != "tr" {
return
}
hasData := false
for child := row.FirstChild; child != nil; child = child.NextSibling {
hasData = hasData || child.Data == "td"
}
if !hasData {
return
}
if !strings.HasPrefix(attr(row, "id"), "cbi-table-") {
invalid = true
return
}
fields := map[string]*html.Node{}
prefix := attr(row, "id") + "-"
walk(row, func(n *html.Node) {
if n.Data == "div" && strings.HasPrefix(attr(n, "id"), prefix) {
fields[strings.TrimPrefix(attr(n, "id"), prefix)] = n
}
})
var band string
wired := false
if hostname := fields["hostname"]; hostname != nil {
walk(hostname, func(n *html.Node) {
if n.Data == "span" {
label := nodeText(n)
wired = wired || label == "Ethernet"
if label == "2.4G WiFi" {
band = "2.4 GHz"
}
if label == "5G WiFi" {
band = "5 GHz"
}
}
})
}
// shortcut: stock TR3000 2.5.27 DOM and two bands; update parser fixtures for other firmware.
if band == "" {
if !wired {
invalid = true
}
return
}
client := Client{Band: band}
if field := fields["ipmac"]; field != nil {
walk(field, func(n *html.Node) {
if n.Data != "p" || client.MAC != "" {
return
}
for _, value := range strings.Fields(nodeText(n)) {
if ip, e := netip.ParseAddr(value); e == nil {
client.IP = ip.String()
}
if mac, e := net.ParseMAC(value); e == nil && len(mac) == 6 {
client.MAC = strings.ToUpper(mac.String())
}
}
})
}
if field := fields["signal"]; field != nil {
walk(field, func(n *html.Node) {
if n.Data == "p" && client.Signal == nil {
parts := strings.Fields(nodeText(n))
if len(parts) == 2 && parts[1] == "dBm" {
if value, e := strconv.Atoi(parts[0]); e == nil && value >= -120 && value <= 0 {
client.Signal = &value
}
}
}
})
}
if client.MAC == "" || seen[client.MAC] {
invalid = true
return
}
seen[client.MAC] = true
clients = append(clients, client)
})
if invalid || len(clients) > 1024 {
return nil, ErrRead
}
sort.Slice(clients, func(i, j int) bool { return clients[i].MAC < clients[j].MAC })
return clients, nil
}