Files

200 lines
3.8 KiB
Go

package transportcfg
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"os"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"time"
)
type ConfigDiff struct {
Added int
Removed int
Changed int
}
func SingBoxTypedProtocolSupported(proto string) bool {
switch strings.ToLower(strings.TrimSpace(proto)) {
case "vless", "trojan", "shadowsocks", "wireguard", "hysteria2", "tuic":
return true
default:
return false
}
}
func ParsePort(v any) (int, bool) {
switch x := v.(type) {
case int:
return x, true
case int64:
return int(x), true
case float64:
return int(x), true
case string:
s := strings.TrimSpace(x)
if s == "" {
return 0, false
}
n, err := strconv.Atoi(s)
if err != nil {
return 0, false
}
return n, true
default:
return 0, false
}
}
func DigestJSONMap(config map[string]any) string {
if config == nil {
return ""
}
b, err := json.Marshal(config)
if err != nil {
return ""
}
h := sha256.Sum256(b)
return hex.EncodeToString(h[:])
}
func DiffConfigMaps(prev, next map[string]any) (ConfigDiff, bool) {
diff := ConfigDiff{}
changed := false
if prev == nil {
diff.Added = len(next)
return diff, diff.Added > 0
}
seen := map[string]struct{}{}
for k, pv := range prev {
seen[k] = struct{}{}
nv, ok := next[k]
if !ok {
diff.Removed++
changed = true
continue
}
if !reflect.DeepEqual(pv, nv) {
diff.Changed++
changed = true
}
}
for k := range next {
if _, ok := seen[k]; ok {
continue
}
diff.Added++
changed = true
}
return diff, changed
}
func ProfileConfigPath(rootDir, profileID string, sanitizeID func(string) string) string {
id := profileID
if sanitizeID != nil {
id = sanitizeID(id)
}
if strings.TrimSpace(id) == "" {
id = "profile"
}
return filepath.Join(strings.TrimSpace(rootDir), id+".json")
}
func WriteJSONConfigFile(path string, config map[string]any) error {
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
tmp := path + ".tmp"
if err := os.WriteFile(tmp, append(data, '\n'), 0o644); err != nil {
return err
}
return os.Rename(tmp, path)
}
func ReadJSONMapFile(path string) map[string]any {
data, err := os.ReadFile(path)
if err != nil {
return nil
}
var out map[string]any
if err := json.Unmarshal(data, &out); err != nil {
return nil
}
return out
}
func ReadFileOptional(path string) ([]byte, bool, error) {
data, err := os.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
return nil, false, nil
}
return nil, false, err
}
return data, true, nil
}
func RestoreFileOptional(path string, data []byte, exists bool) error {
if !exists {
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
tmp := path + ".tmp"
if err := os.WriteFile(tmp, data, 0o644); err != nil {
return err
}
return os.Rename(tmp, path)
}
func SanitizeHistoryStamp(ts string, now time.Time) string {
s := strings.TrimSpace(ts)
if s == "" {
s = now.UTC().Format(time.RFC3339Nano)
}
repl := strings.NewReplacer(":", "", "-", "", "T", "_", "Z", "", ".", "")
out := repl.Replace(s)
out = strings.Trim(out, "_")
if out == "" {
out = strconv.FormatInt(now.UTC().UnixNano(), 10)
}
return out
}
func JoinMessages(messages []string) string {
if len(messages) == 0 {
return ""
}
parts := make([]string, 0, len(messages))
for _, msg := range messages {
v := strings.TrimSpace(msg)
if v == "" {
continue
}
parts = append(parts, v)
}
return strings.Join(parts, "; ")
}
func SortRecordsDescByAt[T any](items []T, extractAt func(T) string) {
if len(items) < 2 || extractAt == nil {
return
}
sort.Slice(items, func(i, j int) bool {
return extractAt(items[i]) > extractAt(items[j])
})
}