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:
BIN
hamy-vpn-client-final.exe
Normal file
BIN
hamy-vpn-client-final.exe
Normal file
Binary file not shown.
BIN
hamy-vpn-client-new.exe
Normal file
BIN
hamy-vpn-client-new.exe
Normal file
Binary file not shown.
304
main.go
304
main.go
@@ -1,14 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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
|
||||
func runSingBox(configPath string) error {
|
||||
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() {
|
||||
myApp := app.New()
|
||||
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
|
||||
myWindow.Resize(fyne.NewSize(200, 300))
|
||||
|
||||
@@ -205,8 +484,23 @@ func main() {
|
||||
}
|
||||
statusLabel.Refresh()
|
||||
|
||||
// This functionality will be implemented later
|
||||
dialog.ShowInformation("Info", fmt.Sprintf("Connect/Disconnect functionality will be implemented later using: %s", configs[activeConfig].Title), myWindow)
|
||||
if isConnected {
|
||||
// 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
|
||||
if len(configs) == 0 {
|
||||
|
||||
31
test_config.json
Normal file
31
test_config.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user