3. Парсинг ссылок и генерация конфига (vibe-kanban 09e20645)

Реализуй функцию, которая парсит ссылку `vless://`.

1. Извлеки: UUID, адрес сервера, порт, тип передачи (TCP), и SNI (если есть) и другие данные.
2. Сгенерируй JSON-конфиг в файл config.json специально в формате **Sing-box** (не Xray!).
3. В секции `outbounds` укажи `type: vless`.
4. Обязательно добавь секцию `v2ray-transport` внутри `outbound`, если на сервере 3X-UI используются специфические настройки транспорта.
5. Настрой `inbound` на `mixed` (SOCKS5 + HTTP) на порту 1080, чтобы через это ядро мог ходить любой трафик Windows.
This commit is contained in:
Vibe Kanban
2026-01-16 19:24:58 +03:00
parent 5b0affda59
commit 2704069be1
4 changed files with 330 additions and 5 deletions

BIN
hamy-vpn-client-final.exe Normal file

Binary file not shown.

BIN
hamy-vpn-client-new.exe Normal file

Binary file not shown.

304
main.go
View File

@@ -1,14 +1,17 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"image/color" "image/color"
"log"
"net/url" "net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv"
"strings" "strings"
"syscall" "syscall"
@@ -91,6 +94,21 @@ func removeConfig(index int) {
} }
} }
// generateAndRunSingBox generates a Sing-box config from VLESS URL and runs sing-box
func generateAndRunSingBox(vlessURL string) error {
// Generate unique config filename based on timestamp or hash
configPath := "config.json"
// Generate the Sing-box configuration from the VLESS URL
err := generateConfigFromVLESSURL(vlessURL, configPath)
if err != nil {
return fmt.Errorf("failed to generate config: %v", err)
}
// Run sing-box with the generated config
return runSingBox(configPath)
}
// runSingBox starts the sing-box process with the given config path // runSingBox starts the sing-box process with the given config path
func runSingBox(configPath string) error { func runSingBox(configPath string) error {
singBoxPath := filepath.Join("bin", "sing-box.exe") singBoxPath := filepath.Join("bin", "sing-box.exe")
@@ -123,13 +141,274 @@ func killCurrentProcess() {
} }
} }
// VLESSConfig represents parsed VLESS URL data
type VLESSConfig struct {
UUID string
ServerAddr string
ServerPort int
Type string
SNI string
Network string
Path string
Host string
Fingerprint string
Flow string
AllowInsecure bool
}
// parseVLESS parses a VLESS URL and extracts relevant information
func parseVLESS(vlessURL string) (*VLESSConfig, error) {
u, err := url.Parse(vlessURL)
if err != nil {
return nil, err
}
// Extract UUID from the user part of the URL
uuid := u.User.Username()
// Extract server address and port
serverAddr := u.Hostname()
portStr := u.Port()
if portStr == "" {
return nil, fmt.Errorf("port not specified in VLESS URL")
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, err
}
// Parse query parameters
params := u.Query()
vlessConfig := &VLESSConfig{
UUID: uuid,
ServerAddr: serverAddr,
ServerPort: port,
Type: params.Get("type"),
SNI: params.Get("sni"),
Network: params.Get("network"),
Path: params.Get("path"),
Host: params.Get("host"),
Fingerprint: params.Get("fp"),
Flow: params.Get("flow"),
AllowInsecure: params.Get("allowInsecure") == "true",
}
// Set default network type if not specified
if vlessConfig.Network == "" {
vlessConfig.Network = "tcp"
}
return vlessConfig, nil
}
// SingBoxOutbound represents an outbound configuration for Sing-box
type SingBoxOutbound struct {
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
Server string `json:"server"`
ServerPort int `json:"server_port"`
UUID string `json:"uuid"`
Flow string `json:"flow,omitempty"`
Network string `json:"network,omitempty"`
TLS *SingBoxTLS `json:"tls,omitempty"`
Transport map[string]interface{} `json:"transport,omitempty"`
V2RayTransport *V2RayTransport `json:"v2ray_transport,omitempty"`
}
// SingBoxTLS represents TLS configuration for Sing-box
type SingBoxTLS struct {
Enabled bool `json:"enabled"`
ServerName string `json:"server_name,omitempty"`
Insecure bool `json:"insecure,omitempty"`
Fingerprint string `json:"fingerprint,omitempty"`
UTLS *UTLS `json:"utls,omitempty"`
}
// UTLS represents uTLS configuration
type UTLS struct {
Enabled bool `json:"enabled"`
Fingerprint string `json:"fingerprint"`
}
// V2RayTransport represents V2Ray transport configuration
type V2RayTransport struct {
Type string `json:"type"`
Headers map[string]string `json:"headers,omitempty"`
Host string `json:"host,omitempty"`
Method string `json:"method,omitempty"`
Path string `json:"path,omitempty"`
ServiceName string `json:"service_name,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// SingBoxInbound represents an inbound configuration for Sing-box
type SingBoxInbound struct {
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
Listen string `json:"listen"`
ListenPort int `json:"listen_port"`
SetSystemProxy bool `json:"set_system_proxy,omitempty"`
}
// SingBoxConfig represents the full Sing-box configuration
type SingBoxConfig struct {
Log map[string]interface{} `json:"log"`
Inbounds []SingBoxInbound `json:"inbounds"`
Outbounds []SingBoxOutbound `json:"outbounds"`
}
// generateSingBoxConfig generates a Sing-box configuration from VLESSConfig
func generateSingBoxConfig(vlessConfig *VLESSConfig, configPath string) error {
// Create TLS configuration if SNI is present
var tlsConfig *SingBoxTLS
if vlessConfig.SNI != "" || vlessConfig.Fingerprint != "" {
tlsConfig = &SingBoxTLS{
Enabled: true,
ServerName: vlessConfig.SNI,
Insecure: vlessConfig.AllowInsecure,
}
if vlessConfig.Fingerprint != "" {
tlsConfig.Fingerprint = vlessConfig.Fingerprint
// Enable uTLS if fingerprint is specified
tlsConfig.UTLS = &UTLS{
Enabled: true,
Fingerprint: vlessConfig.Fingerprint,
}
}
}
// Create V2Ray transport if specific transport settings are present
var v2rayTransport *V2RayTransport
if vlessConfig.Path != "" || vlessConfig.Host != "" {
v2rayTransport = &V2RayTransport{
Type: vlessConfig.Network,
}
if vlessConfig.Path != "" {
v2rayTransport.Path = vlessConfig.Path
}
if vlessConfig.Host != "" {
v2rayTransport.Host = vlessConfig.Host
}
// For HTTP headers
if vlessConfig.Host != "" {
v2rayTransport.Headers = map[string]string{
"Host": vlessConfig.Host,
}
}
}
// Create the outbound configuration
outbound := SingBoxOutbound{
Type: "vless",
Server: vlessConfig.ServerAddr,
ServerPort: vlessConfig.ServerPort,
UUID: vlessConfig.UUID,
Flow: vlessConfig.Flow,
Network: vlessConfig.Network,
}
if tlsConfig != nil {
outbound.TLS = tlsConfig
}
if v2rayTransport != nil {
outbound.V2RayTransport = v2rayTransport
}
// Special handling for different transport types
switch vlessConfig.Network {
case "ws":
// WebSocket transport
transport := map[string]interface{}{
"type": vlessConfig.Network,
}
if vlessConfig.Path != "" {
transport["path"] = vlessConfig.Path
}
if vlessConfig.Host != "" {
headers := map[string]interface{}{
"Host": vlessConfig.Host,
}
transport["headers"] = headers
}
outbound.Transport = transport
case "grpc":
// gRPC transport
transport := map[string]interface{}{
"type": vlessConfig.Network,
}
if vlessConfig.Path != "" {
transport["service_name"] = vlessConfig.Path
}
outbound.Transport = transport
}
// Create inbounds - mixed (SOCKS5 + HTTP) on port 1080
inbounds := []SingBoxInbound{
{
Type: "mixed",
Listen: "127.0.0.1",
ListenPort: 1080,
SetSystemProxy: true,
},
}
// Create the full configuration
config := SingBoxConfig{
Log: map[string]interface{}{
"level": "info",
},
Inbounds: inbounds,
Outbounds: []SingBoxOutbound{outbound},
}
// Convert to JSON
jsonData, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
// Write to file
err = os.WriteFile(configPath, jsonData, 0644)
if err != nil {
return err
}
log.Printf("Sing-box configuration written to %s", configPath)
return nil
}
// generateConfigFromVLESSURL parses a VLESS URL and generates a Sing-box config file
func generateConfigFromVLESSURL(vlessURL, outputPath string) error {
// Parse the VLESS URL
vlessConfig, err := parseVLESS(vlessURL)
if err != nil {
return err
}
// Generate the Sing-box configuration
err = generateSingBoxConfig(vlessConfig, outputPath)
if err != nil {
return err
}
return nil
}
func main() { func main() {
myApp := app.New() myApp := app.New()
myWindow := myApp.NewWindow("Hamy VPN Client") myWindow := myApp.NewWindow("Hamy VPN Client")
// Set the window to not resizable and fixed size
myWindow.SetFixedSize(true)
// Set the window size to 200x300 as requested // Set the window size to 200x300 as requested
myWindow.Resize(fyne.NewSize(200, 300)) myWindow.Resize(fyne.NewSize(200, 300))
@@ -205,8 +484,23 @@ func main() {
} }
statusLabel.Refresh() statusLabel.Refresh()
// This functionality will be implemented later if isConnected {
dialog.ShowInformation("Info", fmt.Sprintf("Connect/Disconnect functionality will be implemented later using: %s", configs[activeConfig].Title), myWindow) // Disconnect - kill the current process
killCurrentProcess()
} else {
// Connect - generate config and run sing-box
err := generateAndRunSingBox(configs[activeConfig].URL)
if err != nil {
dialog.ShowError(fmt.Errorf("failed to start connection: %v", err), myWindow)
// Revert connection state
isConnected = false
updateConnectionButtonText(connectButton)
statusLabel.Text = "Отключено"
statusLabel.Color = color.RGBA{R: 128, G: 128, B: 128, A: 255}
statusLabel.Refresh()
return
}
}
}) })
connectButton.Importance = widget.HighImportance connectButton.Importance = widget.HighImportance
if len(configs) == 0 { if len(configs) == 0 {

31
test_config.json Normal file
View File

@@ -0,0 +1,31 @@
{
"log": {
"level": "info"
},
"inbounds": [
{
"type": "mixed",
"listen": "127.0.0.1",
"listen_port": 1080,
"set_system_proxy": true
}
],
"outbounds": [
{
"type": "vless",
"server": "myserver.com",
"server_port": 443,
"uuid": "de3338a1-1234-5678-abcd-1234567890ab",
"network": "tcp",
"tls": {
"enabled": true,
"server_name": "myserver.com",
"fingerprint": "chrome",
"utls": {
"enabled": true,
"fingerprint": "chrome"
}
}
}
]
}