mirror of
https://github.com/qdm12/gluetun.git
synced 2025-12-10 10:45:38 -06:00
- `OPENVPN_ENCRYPTED_KEY` environment variable - `OPENVPN_ENCRYPTED_KEY_SECRETFILE` environment variable - `OPENVPN_KEY_PASSPHRASE` environment variable - `OPENVPN_KEY_PASSPHRASE_SECRETFILE` environment variable - `PREMIUM_ONLY` environment variable - OpenVPN user and password not required for vpnsecure provider
150 lines
3.7 KiB
Go
150 lines
3.7 KiB
Go
package updater
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"regexp"
|
|
|
|
htmlutils "github.com/qdm12/gluetun/internal/updater/html"
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
func fetchServers(ctx context.Context, client *http.Client) (
|
|
hostToData map[string]serverData, err error) {
|
|
const url = "https://www.slickvpn.com/locations/"
|
|
rootNode, err := htmlutils.Fetch(ctx, client, url)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("fetching HTML code: %w", err)
|
|
}
|
|
|
|
hostToData, err = parseHTML(rootNode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing HTML: %w", err)
|
|
}
|
|
|
|
return hostToData, nil
|
|
}
|
|
|
|
type serverData struct {
|
|
ovpnURL string
|
|
country string
|
|
region string
|
|
city string
|
|
}
|
|
|
|
var (
|
|
ErrLocationTableNotFound = errors.New("HTML location table node not found")
|
|
ErrTbodyNotFound = errors.New("HTML tbody node not found")
|
|
ErrExtractOpenVPNURL = errors.New("failed extracting OpenVPN URL")
|
|
)
|
|
|
|
func parseHTML(rootNode *html.Node) (hostToData map[string]serverData, err error) {
|
|
locationTableNode := htmlutils.BFS(rootNode, matchLocationTable)
|
|
if locationTableNode == nil {
|
|
return nil, htmlutils.WrapError(ErrLocationTableNotFound, rootNode)
|
|
}
|
|
|
|
tBodyNode := htmlutils.BFS(locationTableNode, matchTbody)
|
|
if tBodyNode == nil {
|
|
return nil, htmlutils.WrapError(ErrTbodyNotFound, rootNode)
|
|
}
|
|
|
|
rowNodes := htmlutils.DirectChildren(tBodyNode, matchTr)
|
|
hostToData = make(map[string]serverData, len(rowNodes))
|
|
|
|
for _, rowNode := range rowNodes {
|
|
hostname, data, err := parseRowNode(rowNode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing row node: %w", err)
|
|
}
|
|
hostToData[hostname] = data
|
|
}
|
|
|
|
return hostToData, nil
|
|
}
|
|
|
|
func parseRowNode(rowNode *html.Node) (hostname string, data serverData, err error) {
|
|
columnIndex := 0
|
|
const (
|
|
columnIndexContinent = 0
|
|
columnIndexCountry = 1
|
|
columnIndexCity = 2
|
|
columnIndexConfig = 3
|
|
)
|
|
for cellNode := rowNode.FirstChild; cellNode != nil; cellNode = cellNode.NextSibling {
|
|
if cellNode.FirstChild == nil {
|
|
continue
|
|
}
|
|
|
|
switch columnIndex {
|
|
case columnIndexContinent:
|
|
data.region = cellNode.FirstChild.Data
|
|
case columnIndexCountry:
|
|
data.country = cellNode.FirstChild.Data
|
|
case columnIndexCity:
|
|
data.city = cellNode.FirstChild.Data
|
|
case columnIndexConfig:
|
|
linkNodes := htmlutils.DirectChildren(cellNode, matchA)
|
|
for _, linkNode := range linkNodes {
|
|
if linkNode.FirstChild.Data != "OpenVPN" {
|
|
continue
|
|
}
|
|
|
|
data.ovpnURL = htmlutils.Attribute(linkNode, "href")
|
|
if data.ovpnURL == "" {
|
|
return "", data, htmlutils.WrapError(ErrExtractOpenVPNURL, linkNode)
|
|
}
|
|
|
|
hostname, err = extractHostnameFromURL(data.ovpnURL)
|
|
if err != nil {
|
|
return "", data, fmt.Errorf("extracting hostname from url: %w", err)
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
columnIndex++
|
|
if columnIndex == columnIndexConfig+1 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return hostname, data, nil
|
|
}
|
|
|
|
func matchLocationTable(rootNode *html.Node) (match bool) {
|
|
return htmlutils.MatchID("location-table")(rootNode)
|
|
}
|
|
|
|
func matchTbody(locationTableNode *html.Node) (match bool) {
|
|
return htmlutils.MatchData("tbody")(locationTableNode)
|
|
}
|
|
|
|
func matchTr(tbodyNode *html.Node) (match bool) {
|
|
return htmlutils.MatchData("tr")(tbodyNode)
|
|
}
|
|
|
|
func matchA(cellNode *html.Node) (match bool) {
|
|
return htmlutils.MatchData("a")(cellNode)
|
|
}
|
|
|
|
var serverNameRegex = regexp.MustCompile(`^.+\/(?P<serverName>.+)\.ovpn$`)
|
|
|
|
var (
|
|
ErrExtractHostnameFromURL = errors.New("cannot extract hostname from url")
|
|
)
|
|
|
|
func extractHostnameFromURL(url string) (hostname string, err error) {
|
|
matches := serverNameRegex.FindStringSubmatch(url)
|
|
const minMatches = 2
|
|
if len(matches) < minMatches {
|
|
return "", fmt.Errorf("%w: %s has less than 2 matches for %s",
|
|
ErrExtractHostnameFromURL, url, serverNameRegex)
|
|
}
|
|
hostname = matches[1]
|
|
return hostname, nil
|
|
}
|