package main /* #include "stdint.h" */ import "C" import ( "context" "encoding/json" "fmt" "os" "strconv" "time" "unsafe" "github.com/Dreamacro/clash/adapter" "github.com/Dreamacro/clash/adapter/outboundgroup" "github.com/Dreamacro/clash/common/observable" "github.com/Dreamacro/clash/common/utils" "github.com/Dreamacro/clash/component/profile/cachefile" "github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/config" "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/hub" "github.com/Dreamacro/clash/hub/executor" P "github.com/Dreamacro/clash/listener" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" "github.com/Dreamacro/clash/tunnel/statistic" fclashgobridge "github.com/kingtous/fclash-go-bridge" ) var ( options []hub.Option log_subscriber observable.Subscription[log.Event] ) //export clash_init func clash_init(home_dir *C.char) int { home := C.GoString(home_dir) // constant. err := config.Init(home) if err != nil { return -1 } return 0 } //export set_config func set_config(config_path *C.char) int { file := C.GoString(config_path) if _, err := executor.ParseWithPath(file); err != nil { fmt.Println("config validate failed:", err) return -1 } constant.SetConfig(file) return 0 } //export set_home_dir func set_home_dir(home *C.char) int { home_gostr := C.GoString(home) info, err := os.Stat(home_gostr) if err == nil && info.IsDir() { fmt.Println("GO: set home dir to", home_gostr) constant.SetHomeDir(home_gostr) return 0 } else { if err != nil { fmt.Println("error:", err) } } return -1 } //export get_config func get_config() *C.char { return C.CString(constant.Path.Config()) } //export set_ext_controller func set_ext_controller(port uint64) int { url := "127.0.0.1:" + strconv.FormatUint(port, 10) options = append(options, hub.WithExternalController(url)) return 0 } //export clear_ext_options func clear_ext_options() { options = options[:0] } //export is_config_valid func is_config_valid(config_path *C.char) int { if _, err := executor.ParseWithPath(C.GoString(config_path)); err != nil { fmt.Println("error reading config:", err) return -1 } return 0 } //export get_all_connections func get_all_connections() *C.char { snapshot := statistic.DefaultManager.Snapshot() data, err := json.Marshal(snapshot) if err != nil { fmt.Println("Error:", err) return C.CString("") } return C.CString(string(data)) } //export close_all_connections func close_all_connections() { statistic.DefaultManager.Range(func(c statistic.Tracker) bool { c.Close() return true }) } //export close_connection func close_connection(id *C.char) bool { connection_id := C.GoString(id) statistic.DefaultManager.Range(func(c statistic.Tracker) bool { if c.ID() == connection_id { c.Close() return false } return true }) return false } //export parse_options func parse_options() bool { err := hub.Parse(options...) if err != nil { return true } return false } //export get_traffic func get_traffic() *C.char { up, down := statistic.DefaultManager.Now() traffic := map[string]int64{ "Up": up, "Down": down, } data, err := json.Marshal(traffic) if err != nil { fmt.Println("Error:", err) return C.CString("") } return C.CString(string(data)) } //export init_native_api_bridge func init_native_api_bridge(api unsafe.Pointer) { fclashgobridge.InitDartApi(api) } //export start_log func start_log(port C.longlong) { if log_subscriber != nil { log.UnSubscribe(log_subscriber) log_subscriber = nil } log_subscriber = log.Subscribe() go func() { for elem := range log_subscriber { lg := elem data, err := json.Marshal(lg) if err != nil { fmt.Println("Error:", err) } ret_str := string(data) fclashgobridge.SendToPort(int64(port), ret_str) } }() fmt.Println("[GO] subscribe logger on dart bridge port", int64(port)) } //export stop_log func stop_log() { if log_subscriber != nil { log.UnSubscribe(log_subscriber) fmt.Println("Logger stopped") log_subscriber = nil } } //export change_proxy func change_proxy(selector_name *C.char, proxy_name *C.char) C.long { proxies := tunnel.Proxies() proxy := proxies[C.GoString(selector_name)] if proxy == nil { return C.long(-1) } adapter_proxy := proxy.(*adapter.Proxy) selector, ok := adapter_proxy.ProxyAdapter.(*outboundgroup.Selector) if !ok { // not selector return C.long(-1) } if err := selector.Set(C.GoString(proxy_name)); err != nil { fmt.Println("", err) return C.long(-1) } cachefile.Cache().SetSelected(string(C.GoString(selector_name)), string(C.GoString(proxy_name))) return C.long(0) } type configSchema struct { Port *int `json:"port"` SocksPort *int `json:"socks-port"` RedirPort *int `json:"redir-port"` TProxyPort *int `json:"tproxy-port"` MixedPort *int `json:"mixed-port"` AllowLan *bool `json:"allow-lan"` BindAddress *string `json:"bind-address"` Mode *tunnel.TunnelMode `json:"mode"` LogLevel *log.LogLevel `json:"log-level"` IPv6 *bool `json:"ipv6"` } func pointerOrDefault(p *int, def int) int { if p != nil { return *p } return def } //export change_config_field func change_config_field(s *C.char) C.long { // todo general := &configSchema{} json_str := C.GoString(s) if err := json.Unmarshal([]byte(json_str), general); err != nil { fmt.Println(err) return C.long(-1) } // copy from clash source code if general.AllowLan != nil { P.SetAllowLan(*general.AllowLan) } if general.BindAddress != nil { P.SetBindAddress(*general.BindAddress) } ports := P.GetPorts() ports.Port = pointerOrDefault(general.Port, ports.Port) ports.MixedPort = pointerOrDefault(general.MixedPort, ports.MixedPort) ports.SocksPort = pointerOrDefault(general.SocksPort, ports.SocksPort) ports.RedirPort = pointerOrDefault(general.RedirPort, ports.RedirPort) ports.TProxyPort = pointerOrDefault(general.TProxyPort, ports.TProxyPort) tcpIn := tunnel.TCPIn() udpIn := tunnel.UDPIn() natTable := tunnel.NatTable() // P.ReCreate(*ports, tcpIn, udpIn) P.ReCreateHTTP(ports.Port, tcpIn) P.ReCreateMixed(ports.Port, tcpIn, udpIn) P.ReCreateSocks(ports.SocksPort, tcpIn, udpIn) P.ReCreateRedir(ports.RedirPort, tcpIn, udpIn, natTable) P.ReCreateTProxy(ports.TProxyPort, tcpIn, udpIn, natTable) if general.Mode != nil { tunnel.SetMode(*general.Mode) } if general.LogLevel != nil { log.SetLevel(*general.LogLevel) } if general.IPv6 != nil { resolver.DisableIPv6 = !*general.IPv6 } return C.long(0) } //export async_test_delay func async_test_delay(proxy_name *C.char, url *C.char, timeout C.long, port C.longlong) { go func() { ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(int64(timeout))) defer cancel() proxies := tunnel.Proxies() proxy := proxies[C.GoString(proxy_name)] if proxy == nil { data, err := json.Marshal(map[string]int64{ "delay": -1, }) if err != nil { return } fclashgobridge.SendToPort(int64(port), string(data)) return } rg := make(utils.IntRanges[uint16], 1) rg[0] = utils.NewRange[uint16](200, 400) delay, err := proxy.URLTest(ctx, C.GoString(url), rg, constant.ExtraHistory) if err != nil || delay == 0 { data, err := json.Marshal(map[string]int64{ "delay": -1, }) if err != nil { return } fclashgobridge.SendToPort(int64(port), string(data)) return } data, err := json.Marshal(map[string]uint16{ "delay": delay, }) if err != nil { fmt.Println("err: ", err) } fclashgobridge.SendToPort(int64(port), string(data)) }() } //export get_proxies func get_proxies() *C.char { proxies := tunnel.Proxies() for _, provider := range tunnel.Providers() { for _, proxy := range provider.Proxies() { proxies[proxy.Name()] = proxy } } data, err := json.Marshal(map[string]map[string]constant.Proxy{ "proxies": proxies, }) if err != nil { return C.CString("") } return C.CString(string(data)) } //export get_configs func get_configs() *C.char { general := executor.GetGeneral() data, err := json.Marshal(general) if err != nil { return C.CString("") } return C.CString(string(data)) } func main() { fmt.Println("hello fclash") }