Files

148 lines
3.2 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package sub
import (
"bufio"
"context"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/samosvalishe/free-turn-proxy/internal/client/dnsdial"
"github.com/samosvalishe/free-turn-proxy/internal/logx"
"github.com/samosvalishe/free-turn-proxy/internal/uri"
)
var logHolder logx.Holder
func SetLogger(l logx.Logger) { logHolder.Set(l) }
func log() logx.Logger { return logHolder.Get() }
// Sub представляет структуру подписки на серверы.
type Sub struct {
Name string
Update string
Refresh string
Color string
Icon string
Used string
Available string
Nodes []Node
}
// Node представляет узел сервера из подписки.
type Node struct {
URI *uri.Config
Name string
Color string
Icon string
Used string
Available string
IP string
Comment string
}
func Fetch(ctx context.Context, url string) (*Sub, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
dialer := dnsdial.AppDialer(dnsdial.DNSModeAuto)
// Клон DefaultTransport, а не пустой: иначе теряются системный прокси и таймауты TLS.
tr := &http.Transport{}
if dt, ok := http.DefaultTransport.(*http.Transport); ok {
tr = dt.Clone()
}
tr.DialContext = dialer.DialContext
client := &http.Client{Transport: tr}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
return Parse(resp.Body)
}
func Parse(r io.Reader) (*Sub, error) {
s := &Sub{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
switch {
case strings.HasPrefix(line, "##"):
if key, val, ok := metaField(line, "##"); ok && len(s.Nodes) > 0 {
s.Nodes[len(s.Nodes)-1].setMeta(key, val)
}
case strings.HasPrefix(line, "#"):
if key, val, ok := metaField(line, "#"); ok {
s.setMeta(key, val)
}
case strings.HasPrefix(line, "freeturn://"):
cfg, err := uri.Parse(line)
if err != nil {
log().Warnf("[Sub] skipped invalid freeturn URI: %v", err)
continue
}
s.Nodes = append(s.Nodes, Node{URI: cfg})
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return s, nil
}
func metaField(line, prefix string) (key, val string, ok bool) {
key, val, ok = strings.Cut(strings.TrimPrefix(line, prefix), ":")
return strings.TrimSpace(key), strings.TrimSpace(val), ok
}
func (s *Sub) setMeta(key, val string) {
switch key {
case "name":
s.Name = val
case "update":
s.Update = val
case "refresh":
s.Refresh = val
case "color":
s.Color = val
case "icon":
s.Icon = val
case "used":
s.Used = val
case "available":
s.Available = val
}
}
func (n *Node) setMeta(key, val string) {
switch key {
case "name":
n.Name = val
case "color":
n.Color = val
case "icon":
n.Icon = val
case "used":
n.Used = val
case "available":
n.Available = val
case "ip":
n.IP = val
case "comment":
n.Comment = val
}
}