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
478 lines
9.5 KiB
Go
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), ¶ms)
|
|
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,
|
|
})
|
|
}
|
|
}
|