Files
MWClash/core/hub.go
chen08209 676f2d058a Add windows server mode start process verify
Add linux deb dependencies

Add backup recovery strategy select

Support custom text scaling

Optimize the display of different text scale

Optimize windows setup experience

Optimize startTun performance

Optimize android tv experience

Optimize default option

Optimize computed text size

Optimize hyperOS freeform window

Add developer mode

Update core

Optimize more details
2025-05-01 00:02:29 +08:00

478 lines
9.5 KiB
Go

package main
import (
"context"
"core/state"
"encoding/json"
"fmt"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/adapter/outboundgroup"
"github.com/metacubex/mihomo/common/observable"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/mmdb"
"github.com/metacubex/mihomo/component/updater"
"github.com/metacubex/mihomo/config"
"github.com/metacubex/mihomo/constant"
cp "github.com/metacubex/mihomo/constant/provider"
"github.com/metacubex/mihomo/hub/executor"
"github.com/metacubex/mihomo/listener"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/tunnel"
"github.com/metacubex/mihomo/tunnel/statistic"
"net"
"runtime"
"sort"
"strconv"
"time"
)
var (
isInit = false
configParams = ConfigExtendedParams{}
externalProviders = map[string]cp.Provider{}
logSubscriber observable.Subscription[log.Event]
currentConfig *config.Config
)
func handleInitClash(paramsString string) bool {
var params = InitParams{}
err := json.Unmarshal([]byte(paramsString), &params)
if err != nil {
return false
}
version = params.Version
if !isInit {
constant.SetHomeDir(params.HomeDir)
isInit = true
}
return isInit
}
func handleStartListener() bool {
runLock.Lock()
defer runLock.Unlock()
isRunning = true
updateListeners(true)
return true
}
func handleStopListener() bool {
runLock.Lock()
defer runLock.Unlock()
isRunning = false
listener.StopListener()
return true
}
func handleGetIsInit() bool {
return isInit
}
func handleForceGc() {
go func() {
log.Infoln("[APP] request force GC")
runtime.GC()
}()
}
func handleShutdown() bool {
stopListeners()
executor.Shutdown()
runtime.GC()
isInit = false
return true
}
func handleValidateConfig(bytes []byte) string {
_, err := config.UnmarshalRawConfig(bytes)
if err != nil {
return err.Error()
}
return ""
}
func handleUpdateConfig(bytes []byte) string {
var params = &GenerateConfigParams{}
err := json.Unmarshal(bytes, params)
if err != nil {
return err.Error()
}
configParams = params.Params
prof := decorationConfig(params.ProfileId, params.Config)
err = applyConfig(prof)
if err != nil {
return err.Error()
}
return ""
}
func handleGetProxies() string {
runLock.Lock()
defer runLock.Unlock()
data, err := json.Marshal(tunnel.ProxiesWithProviders())
if err != nil {
return ""
}
return string(data)
}
func handleChangeProxy(data string, fn func(string string)) {
runLock.Lock()
go func() {
defer runLock.Unlock()
var params = &ChangeProxyParams{}
err := json.Unmarshal([]byte(data), params)
if err != nil {
fn(err.Error())
return
}
groupName := *params.GroupName
proxyName := *params.ProxyName
proxies := tunnel.ProxiesWithProviders()
group, ok := proxies[groupName]
if !ok {
fn("Not found group")
return
}
adapterProxy := group.(*adapter.Proxy)
selector, ok := adapterProxy.ProxyAdapter.(outboundgroup.SelectAble)
if !ok {
fn("Group is not selectable")
return
}
if proxyName == "" {
selector.ForceSet(proxyName)
} else {
err = selector.Set(proxyName)
}
if err != nil {
fn(err.Error())
return
}
fn("")
return
}()
}
func handleGetTraffic() string {
up, down := statistic.DefaultManager.Current(state.CurrentState.OnlyStatisticsProxy)
traffic := map[string]int64{
"up": up,
"down": down,
}
data, err := json.Marshal(traffic)
if err != nil {
fmt.Println("Error:", err)
return ""
}
return string(data)
}
func handleGetTotalTraffic() string {
up, down := statistic.DefaultManager.Total(state.CurrentState.OnlyStatisticsProxy)
traffic := map[string]int64{
"up": up,
"down": down,
}
data, err := json.Marshal(traffic)
if err != nil {
fmt.Println("Error:", err)
return ""
}
return string(data)
}
func handleGetProfile(profileId string) string {
prof := getRawConfigWithId(profileId)
data, err := json.Marshal(prof)
if err != nil {
return ""
}
return string(data)
}
func handleResetTraffic() {
statistic.DefaultManager.ResetStatistic()
}
func handleAsyncTestDelay(paramsString string, fn func(string)) {
b.Go(paramsString, func() (bool, error) {
var params = &TestDelayParams{}
err := json.Unmarshal([]byte(paramsString), params)
if err != nil {
fn("")
return false, nil
}
expectedStatus, err := utils.NewUnsignedRanges[uint16]("")
if err != nil {
fn("")
return false, nil
}
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout))
defer cancel()
proxies := tunnel.ProxiesWithProviders()
proxy := proxies[params.ProxyName]
delayData := &Delay{
Name: params.ProxyName,
}
if proxy == nil {
delayData.Value = -1
data, _ := json.Marshal(delayData)
fn(string(data))
return false, nil
}
testUrl := constant.DefaultTestURL
if params.TestUrl != "" {
testUrl = params.TestUrl
}
delayData.Url = testUrl
delay, err := proxy.URLTest(ctx, testUrl, expectedStatus)
if err != nil || delay == 0 {
delayData.Value = -1
data, _ := json.Marshal(delayData)
fn(string(data))
return false, nil
}
delayData.Value = int32(delay)
data, _ := json.Marshal(delayData)
fn(string(data))
return false, nil
})
}
func handleGetConnections() string {
runLock.Lock()
defer runLock.Unlock()
snapshot := statistic.DefaultManager.Snapshot()
data, err := json.Marshal(snapshot)
if err != nil {
fmt.Println("Error:", err)
return ""
}
return string(data)
}
func handleCloseConnections() bool {
runLock.Lock()
defer runLock.Unlock()
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
err := c.Close()
if err != nil {
return false
}
return true
})
return true
}
func handleCloseConnection(connectionId string) bool {
runLock.Lock()
defer runLock.Unlock()
c := statistic.DefaultManager.Get(connectionId)
if c == nil {
return false
}
_ = c.Close()
return true
}
func handleGetExternalProviders() string {
runLock.Lock()
defer runLock.Unlock()
externalProviders = getExternalProvidersRaw()
eps := make([]ExternalProvider, 0)
for _, p := range externalProviders {
externalProvider, err := toExternalProvider(p)
if err != nil {
continue
}
eps = append(eps, *externalProvider)
}
sort.Sort(ExternalProviders(eps))
data, err := json.Marshal(eps)
if err != nil {
return ""
}
return string(data)
}
func handleGetExternalProvider(externalProviderName string) string {
runLock.Lock()
defer runLock.Unlock()
externalProvider, exist := externalProviders[externalProviderName]
if !exist {
return ""
}
e, err := toExternalProvider(externalProvider)
if err != nil {
return ""
}
data, err := json.Marshal(e)
if err != nil {
return ""
}
return string(data)
}
func handleUpdateGeoData(geoType string, geoName string, fn func(value string)) {
go func() {
path := constant.Path.Resolve(geoName)
switch geoType {
case "MMDB":
err := updater.UpdateMMDBWithPath(path)
if err != nil {
fn(err.Error())
return
}
case "ASN":
err := updater.UpdateASNWithPath(path)
if err != nil {
fn(err.Error())
return
}
case "GeoIp":
err := updater.UpdateGeoIpWithPath(path)
if err != nil {
fn(err.Error())
return
}
case "GeoSite":
err := updater.UpdateGeoSiteWithPath(path)
if err != nil {
fn(err.Error())
return
}
}
fn("")
}()
}
func handleUpdateExternalProvider(providerName string, fn func(value string)) {
go func() {
externalProvider, exist := externalProviders[providerName]
if !exist {
fn("external provider is not exist")
return
}
err := externalProvider.Update()
if err != nil {
fn(err.Error())
return
}
fn("")
}()
}
func handleSideLoadExternalProvider(providerName string, data []byte, fn func(value string)) {
go func() {
runLock.Lock()
defer runLock.Unlock()
externalProvider, exist := externalProviders[providerName]
if !exist {
fn("external provider is not exist")
return
}
err := sideUpdateExternalProvider(externalProvider, data)
if err != nil {
fn(err.Error())
return
}
fn("")
}()
}
func handleStartLog() {
if logSubscriber != nil {
log.UnSubscribe(logSubscriber)
logSubscriber = nil
}
logSubscriber = log.Subscribe()
go func() {
for logData := range logSubscriber {
if logData.LogLevel < log.Level() {
continue
}
message := &Message{
Type: LogMessage,
Data: logData,
}
sendMessage(*message)
}
}()
}
func handleStopLog() {
if logSubscriber != nil {
log.UnSubscribe(logSubscriber)
logSubscriber = nil
}
}
func handleGetCountryCode(ip string, fn func(value string)) {
go func() {
runLock.Lock()
defer runLock.Unlock()
codes := mmdb.IPInstance().LookupCode(net.ParseIP(ip))
if len(codes) == 0 {
fn("")
return
}
fn(codes[0])
}()
}
func handleGetMemory(fn func(value string)) {
go func() {
fn(strconv.FormatUint(statistic.DefaultManager.Memory(), 10))
}()
}
func handleSetState(params string) {
_ = json.Unmarshal([]byte(params), state.CurrentState)
}
func handleCrash() {
panic("handle invoke crash")
}
func init() {
adapter.UrlTestHook = func(url string, name string, delay uint16) {
delayData := &Delay{
Url: url,
Name: name,
}
if delay == 0 {
delayData.Value = -1
} else {
delayData.Value = int32(delay)
}
sendMessage(Message{
Type: DelayMessage,
Data: delayData,
})
}
statistic.DefaultRequestNotify = func(c statistic.Tracker) {
sendMessage(Message{
Type: RequestMessage,
Data: c,
})
}
executor.DefaultProviderLoadedHook = func(providerName string) {
sendMessage(Message{
Type: LoadedMessage,
Data: providerName,
})
}
}