mirror of
https://github.com/qdm12/gluetun.git
synced 2026-08-11 14:52:56 +02:00
expressvpn wip
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/constants/vpn"
|
||||
"github.com/qdm12/gluetun/internal/models"
|
||||
htmlutils "github.com/qdm12/gluetun/internal/updater/html"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func fetchServersFromWebsite(ctx context.Context, client *http.Client) (
|
||||
servers []models.Server, warnings []string, err error,
|
||||
) {
|
||||
const url = "https://www.expressvpn.com/vpn-server"
|
||||
rootNode, err := htmlutils.Fetch(ctx, client, url)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("fetching HTML code: %w", err)
|
||||
}
|
||||
|
||||
servers, warnings = parseServerListTable(rootNode)
|
||||
|
||||
return servers, warnings, nil
|
||||
}
|
||||
|
||||
func parseServerListTable(rootNode *html.Node) (
|
||||
servers []models.Server, warnings []string,
|
||||
) {
|
||||
// Collect all tr nodes from all tbody elements using recursive walk
|
||||
var allRows []*html.Node
|
||||
var walk func(n *html.Node)
|
||||
walk = func(n *html.Node) {
|
||||
if n.Data == "tr" {
|
||||
allRows = append(allRows, n)
|
||||
return
|
||||
}
|
||||
for child := n.FirstChild; child != nil; child = child.NextSibling {
|
||||
walk(child)
|
||||
}
|
||||
}
|
||||
walk(rootNode)
|
||||
|
||||
seenLocations := make(map[string]struct{})
|
||||
for _, trNode := range allRows {
|
||||
serverLocation := strings.TrimSpace(htmlutils.Attribute(trNode, "data-server-location"))
|
||||
if serverLocation == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
country, city, number, err := parseLocation(serverLocation)
|
||||
if err != nil {
|
||||
warnings = append(warnings, fmt.Sprintf("parsing location: %s", err))
|
||||
continue
|
||||
}
|
||||
server := models.Server{
|
||||
VPN: vpn.OpenVPN,
|
||||
TCP: true,
|
||||
UDP: true,
|
||||
Country: country,
|
||||
City: city,
|
||||
Number: number,
|
||||
}
|
||||
|
||||
locationKey := fmt.Sprintf("%s-%s-%d", country, city, number)
|
||||
if _, ok := seenLocations[locationKey]; ok {
|
||||
continue
|
||||
}
|
||||
seenLocations[locationKey] = struct{}{}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
return servers, warnings
|
||||
}
|
||||
|
||||
func parseLocation(location string) (country, city string, number uint16, err error) {
|
||||
// Location formats:
|
||||
// - "Argentina" -> country="Argentina", city="", number=0
|
||||
// - "Australia - Adelaide" -> country="Australia", city="Adelaide", number=0
|
||||
// - "Canada - Toronto" -> country="Canada", city="Toronto", number=0
|
||||
parts := strings.Split(location, " - ")
|
||||
country = strings.TrimSpace(parts[0])
|
||||
switch len(parts) {
|
||||
case 1: // country only
|
||||
if strings.Contains(country, " [") {
|
||||
// This can be "country [city]"
|
||||
parts := strings.SplitN(country, " [", 2)
|
||||
country = strings.TrimSpace(parts[0])
|
||||
city = strings.TrimSuffix(strings.TrimSpace(parts[1]), "]")
|
||||
}
|
||||
case 2: // country and city
|
||||
city = strings.TrimSpace(parts[1])
|
||||
case 3: // country, city, and number
|
||||
city = strings.TrimSpace(parts[1])
|
||||
fmt.Sscanf(strings.TrimSpace(parts[2]), "%d", &number)
|
||||
default:
|
||||
return "", "", 0, fmt.Errorf("invalid location format: %q", location)
|
||||
}
|
||||
|
||||
// Retro-compatibility transforms
|
||||
switch strings.ToLower(country) {
|
||||
case "united states":
|
||||
country = "USA"
|
||||
case "united kingdom":
|
||||
country = "UK"
|
||||
}
|
||||
|
||||
return country, city, number, nil
|
||||
}
|
||||
Reference in New Issue
Block a user