feat(fastestvpn): update servers data using API instead of zip file
- Add city filter - More dynamic to servers updates on fastestvpn's end - Update servers data
This commit is contained in:
129
internal/provider/fastestvpn/updater/api.go
Normal file
129
internal/provider/fastestvpn/updater/api.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/qdm12/gluetun/internal/provider/common"
|
||||
)
|
||||
|
||||
type apiServer struct {
|
||||
country string
|
||||
city string
|
||||
hostname string
|
||||
}
|
||||
|
||||
var (
|
||||
ErrDataMalformed = errors.New("data is malformed")
|
||||
)
|
||||
|
||||
const apiURL = "https://support.fastestvpn.com/wp-admin/admin-ajax.php"
|
||||
|
||||
// The API URL and requests are shamelessly taken from network operations
|
||||
// done on the page https://support.fastestvpn.com/vpn-servers/
|
||||
func fetchAPIServers(ctx context.Context, client *http.Client, protocol string) (
|
||||
servers []apiServer, err error) {
|
||||
form := url.Values{
|
||||
"action": []string{"vpn_servers"},
|
||||
"protocol": []string{protocol},
|
||||
}
|
||||
body := strings.NewReader(form.Encode())
|
||||
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
// request.Header.Set("User-Agent", "curl/8.9.0")
|
||||
// request.Header.Set("Accept", "*/*")
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sending request: %w", err)
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
_ = response.Body.Close()
|
||||
return nil, fmt.Errorf("%w: %d", common.ErrHTTPStatusCodeNotOK, response.StatusCode)
|
||||
}
|
||||
|
||||
data, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
_ = response.Body.Close()
|
||||
return nil, fmt.Errorf("reading response body: %w", err)
|
||||
}
|
||||
|
||||
err = response.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("closing response body: %w", err)
|
||||
}
|
||||
|
||||
const usualMaxNumber = 100
|
||||
servers = make([]apiServer, 0, usualMaxNumber)
|
||||
|
||||
for {
|
||||
trBlock := getNextTRBlock(data)
|
||||
if trBlock == nil {
|
||||
break
|
||||
}
|
||||
data = data[len(trBlock):]
|
||||
|
||||
var server apiServer
|
||||
|
||||
const numberOfTDBlocks = 3
|
||||
for i := 0; i < numberOfTDBlocks; i++ {
|
||||
tdBlock := getNextTDBlock(trBlock)
|
||||
if tdBlock == nil {
|
||||
return nil, fmt.Errorf("%w: expected 3 <td> blocks in <tr> block %q",
|
||||
ErrDataMalformed, string(trBlock))
|
||||
}
|
||||
trBlock = trBlock[len(tdBlock):]
|
||||
|
||||
const startToken, endToken = "<td>", "</td>"
|
||||
tdBlockData := string(tdBlock[len(startToken) : len(tdBlock)-len(endToken)])
|
||||
const countryIndex, cityIndex, hostnameIndex = 0, 1, 2
|
||||
switch i {
|
||||
case countryIndex:
|
||||
server.country = tdBlockData
|
||||
case cityIndex:
|
||||
server.city = tdBlockData
|
||||
case hostnameIndex:
|
||||
server.hostname = tdBlockData
|
||||
}
|
||||
}
|
||||
servers = append(servers, server)
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
func getNextTRBlock(data []byte) (trBlock []byte) {
|
||||
const startToken, endToken = "<tr>", "</tr>"
|
||||
return getNextBlock(data, startToken, endToken)
|
||||
}
|
||||
|
||||
func getNextTDBlock(data []byte) (tdBlock []byte) {
|
||||
const startToken, endToken = "<td>", "</td>"
|
||||
return getNextBlock(data, startToken, endToken)
|
||||
}
|
||||
|
||||
func getNextBlock(data []byte, startToken, endToken string) (nextBlock []byte) {
|
||||
i := bytes.Index(data, []byte(startToken))
|
||||
if i == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
nextBlock = data[i:]
|
||||
i = bytes.Index(nextBlock[len(startToken):], []byte(endToken))
|
||||
if i == -1 {
|
||||
return nil
|
||||
}
|
||||
nextBlock = nextBlock[:i+len(startToken)+len(endToken)]
|
||||
return nextBlock
|
||||
}
|
||||
Reference in New Issue
Block a user