Projects STRLCPY SeaMoon Commits e165e5f8
🤬
  • ■ ■ ■ ■
    cmd/client/client.go
    skipped 13 lines
    14 14   "github.com/DVKunion/SeaMoon/cmd/client/route"
    15 15   "github.com/DVKunion/SeaMoon/cmd/client/static"
    16 16   "github.com/DVKunion/SeaMoon/pkg/api/service"
    17  - "github.com/DVKunion/SeaMoon/pkg/signal"
     17 + "github.com/DVKunion/SeaMoon/pkg/api/signal"
    18 18   "github.com/DVKunion/SeaMoon/pkg/system/xlog"
    19 19  )
    20 20   
    skipped 56 lines
  • ■ ■ ■ ■ ■
    cmd/client/route/route.go
    skipped 42 lines
    43 43   // tunnel
    44 44   v1.GET("/tunnel", append(middles, api_v1.ListTunnels)...)
    45 45   v1.GET("/tunnel/:id", append(middles, api_v1.GetTunnelById)...)
     46 + v1.GET("/tunnel/subscribe/:type/", append(middles, api_v1.SubscribeTunnel)...)
    46 47   v1.POST("/tunnel", append(middles, api_v1.CreateTunnel)...)
    47 48   v1.PUT("/tunnel/:id", append(middles, api_v1.UpdateTunnel)...)
    48 49   v1.DELETE("/tunnel/:id", append(middles, api_v1.DeleteTunnel)...)
    skipped 15 lines
  • ■ ■ ■ ■
    go.mod
    skipped 22 lines
    23 23   github.com/v2fly/v2ray-core/v5 v5.15.1
    24 24   google.golang.org/grpc v1.62.1
    25 25   google.golang.org/protobuf v1.33.0
     26 + gopkg.in/yaml.v3 v3.0.1
    26 27   gorm.io/gen v0.3.25
    27 28   gorm.io/gorm v1.25.5
    28 29   gorm.io/plugin/dbresolver v1.5.0
    skipped 115 lines
    144 145   gopkg.in/ini.v1 v1.56.0 // indirect
    145 146   gopkg.in/resty.v1 v1.12.0 // indirect
    146 147   gopkg.in/yaml.v2 v2.4.0 // indirect
    147  - gopkg.in/yaml.v3 v3.0.1 // indirect
    148 148   gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c // indirect
    149 149   gorm.io/driver/mysql v1.5.2 // indirect
    150 150   gorm.io/hints v1.1.0 // indirect
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    pkg/api/controller/middleware/jwt.go
    skipped 15 lines
    16 16  func JWTAuthMiddleware(c *gin.Context) {
    17 17   // 这里简化了JWT验证逻辑,实际使用时应更复杂
    18 18   tokenString := c.GetHeader("Authorization")
     19 + if tokenString == "" {
     20 + tokenString = c.Query("token")
     21 + }
    19 22   
    20 23   if tokenString == "" {
    21 24   servant.ErrorMsg(c, http.StatusUnauthorized, errors.ApiError(xlog.ApiParamsRequire, nil))
    skipped 28 lines
  • ■ ■ ■ ■ ■ ■
    pkg/api/controller/servant/response.go
    skipped 8 lines
    9 9   "github.com/DVKunion/SeaMoon/pkg/system/xlog"
    10 10  )
    11 11   
     12 +func RawMsg(c *gin.Context, name string, text []byte) {
     13 + c.Header("Content-Disposition", "attachment; filename="+name)
     14 + c.Data(200, "application/octet-stream; charset=utf-8", text)
     15 + return
     16 +}
     17 + 
    12 18  // SuccessMsg 通用正常响应
    13 19  func SuccessMsg(c *gin.Context, total int64, data interface{}) {
    14 20   c.JSON(http.StatusOK, gin.H{
    skipped 19 lines
  • ■ ■ ■ ■
    pkg/api/controller/v1/provider.go
    skipped 8 lines
    9 9   "github.com/DVKunion/SeaMoon/pkg/api/enum"
    10 10   "github.com/DVKunion/SeaMoon/pkg/api/models"
    11 11   "github.com/DVKunion/SeaMoon/pkg/api/service"
    12  - "github.com/DVKunion/SeaMoon/pkg/signal"
     12 + "github.com/DVKunion/SeaMoon/pkg/api/signal"
    13 13   "github.com/DVKunion/SeaMoon/pkg/system/errors"
    14 14   "github.com/DVKunion/SeaMoon/pkg/system/xlog"
    15 15  )
    skipped 107 lines
  • ■ ■ ■ ■
    pkg/api/controller/v1/proxy.go
    skipped 8 lines
    9 9   "github.com/DVKunion/SeaMoon/pkg/api/enum"
    10 10   "github.com/DVKunion/SeaMoon/pkg/api/models"
    11 11   "github.com/DVKunion/SeaMoon/pkg/api/service"
    12  - "github.com/DVKunion/SeaMoon/pkg/signal"
     12 + "github.com/DVKunion/SeaMoon/pkg/api/signal"
    13 13   "github.com/DVKunion/SeaMoon/pkg/system/errors"
    14 14   "github.com/DVKunion/SeaMoon/pkg/system/tools"
    15 15   "github.com/DVKunion/SeaMoon/pkg/system/xlog"
    skipped 118 lines
  • ■ ■ ■ ■ ■ ■
    pkg/api/controller/v1/tunnel.go
    skipped 10 lines
    11 11   "github.com/DVKunion/SeaMoon/pkg/api/enum"
    12 12   "github.com/DVKunion/SeaMoon/pkg/api/models"
    13 13   "github.com/DVKunion/SeaMoon/pkg/api/service"
    14  - "github.com/DVKunion/SeaMoon/pkg/signal"
     14 + "github.com/DVKunion/SeaMoon/pkg/api/signal"
    15 15   "github.com/DVKunion/SeaMoon/pkg/system/errors"
    16 16   "github.com/DVKunion/SeaMoon/pkg/system/xlog"
    17 17  )
    skipped 82 lines
    100 100   signal.Signal().SendTunnelSignalSync(uint(id), enum.TunnelDelete)
    101 101   
    102 102   servant.SuccessMsg(c, 1, nil)
     103 +}
     104 + 
     105 +func SubscribeTunnel(c *gin.Context) {
     106 + subType := c.Param("type")
     107 + tuns, err := service.SVC.ListTunnels(c, 0, 9999)
     108 + if err != nil {
     109 + servant.ErrorMsg(c, http.StatusInternalServerError, errors.ApiError(xlog.ApiServiceError, err))
     110 + return
     111 + }
     112 + 
     113 + switch subType {
     114 + case "ss":
     115 + servant.RawMsg(c, "seamoon-ss.", []byte(""))
     116 + return
     117 + case "clash":
     118 + servant.RawMsg(c, "seamoon-clash.yaml", tuns.ToConfig("clash"))
     119 + return
     120 + case "shadowrocket":
     121 + servant.RawMsg(c, "seamoon-shadowrocket.txt", tuns.ToConfig("shadowrocket"))
     122 + return
     123 + case "v2ray":
     124 + servant.RawMsg(c, "seamoon-v2ray.json", []byte(""))
     125 + return
     126 + }
     127 + servant.ErrorMsg(c, http.StatusBadRequest, errors.ApiError(xlog.ApiParamsError, nil))
    103 128  }
    104 129   
    105 130  func extra() func(api interface{}) {
    skipped 15 lines
  • ■ ■ ■ ■
    pkg/api/enum/proxy.go
    skipped 19 lines
    20 20   ProxyTypeAUTO ProxyType = "auto"
    21 21   ProxyTypeHTTP ProxyType = "http"
    22 22   ProxyTypeSOCKS5 ProxyType = "socks5"
    23  - ProxyTypeShadowSocks ProxyType = "ss"
     23 + ProxyTypeShadowSocks ProxyType = "shadowsocks"
    24 24   ProxyTypeVmess ProxyType = "vmess"
    25 25   ProxyTypeVless ProxyType = "vless"
    26 26  )
    skipped 17 lines
  • ■ ■ ■ ■ ■ ■
    pkg/api/enum/tunnel.go
    skipped 34 lines
    35 35   return TunnelTypeNULL
    36 36   }
    37 37  }
     38 +func (t TunnelType) String() string {
     39 + return string(t)
     40 +}
    38 41   
    39 42  func (t TunnelType) ToPtr() *string {
    40 43   return (*string)(&t)
    skipped 10 lines
  • ■ ■ ■ ■ ■ ■
    pkg/api/models/client.go
     1 +package models
     2 + 
     3 +// 不同 client 结构序列化时使用
     4 + 
     5 +type ClashConfig struct {
     6 + MixedPort int `yaml:"mixed-port,omitempty"`
     7 + AllowLan bool `yaml:"allow-lan,omitempty"`
     8 + Mode string `yaml:"mode,omitempty"`
     9 + LogLevel string `yaml:"log-level,omitempty"`
     10 + ExternalController string `yaml:"external-controller,omitempty"`
     11 + Secret string `yaml:"secret,omitempty"`
     12 + DNS ClashDNS `yaml:"dns,omitempty"`
     13 + Proxies []ClashProxies `yaml:"proxies,omitempty"`
     14 + ProxyGroups []ClashProxyGroups `yaml:"proxy-groups,omitempty"`
     15 + Rules []string `yaml:"rules,omitempty"`
     16 +}
     17 + 
     18 +type ClashFallbackFilter struct {
     19 + Geoip bool `yaml:"geoip,omitempty"`
     20 + Ipcidr []string `yaml:"ipcidr,omitempty"`
     21 + Domain []string `yaml:"domain,omitempty"`
     22 +}
     23 + 
     24 +type ClashDNS struct {
     25 + Enable bool `yaml:"enable,omitempty"`
     26 + Ipv6 bool `yaml:"ipv6,omitempty"`
     27 + Listen string `yaml:"listen,omitempty"`
     28 + EnhancedMode string `yaml:"enhanced-mode,omitempty"`
     29 + FakeIPFilter []string `yaml:"fake-ip-filter,omitempty"`
     30 + Nameserver []string `yaml:"nameserver,omitempty"`
     31 + Fallback []string `yaml:"fallback,omitempty"`
     32 + FallbackFilter ClashFallbackFilter `yaml:"fallback-filter,omitempty"`
     33 +}
     34 + 
     35 +type ClashProxies struct {
     36 + Name string `yaml:"name"`
     37 + Type string `yaml:"type"`
     38 + Server string `yaml:"server"`
     39 + Port int `yaml:"port"`
     40 + UUID string `yaml:"uuid"`
     41 + AlterId int `yaml:"alterId"`
     42 + TLS bool `yaml:"tls"`
     43 + SkipCertVerify bool `yaml:"skip-cert-verify,omitempty"`
     44 + NetWork string `yaml:"network,omitempty"`
     45 + WsOpts struct {
     46 + Path string `yaml:"path,omitempty"`
     47 + } `yaml:"ws-opts"`
     48 + Cipher string `yaml:"cipher,omitempty"`
     49 + Password string `yaml:"password,omitempty"`
     50 + Protocol string `yaml:"protocol,omitempty"`
     51 + ProtocolParam string `yaml:"protocol-param,omitempty"`
     52 + Obfs string `yaml:"obfs,omitempty"`
     53 + ObfsParam string `yaml:"obfs-param,omitempty"`
     54 + UDP bool `yaml:"udp,omitempty"`
     55 +}
     56 + 
     57 +type ClashProxyGroups struct {
     58 + Name string `yaml:"name,omitempty"`
     59 + Type string `yaml:"type,omitempty"`
     60 + Proxies []string `yaml:"proxies,omitempty"`
     61 + URL string `yaml:"url,omitempty,omitempty"`
     62 + Interval string `yaml:"interval,omitempty,omitempty"`
     63 +}
     64 + 
     65 +var BindingRules = []string{
     66 + // (Global_TV)
     67 + // > ABC
     68 + "DOMAIN-SUFFIX,edgedatg.com,Proxies",
     69 + "DOMAIN-SUFFIX,go.com,Proxies",
     70 + 
     71 + // > AbemaTV
     72 + "DOMAIN,linear-abematv.akamaized.net,Proxies",
     73 + "DOMAIN-SUFFIX,abema.io,Proxies",
     74 + "DOMAIN-SUFFIX,abema.tv,Proxies",
     75 + "DOMAIN-SUFFIX,akamaized.net,Proxies",
     76 + "DOMAIN-SUFFIX,ameba.jp,Proxies",
     77 + "DOMAIN-SUFFIX,hayabusa.io,Proxies",
     78 + 
     79 + // > Amazon Prime Video
     80 + "DOMAIN-SUFFIX,aiv-cdn.net,Proxies",
     81 + "DOMAIN-SUFFIX,amazonaws.com,Proxies",
     82 + "DOMAIN-SUFFIX,amazonvideo.com,Proxies",
     83 + "DOMAIN-SUFFIX,llnwd.net,Proxies",
     84 + 
     85 + // > Bahamut
     86 + "DOMAIN-SUFFIX,bahamut.com.tw,Proxies",
     87 + "DOMAIN-SUFFIX,gamer.com.tw,Proxies",
     88 + "DOMAIN-SUFFIX,hinet.net,Proxies",
     89 + 
     90 + // > BBC
     91 + "DOMAIN-KEYWORD,bbcfmt,Proxies",
     92 + "DOMAIN-KEYWORD,co.uk,Proxies",
     93 + "DOMAIN-KEYWORD,uk-live,Proxies",
     94 + "DOMAIN-SUFFIX,bbc.co,Proxies",
     95 + "DOMAIN-SUFFIX,bbc.co.uk,Proxies",
     96 + "DOMAIN-SUFFIX,bbc.com,Proxies",
     97 + "DOMAIN-SUFFIX,bbci.co,Proxies",
     98 + "DOMAIN-SUFFIX,bbci.co.uk,Proxies",
     99 + 
     100 + // > CHOCO TV
     101 + "DOMAIN-SUFFIX,chocotv.com.tw,Proxies",
     102 + 
     103 + // > Epicgames
     104 + "DOMAIN-KEYWORD,epicgames,Proxies",
     105 + "DOMAIN-SUFFIX,helpshift.com,Proxies",
     106 + 
     107 + // > Fox+
     108 + "DOMAIN-KEYWORD,foxplus,Proxies",
     109 + "DOMAIN-SUFFIX,config.fox.com,Proxies",
     110 + "DOMAIN-SUFFIX,emome.net,Proxies",
     111 + "DOMAIN-SUFFIX,fox.com,Proxies",
     112 + "DOMAIN-SUFFIX,foxdcg.com,Proxies",
     113 + "DOMAIN-SUFFIX,foxnow.com,Proxies",
     114 + "DOMAIN-SUFFIX,foxplus.com,Proxies",
     115 + "DOMAIN-SUFFIX,foxplay.com,Proxies",
     116 + "DOMAIN-SUFFIX,ipinfo.io,Proxies",
     117 + "DOMAIN-SUFFIX,mstage.io,Proxies",
     118 + "DOMAIN-SUFFIX,now.com,Proxies",
     119 + "DOMAIN-SUFFIX,theplatform.com,Proxies",
     120 + "DOMAIN-SUFFIX,urlload.net,Proxies",
     121 + 
     122 + // > HBO && HBO Go
     123 + "DOMAIN-SUFFIX,execute-api.ap-southeast-1.amazonaws.com,Proxies",
     124 + "DOMAIN-SUFFIX,hbo.com,Proxies",
     125 + "DOMAIN-SUFFIX,hboasia.com,Proxies",
     126 + "DOMAIN-SUFFIX,hbogo.com,Proxies",
     127 + "DOMAIN-SUFFIX,hbogoasia.hk,Proxies",
     128 + 
     129 + // > Hulu
     130 + "DOMAIN-SUFFIX,happyon.jp,Proxies",
     131 + "DOMAIN-SUFFIX,hulu.com,Proxies",
     132 + "DOMAIN-SUFFIX,huluim.com,Proxies",
     133 + "DOMAIN-SUFFIX,hulustream.com,Proxies",
     134 + 
     135 + // > Imkan
     136 + "DOMAIN-SUFFIX,imkan.tv,Proxies",
     137 + 
     138 + // > JOOX
     139 + "DOMAIN-SUFFIX,joox.com,Proxies",
     140 + 
     141 + // > MytvSUPER
     142 + "DOMAIN-KEYWORD,nowtv100,Proxies",
     143 + "DOMAIN-KEYWORD,rthklive,Proxies",
     144 + "DOMAIN-SUFFIX,mytvsuper.com,Proxies",
     145 + "DOMAIN-SUFFIX,tvb.com,Proxies",
     146 + 
     147 + // > Netflix
     148 + "DOMAIN-SUFFIX,netflix.com,Proxies",
     149 + "DOMAIN-SUFFIX,netflix.net,Proxies",
     150 + "DOMAIN-SUFFIX,nflxext.com,Proxies",
     151 + "DOMAIN-SUFFIX,nflximg.com,Proxies",
     152 + "DOMAIN-SUFFIX,nflximg.net,Proxies",
     153 + "DOMAIN-SUFFIX,nflxso.net,Proxies",
     154 + "DOMAIN-SUFFIX,nflxvideo.net,Proxies",
     155 + 
     156 + // > Pandora
     157 + "DOMAIN-SUFFIX,pandora.com,Proxies",
     158 + 
     159 + // > Sky GO
     160 + "DOMAIN-SUFFIX,sky.com,Proxies",
     161 + "DOMAIN-SUFFIX,skygo.co.nz,Proxies",
     162 + 
     163 + // > Spotify
     164 + "DOMAIN-KEYWORD,spotify,Proxies",
     165 + "DOMAIN-SUFFIX,scdn.co,Proxies",
     166 + "DOMAIN-SUFFIX,spoti.fi,Proxies",
     167 + 
     168 + // > viuTV
     169 + "DOMAIN-SUFFIX,viu.tv,Proxies",
     170 + 
     171 + // > Youtube
     172 + "DOMAIN-KEYWORD,youtube,Proxies",
     173 + "DOMAIN-SUFFIX,googlevideo.com,Proxies",
     174 + "DOMAIN-SUFFIX,gvt2.com,Proxies",
     175 + "DOMAIN-SUFFIX,youtu.be,Proxies",
     176 + 
     177 + // (Asian_TV)
     178 + // > Bilibili
     179 + "DOMAIN-KEYWORD,bilibili,Direct",
     180 + "DOMAIN-SUFFIX,acg.tv,Direct",
     181 + "DOMAIN-SUFFIX,acgvideo.com,Direct",
     182 + "DOMAIN-SUFFIX,b23.tv,Direct",
     183 + "DOMAIN-SUFFIX,biliapi.com,Direct",
     184 + "DOMAIN-SUFFIX,biliapi.net,Direct",
     185 + "DOMAIN-SUFFIX,bilibili.com,Direct",
     186 + "DOMAIN-SUFFIX,biligame.com,Direct",
     187 + "DOMAIN-SUFFIX,biligame.net,Direct",
     188 + "DOMAIN-SUFFIX,hdslb.com,Direct",
     189 + "DOMAIN-SUFFIX,im9.com,Direct",
     190 + 
     191 + // > IQIYI
     192 + "DOMAIN-KEYWORD,qiyi,Direct",
     193 + "DOMAIN-SUFFIX,qy.net,Direct",
     194 + 
     195 + // > letv
     196 + "DOMAIN-SUFFIX,api.mob.app.letv.com,Direct",
     197 + 
     198 + // > NeteaseMusic
     199 + "DOMAIN-SUFFIX,163yun.com,Direct",
     200 + "DOMAIN-SUFFIX,music.126.net,Direct",
     201 + "DOMAIN-SUFFIX,music.163.com,Direct",
     202 + 
     203 + // > Tencent Video
     204 + "DOMAIN-SUFFIX,vv.video.qq.com,Direct",
     205 + 
     206 + // China Area Network
     207 + // > Microsoft
     208 + "DOMAIN-SUFFIX,microsoft.com,Direct",
     209 + "DOMAIN-SUFFIX,windows.net,Direct",
     210 + "DOMAIN-SUFFIX,sfx.ms,Direct",
     211 + "DOMAIN-SUFFIX,sharepoint.com,Direct",
     212 + "DOMAIN-KEYWORD,officecdn,Direct",
     213 + // > Blizzard
     214 + "DOMAIN-SUFFIX,blizzard.com,Direct",
     215 + "DOMAIN-SUFFIX,battle.net,Direct",
     216 + "DOMAIN,blzddist1-a.akamaihd.net,Direct",
     217 + // > Tencent
     218 + // "USER-AGENT,MicroMessenger%20Client,Direct",
     219 + // "USER-AGENT,WeChat*,Direct",
     220 + "DOMAIN-SUFFIX,qq.com,Direct",
     221 + "DOMAIN-SUFFIX,qpic.cn,Direct",
     222 + "DOMAIN-SUFFIX,tencent.com,Direct",
     223 + // > Alibaba
     224 + "DOMAIN-SUFFIX,alibaba.com,Direct",
     225 + "DOMAIN-SUFFIX,alicdn.com,Direct",
     226 + "DOMAIN-SUFFIX,amap.com,Direct",
     227 + "DOMAIN-SUFFIX,dingtalk.com,Direct",
     228 + "DOMAIN-SUFFIX,taobao.com,Direct",
     229 + "DOMAIN-SUFFIX,tmall.com,Direct",
     230 + "DOMAIN-SUFFIX,ykimg.com,Direct",
     231 + "DOMAIN-SUFFIX,youku.com,Direct",
     232 + "DOMAIN-SUFFIX,xiami.com,Direct",
     233 + "DOMAIN-SUFFIX,xiami.net,Direct",
     234 + // > NetEase
     235 + "DOMAIN-SUFFIX,163.com,Direct",
     236 + "DOMAIN-SUFFIX,126.net,Direct",
     237 + "DOMAIN-SUFFIX,163yun.com,Direct",
     238 + // > Sohu
     239 + "DOMAIN-SUFFIX,sohu.com.cn,Direct",
     240 + "DOMAIN-SUFFIX,itc.cn,Direct",
     241 + "DOMAIN-SUFFIX,sohu.com,Direct",
     242 + "DOMAIN-SUFFIX,v-56.com,Direct",
     243 + // > Sina
     244 + "DOMAIN-SUFFIX,weibo.com,Direct",
     245 + "DOMAIN-SUFFIX,weibo.cn,Direct",
     246 + // > JD
     247 + "DOMAIN-SUFFIX,jd.com,Direct",
     248 + "DOMAIN-SUFFIX,jd.hk,Direct",
     249 + "DOMAIN-SUFFIX,360buyimg.com,Direct",
     250 + // > MI
     251 + "DOMAIN-SUFFIX,duokan.com,Direct",
     252 + "DOMAIN-SUFFIX,mi-img.com,Direct",
     253 + "DOMAIN-SUFFIX,mifile.cn,Direct",
     254 + "DOMAIN-SUFFIX,xiaomi.com,Direct",
     255 + // > bilibili
     256 + "DOMAIN-SUFFIX,acgvideo.com,Direct",
     257 + "DOMAIN-SUFFIX,bilibili.com,Direct",
     258 + "DOMAIN-SUFFIX,hdslb.com,Direct",
     259 + // > iQiyi
     260 + "DOMAIN-SUFFIX,iqiyi.com,Direct",
     261 + "DOMAIN-SUFFIX,iqiyipic.com,Direct",
     262 + "DOMAIN-SUFFIX,71.am.com,Direct",
     263 + // > HunanTV
     264 + "DOMAIN-SUFFIX,hitv.com,Direct",
     265 + "DOMAIN-SUFFIX,mgtv.com,Direct",
     266 + // > Meitu
     267 + "DOMAIN-SUFFIX,meitu.com,Direct",
     268 + "DOMAIN-SUFFIX,meitudata.com,Direct",
     269 + "DOMAIN-SUFFIX,meipai.com,Direct",
     270 + // > YYeTs
     271 + "DOMAIN-SUFFIX,zmzapi.com,Direct",
     272 + "DOMAIN-SUFFIX,zimuzu.tv,Direct",
     273 + "DOMAIN-SUFFIX,zmzfile.com,Direct",
     274 + "DOMAIN-SUFFIX,zmzapi.net,Direct",
     275 + // > 蛋蛋赞
     276 + "DOMAIN-SUFFIX,baduziyuan.com,Direct",
     277 + "DOMAIN-SUFFIX,com-hs-hkdy.com,Direct",
     278 + "DOMAIN-SUFFIX,czybjz.com,Direct",
     279 + "DOMAIN-SUFFIX,dandanzan.com,Direct",
     280 + "DOMAIN-SUFFIX,fjhps.com,Direct",
     281 + "DOMAIN-SUFFIX,kuyunbo.club,Direct",
     282 + // > Baidu
     283 + "DOMAIN-SUFFIX,baidu.com,Direct",
     284 + "DOMAIN-SUFFIX,baidubcr.com,Direct",
     285 + "DOMAIN-SUFFIX,bdstatic.com,Direct",
     286 + // > ChinaNet
     287 + "DOMAIN-SUFFIX,189.cn,Direct",
     288 + "DOMAIN-SUFFIX,21cn.com,Direct",
     289 + // > ByteDance
     290 + "DOMAIN-SUFFIX,bytecdn.cn,Direct",
     291 + "DOMAIN-SUFFIX,pstatp.com,Direct",
     292 + "DOMAIN-SUFFIX,snssdk.com,Direct",
     293 + "DOMAIN-SUFFIX,toutiao.com,Direct",
     294 + // > Content Delivery Network
     295 + // > Akamai
     296 + "DOMAIN-SUFFIX,akadns.net,Direct",
     297 + // - DOMAIN-SUFFIX,akamai.net,Direct",
     298 + // - DOMAIN-SUFFIX,akamaiedge.net,Direct",
     299 + // - DOMAIN-SUFFIX,akamaihd.net,Direct",
     300 + // - DOMAIN-SUFFIX,akamaistream.net,Direct",
     301 + // - DOMAIN-SUFFIX,akamaized.net,Direct",
     302 + // > ChinaNetCenter
     303 + "DOMAIN-SUFFIX,chinanetcenter.com,Direct",
     304 + "DOMAIN-SUFFIX,wangsu.com,Direct",
     305 + // > IP Query
     306 + "DOMAIN-SUFFIX,ipip.net,Direct",
     307 + "DOMAIN-SUFFIX,ip.cn,Direct",
     308 + "DOMAIN-SUFFIX,ip.la,Direct",
     309 + "DOMAIN-SUFFIX,ip-cdn.com,Direct",
     310 + "DOMAIN-SUFFIX,ipv6-test.com,Direct",
     311 + "DOMAIN-SUFFIX,test-ipv6.com,Direct",
     312 + "DOMAIN-SUFFIX,whatismyip.com,Direct",
     313 + "DOMAIN,ip.bjango.com,Direct",
     314 + // > Other
     315 + "DOMAIN-SUFFIX,40017.cn,Direct",
     316 + "DOMAIN-SUFFIX,broadcasthe.net,Direct",
     317 + "DOMAIN-SUFFIX,cailianpress.com,Direct",
     318 + "DOMAIN-SUFFIX,chdbits.co,Direct",
     319 + "DOMAIN-SUFFIX,chushou.tv,Direct",
     320 + "DOMAIN-SUFFIX,cmbchina.com,Direct",
     321 + "DOMAIN-SUFFIX,cmbimg.com,Direct",
     322 + "DOMAIN-SUFFIX,cmct.tv,Direct",
     323 + "DOMAIN-SUFFIX,cmvideo.cn,Direct",
     324 + "DOMAIN-SUFFIX,cnlang.org,Direct",
     325 + "DOMAIN-SUFFIX,doubanio.com,Direct",
     326 + "DOMAIN-SUFFIX,douyu.com,Direct",
     327 + "DOMAIN-SUFFIX,douyucdn.cn,Direct",
     328 + "DOMAIN-SUFFIX,dxycdn.com,Direct",
     329 + "DOMAIN-SUFFIX,hicloud.com,Direct",
     330 + "DOMAIN-SUFFIX,hdchina.org,Direct",
     331 + "DOMAIN-SUFFIX,hdcmct.org,Direct",
     332 + "DOMAIN-SUFFIX,ithome.com,Direct",
     333 + "DOMAIN-SUFFIX,kkmh.com,Direct",
     334 + "DOMAIN-SUFFIX,ksosoft.com,Direct",
     335 + "DOMAIN-SUFFIX,maoyun.tv,Direct",
     336 + "DOMAIN-SUFFIX,meituan.net,Direct",
     337 + "DOMAIN-SUFFIX,mobike.com,Direct",
     338 + "DOMAIN-SUFFIX,mubu.com,Direct",
     339 + "DOMAIN-SUFFIX,myzaker.com,Direct",
     340 + "DOMAIN-SUFFIX,ourbits.club,Direct",
     341 + "DOMAIN-SUFFIX,passthepopcorn.me,Direct",
     342 + "DOMAIN-SUFFIX,paypal.com,Direct",
     343 + "DOMAIN-SUFFIX,paypalobjects.com,Direct",
     344 + "DOMAIN-SUFFIX,privatehd.to,Direct",
     345 + "DOMAIN-SUFFIX,redacted.ch,Direct",
     346 + "DOMAIN-SUFFIX,ruguoapp.com,Direct",
     347 + "DOMAIN-SUFFIX,smzdm.com,Direct",
     348 + "DOMAIN-SUFFIX,sogou.com,Direct",
     349 + "DOMAIN-SUFFIX,teamviewer.com,Direct",
     350 + "DOMAIN-SUFFIX,totheglory.im,Direct",
     351 + "DOMAIN-SUFFIX,udacity.com,Direct",
     352 + "DOMAIN-SUFFIX,xmcdn.com,Direct",
     353 + "DOMAIN-SUFFIX,yangkeduo.com,Direct",
     354 + "DOMAIN-SUFFIX,zhihu.com,Direct",
     355 + "DOMAIN-SUFFIX,zhimg.com,Direct",
     356 + // "USER-AGENT,NeteaseMusic*,Direct",
     357 + // "USER-AGENT,%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90*,Direct",
     358 + 
     359 + // (DNS Cache Pollution Protection)
     360 + // > Google
     361 + "DOMAIN-SUFFIX,appspot.com,Proxies",
     362 + "DOMAIN-SUFFIX,blogger.com,Proxies",
     363 + "DOMAIN-SUFFIX,getoutline.org,Proxies",
     364 + "DOMAIN-SUFFIX,gvt0.com,Proxies",
     365 + "DOMAIN-SUFFIX,gvt1.com,Proxies",
     366 + "DOMAIN-SUFFIX,gvt3.com,Proxies",
     367 + "DOMAIN-SUFFIX,xn--ngstr-lra8j.com,Proxies",
     368 + "DOMAIN-KEYWORD,google,Proxies",
     369 + "DOMAIN-KEYWORD,blogspot,Proxies",
     370 + // > Facebook
     371 + "DOMAIN-SUFFIX,cdninstagram.com,Proxies",
     372 + "DOMAIN-SUFFIX,fb.com,Proxies",
     373 + "DOMAIN-SUFFIX,fb.me,Proxies",
     374 + "DOMAIN-SUFFIX,fbaddins.com,Proxies",
     375 + "DOMAIN-SUFFIX,fbcdn.net,Proxies",
     376 + "DOMAIN-SUFFIX,fbsbx.com,Proxies",
     377 + "DOMAIN-SUFFIX,fbworkmail.com,Proxies",
     378 + "DOMAIN-SUFFIX,instagram.com,Proxies",
     379 + "DOMAIN-SUFFIX,m.me,Proxies",
     380 + "DOMAIN-SUFFIX,messenger.com,Proxies",
     381 + "DOMAIN-SUFFIX,oculus.com,Proxies",
     382 + "DOMAIN-SUFFIX,oculuscdn.com,Proxies",
     383 + "DOMAIN-SUFFIX,rocksdb.org,Proxies",
     384 + "DOMAIN-SUFFIX,whatsapp.com,Proxies",
     385 + "DOMAIN-SUFFIX,whatsapp.net,Proxies",
     386 + "DOMAIN-KEYWORD,facebook,Proxies",
     387 + // > Twitter
     388 + "DOMAIN-SUFFIX,pscp.tv,Proxies",
     389 + "DOMAIN-SUFFIX,periscope.tv,Proxies",
     390 + "DOMAIN-SUFFIX,t.co,Proxies",
     391 + "DOMAIN-SUFFIX,twimg.co,Proxies",
     392 + "DOMAIN-SUFFIX,twimg.com,Proxies",
     393 + "DOMAIN-SUFFIX,twitpic.com,Proxies",
     394 + "DOMAIN-SUFFIX,vine.co,Proxies",
     395 + "DOMAIN-KEYWORD,twitter,Proxies",
     396 + // > Telegram
     397 + "DOMAIN-SUFFIX,t.me,Proxies",
     398 + "DOMAIN-SUFFIX,tdesktop.com,Proxies",
     399 + "DOMAIN-SUFFIX,telegra.ph,Proxies",
     400 + "DOMAIN-SUFFIX,telegram.me,Proxies",
     401 + "DOMAIN-SUFFIX,telegram.org,Proxies",
     402 + // > Line
     403 + "DOMAIN-SUFFIX,line.me,Proxies",
     404 + "DOMAIN-SUFFIX,line-apps.com,Proxies",
     405 + "DOMAIN-SUFFIX,line-scdn.net,Proxies",
     406 + "DOMAIN-SUFFIX,naver.jp,Proxies",
     407 + // > Other
     408 + "DOMAIN-SUFFIX,4shared.com,Proxies",
     409 + "DOMAIN-SUFFIX,881903.com,Proxies",
     410 + "DOMAIN-SUFFIX,abc.net.au,Proxies",
     411 + "DOMAIN-SUFFIX,abebooks.com,Proxies",
     412 + "DOMAIN-SUFFIX,amazon.co.jp,Proxies",
     413 + "DOMAIN-SUFFIX,apigee.com,Proxies",
     414 + "DOMAIN-SUFFIX,apk-dl.com,Proxies",
     415 + "DOMAIN-SUFFIX,apkmirror.com,Proxies",
     416 + "DOMAIN-SUFFIX,apkmonk.com,Proxies",
     417 + "DOMAIN-SUFFIX,apkpure.com,Proxies",
     418 + "DOMAIN-SUFFIX,aptoide.com,Proxies",
     419 + "DOMAIN-SUFFIX,archive.is,Proxies",
     420 + "DOMAIN-SUFFIX,archive.org,Proxies",
     421 + "DOMAIN-SUFFIX,arte.tv,Proxies",
     422 + "DOMAIN-SUFFIX,ask.com,Proxies",
     423 + "DOMAIN-SUFFIX,avgle.com,Proxies",
     424 + "DOMAIN-SUFFIX,badoo.com,Proxies",
     425 + "DOMAIN-SUFFIX,bandwagonhost.com,Proxies",
     426 + "DOMAIN-SUFFIX,bbc.com,Proxies",
     427 + "DOMAIN-SUFFIX,behance.net,Proxies",
     428 + "DOMAIN-SUFFIX,bibox.com,Proxies",
     429 + "DOMAIN-SUFFIX,biggo.com.tw,Proxies",
     430 + "DOMAIN-SUFFIX,binance.com,Proxies",
     431 + "DOMAIN-SUFFIX,bitcointalk.org,Proxies",
     432 + "DOMAIN-SUFFIX,bitfinex.com,Proxies",
     433 + "DOMAIN-SUFFIX,bitmex.com,Proxies",
     434 + "DOMAIN-SUFFIX,bit-z.com,Proxies",
     435 + "DOMAIN-SUFFIX,bloglovin.com,Proxies",
     436 + "DOMAIN-SUFFIX,bloomberg.cn,Proxies",
     437 + "DOMAIN-SUFFIX,bloomberg.com,Proxies",
     438 + "DOMAIN-SUFFIX,book.com.tw,Proxies",
     439 + "DOMAIN-SUFFIX,booklive.jp,Proxies",
     440 + "DOMAIN-SUFFIX,books.com.tw,Proxies",
     441 + "DOMAIN-SUFFIX,box.com,Proxies",
     442 + "DOMAIN-SUFFIX,brookings.edu,Proxies",
     443 + "DOMAIN-SUFFIX,businessinsider.com,Proxies",
     444 + "DOMAIN-SUFFIX,bwh1.net,Proxies",
     445 + "DOMAIN-SUFFIX,castbox.fm,Proxies",
     446 + "DOMAIN-SUFFIX,cbc.ca,Proxies",
     447 + "DOMAIN-SUFFIX,cdw.com,Proxies",
     448 + "DOMAIN-SUFFIX,change.org,Proxies",
     449 + "DOMAIN-SUFFIX,ck101.com,Proxies",
     450 + "DOMAIN-SUFFIX,clarionproject.org,Proxies",
     451 + "DOMAIN-SUFFIX,clyp.it,Proxies",
     452 + "DOMAIN-SUFFIX,cna.com.tw,Proxies",
     453 + "DOMAIN-SUFFIX,comparitech.com,Proxies",
     454 + "DOMAIN-SUFFIX,conoha.jp,Proxies",
     455 + "DOMAIN-SUFFIX,crucial.com,Proxies",
     456 + "DOMAIN-SUFFIX,cts.com.tw,Proxies",
     457 + "DOMAIN-SUFFIX,cw.com.tw,Proxies",
     458 + "DOMAIN-SUFFIX,cyberctm.com,Proxies",
     459 + "DOMAIN-SUFFIX,dailymotion.com,Proxies",
     460 + "DOMAIN-SUFFIX,dailyview.tw,Proxies",
     461 + "DOMAIN-SUFFIX,daum.net,Proxies",
     462 + "DOMAIN-SUFFIX,daumcdn.net,Proxies",
     463 + "DOMAIN-SUFFIX,dcard.tw,Proxies",
     464 + "DOMAIN-SUFFIX,deepdiscount.com,Proxies",
     465 + "DOMAIN-SUFFIX,deezer.com,Proxies",
     466 + "DOMAIN-SUFFIX,depositphotos.com,Proxies",
     467 + "DOMAIN-SUFFIX,disconnect.me,Proxies",
     468 + "DOMAIN-SUFFIX,discordapp.com,Proxies",
     469 + "DOMAIN-SUFFIX,discordapp.net,Proxies",
     470 + "DOMAIN-SUFFIX,disqus.com,Proxies",
     471 + "DOMAIN-SUFFIX,dns2go.com,Proxies",
     472 + "DOMAIN-SUFFIX,dropbox.com,Proxies",
     473 + "DOMAIN-SUFFIX,dropboxusercontent.com,Proxies",
     474 + "DOMAIN-SUFFIX,duckduckgo.com,Proxies",
     475 + "DOMAIN-SUFFIX,dw.com,Proxies",
     476 + "DOMAIN-SUFFIX,dynu.com,Proxies",
     477 + "DOMAIN-SUFFIX,earthcam.com,Proxies",
     478 + "DOMAIN-SUFFIX,ebookservice.tw,Proxies",
     479 + "DOMAIN-SUFFIX,economist.com,Proxies",
     480 + "DOMAIN-SUFFIX,edgecastcdn.net,Proxies",
     481 + "DOMAIN-SUFFIX,edu,Proxies",
     482 + "DOMAIN-SUFFIX,elpais.com,Proxies",
     483 + "DOMAIN-SUFFIX,enanyang.my,Proxies",
     484 + "DOMAIN-SUFFIX,euronews.com,Proxies",
     485 + "DOMAIN-SUFFIX,feedly.com,Proxies",
     486 + "DOMAIN-SUFFIX,files.wordpress.com,Proxies",
     487 + "DOMAIN-SUFFIX,flickr.com,Proxies",
     488 + "DOMAIN-SUFFIX,flitto.com,Proxies",
     489 + "DOMAIN-SUFFIX,foreignpolicy.com,Proxies",
     490 + "DOMAIN-SUFFIX,friday.tw,Proxies",
     491 + "DOMAIN-SUFFIX,gate.io,Proxies",
     492 + "DOMAIN-SUFFIX,getlantern.org,Proxies",
     493 + "DOMAIN-SUFFIX,getsync.com,Proxies",
     494 + "DOMAIN-SUFFIX,goo.ne.jp,Proxies",
     495 + "DOMAIN-SUFFIX,goodreads.com,Proxies",
     496 + "DOMAIN-SUFFIX,gov.tw,Proxies",
     497 + "DOMAIN-SUFFIX,gumroad.com,Proxies",
     498 + "DOMAIN-SUFFIX,hbg.com,Proxies",
     499 + "DOMAIN-SUFFIX,hightail.com,Proxies",
     500 + "DOMAIN-SUFFIX,hk01.com,Proxies",
     501 + "DOMAIN-SUFFIX,hkbf.org,Proxies",
     502 + "DOMAIN-SUFFIX,hkbookcity.com,Proxies",
     503 + "DOMAIN-SUFFIX,hkej.com,Proxies",
     504 + "DOMAIN-SUFFIX,hket.com,Proxies",
     505 + "DOMAIN-SUFFIX,hkgolden.com,Proxies",
     506 + "DOMAIN-SUFFIX,hootsuite.com,Proxies",
     507 + "DOMAIN-SUFFIX,hudson.org,Proxies",
     508 + "DOMAIN-SUFFIX,huobi.pro,Proxies",
     509 + "DOMAIN-SUFFIX,initiummall.com,Proxies",
     510 + "DOMAIN-SUFFIX,ipfs.io,Proxies",
     511 + "DOMAIN-SUFFIX,issuu.com,Proxies",
     512 + "DOMAIN-SUFFIX,japantimes.co.jp,Proxies",
     513 + "DOMAIN-SUFFIX,jiji.com,Proxies",
     514 + "DOMAIN-SUFFIX,jinx.com,Proxies",
     515 + "DOMAIN-SUFFIX,jkforum.net,Proxies",
     516 + "DOMAIN-SUFFIX,joinmastodon.org,Proxies",
     517 + "DOMAIN-SUFFIX,kakao.com,Proxies",
     518 + "DOMAIN-SUFFIX,lihkg.com,Proxies",
     519 + "DOMAIN-SUFFIX,live.com,Proxies",
     520 + "DOMAIN-SUFFIX,mail.ru,Proxies",
     521 + "DOMAIN-SUFFIX,matters.news,Proxies",
     522 + "DOMAIN-SUFFIX,medium.com,Proxies",
     523 + "DOMAIN-SUFFIX,mega.nz,Proxies",
     524 + "DOMAIN-SUFFIX,mil,Proxies",
     525 + "DOMAIN-SUFFIX,mobile01.com,Proxies",
     526 + "DOMAIN-SUFFIX,naver.com,Proxies",
     527 + "DOMAIN-SUFFIX,nikkei.com,Proxies",
     528 + "DOMAIN-SUFFIX,nofile.io,Proxies",
     529 + "DOMAIN-SUFFIX,now.com,Proxies",
     530 + "DOMAIN-SUFFIX,nyt.com,Proxies",
     531 + "DOMAIN-SUFFIX,nytchina.com,Proxies",
     532 + "DOMAIN-SUFFIX,nytcn.me,Proxies",
     533 + "DOMAIN-SUFFIX,nytco.com,Proxies",
     534 + "DOMAIN-SUFFIX,nytimes.com,Proxies",
     535 + "DOMAIN-SUFFIX,nytimg.com,Proxies",
     536 + "DOMAIN-SUFFIX,nytlog.com,Proxies",
     537 + "DOMAIN-SUFFIX,nytstyle.com,Proxies",
     538 + "DOMAIN-SUFFIX,ok.ru,Proxies",
     539 + "DOMAIN-SUFFIX,okex.com,Proxies",
     540 + "DOMAIN-SUFFIX,pcloud.com,Proxies",
     541 + "DOMAIN-SUFFIX,pinimg.com,Proxies",
     542 + "DOMAIN-SUFFIX,pixiv.net,Proxies",
     543 + "DOMAIN-SUFFIX,pornhub.com,Proxies",
     544 + "DOMAIN-SUFFIX,pureapk.com,Proxies",
     545 + "DOMAIN-SUFFIX,quora.com,Proxies",
     546 + "DOMAIN-SUFFIX,quoracdn.net,Proxies",
     547 + "DOMAIN-SUFFIX,rakuten.co.jp,Proxies",
     548 + "DOMAIN-SUFFIX,reddit.com,Proxies",
     549 + "DOMAIN-SUFFIX,redditmedia.com,Proxies",
     550 + "DOMAIN-SUFFIX,resilio.com,Proxies",
     551 + "DOMAIN-SUFFIX,reuters.com,Proxies",
     552 + "DOMAIN-SUFFIX,scmp.com,Proxies",
     553 + "DOMAIN-SUFFIX,scribd.com,Proxies",
     554 + "DOMAIN-SUFFIX,seatguru.com,Proxies",
     555 + "DOMAIN-SUFFIX,shadowsocks.org,Proxies",
     556 + "DOMAIN-SUFFIX,slideshare.net,Proxies",
     557 + "DOMAIN-SUFFIX,soundcloud.com,Proxies",
     558 + "DOMAIN-SUFFIX,startpage.com,Proxies",
     559 + "DOMAIN-SUFFIX,steemit.com,Proxies",
     560 + "DOMAIN-SUFFIX,t66y.com,Proxies",
     561 + "DOMAIN-SUFFIX,teco-hk.org,Proxies",
     562 + "DOMAIN-SUFFIX,teco-mo.org,Proxies",
     563 + "DOMAIN-SUFFIX,teddysun.com,Proxies",
     564 + "DOMAIN-SUFFIX,theinitium.com,Proxies",
     565 + "DOMAIN-SUFFIX,tineye.com,Proxies",
     566 + "DOMAIN-SUFFIX,torproject.org,Proxies",
     567 + "DOMAIN-SUFFIX,tumblr.com,Proxies",
     568 + "DOMAIN-SUFFIX,turbobit.net,Proxies",
     569 + "DOMAIN-SUFFIX,twitch.tv,Proxies",
     570 + "DOMAIN-SUFFIX,udn.com,Proxies",
     571 + "DOMAIN-SUFFIX,unseen.is,Proxies",
     572 + "DOMAIN-SUFFIX,upmedia.mg,Proxies",
     573 + "DOMAIN-SUFFIX,uptodown.com,Proxies",
     574 + "DOMAIN-SUFFIX,ustream.tv,Proxies",
     575 + "DOMAIN-SUFFIX,uwants.com,Proxies",
     576 + "DOMAIN-SUFFIX,v2ray.com,Proxies",
     577 + "DOMAIN-SUFFIX,viber.com,Proxies",
     578 + "DOMAIN-SUFFIX,videopress.com,Proxies",
     579 + "DOMAIN-SUFFIX,vimeo.com,Proxies",
     580 + "DOMAIN-SUFFIX,voxer.com,Proxies",
     581 + "DOMAIN-SUFFIX,vzw.com,Proxies",
     582 + "DOMAIN-SUFFIX,w3schools.com,Proxies",
     583 + "DOMAIN-SUFFIX,wattpad.com,Proxies",
     584 + "DOMAIN-SUFFIX,whoer.net,Proxies",
     585 + "DOMAIN-SUFFIX,wikimapia.org,Proxies",
     586 + "DOMAIN-SUFFIX,wikipedia.org,Proxies",
     587 + "DOMAIN-SUFFIX,wire.com,Proxies",
     588 + "DOMAIN-SUFFIX,worldcat.org,Proxies",
     589 + "DOMAIN-SUFFIX,wsj.com,Proxies",
     590 + "DOMAIN-SUFFIX,wsj.net,Proxies",
     591 + "DOMAIN-SUFFIX,xboxlive.com,Proxies",
     592 + "DOMAIN-SUFFIX,xvideos.com,Proxies",
     593 + "DOMAIN-SUFFIX,yahoo.com,Proxies",
     594 + "DOMAIN-SUFFIX,yesasia.com,Proxies",
     595 + "DOMAIN-SUFFIX,yes-news.com,Proxies",
     596 + "DOMAIN-SUFFIX,yomiuri.co.jp,Proxies",
     597 + "DOMAIN-SUFFIX,you-get.org,Proxies",
     598 + "DOMAIN-SUFFIX,zb.com,Proxies",
     599 + "DOMAIN-SUFFIX,zello.com,Proxies",
     600 + "DOMAIN-SUFFIX,zeronet.io,Proxies",
     601 + "DOMAIN,cdn-images.mailchimp.com,Proxies",
     602 + "DOMAIN,id.heroku.com,Proxies",
     603 + "DOMAIN-KEYWORD,github,Proxies",
     604 + "DOMAIN-KEYWORD,jav,Proxies",
     605 + "DOMAIN-KEYWORD,pinterest,Proxies",
     606 + "DOMAIN-KEYWORD,porn,Proxies",
     607 + "DOMAIN-KEYWORD,wikileaks,Proxies",
     608 + 
     609 + // (Region-Restricted Access Denied)
     610 + "DOMAIN-SUFFIX,apartmentratings.com,Proxies",
     611 + "DOMAIN-SUFFIX,apartments.com,Proxies",
     612 + "DOMAIN-SUFFIX,bankmobilevibe.com,Proxies",
     613 + "DOMAIN-SUFFIX,bing.com,Proxies",
     614 + "DOMAIN-SUFFIX,booktopia.com.au,Proxies",
     615 + "DOMAIN-SUFFIX,centauro.com.br,Proxies",
     616 + "DOMAIN-SUFFIX,clearsurance.com,Proxies",
     617 + "DOMAIN-SUFFIX,costco.com,Proxies",
     618 + "DOMAIN-SUFFIX,crackle.com,Proxies",
     619 + "DOMAIN-SUFFIX,depositphotos.cn,Proxies",
     620 + "DOMAIN-SUFFIX,dish.com,Proxies",
     621 + "DOMAIN-SUFFIX,dmm.co.jp,Proxies",
     622 + "DOMAIN-SUFFIX,dmm.com,Proxies",
     623 + "DOMAIN-SUFFIX,dnvod.tv,Proxies",
     624 + "DOMAIN-SUFFIX,esurance.com,Proxies",
     625 + "DOMAIN-SUFFIX,extmatrix.com,Proxies",
     626 + "DOMAIN-SUFFIX,fastpic.ru,Proxies",
     627 + "DOMAIN-SUFFIX,flipboard.com,Proxies",
     628 + "DOMAIN-SUFFIX,fnac.be,Proxies",
     629 + "DOMAIN-SUFFIX,fnac.com,Proxies",
     630 + "DOMAIN-SUFFIX,funkyimg.com,Proxies",
     631 + "DOMAIN-SUFFIX,fxnetworks.com,Proxies",
     632 + "DOMAIN-SUFFIX,gettyimages.com,Proxies",
     633 + "DOMAIN-SUFFIX,jcpenney.com,Proxies",
     634 + "DOMAIN-SUFFIX,kknews.cc,Proxies",
     635 + "DOMAIN-SUFFIX,nationwide.com,Proxies",
     636 + "DOMAIN-SUFFIX,nbc.com,Proxies",
     637 + "DOMAIN-SUFFIX,nordstrom.com,Proxies",
     638 + "DOMAIN-SUFFIX,nordstromimage.com,Proxies",
     639 + "DOMAIN-SUFFIX,nordstromrack.com,Proxies",
     640 + "DOMAIN-SUFFIX,read01.com,Proxies",
     641 + "DOMAIN-SUFFIX,superpages.com,Proxies",
     642 + "DOMAIN-SUFFIX,target.com,Proxies",
     643 + "DOMAIN-SUFFIX,thinkgeek.com,Proxies",
     644 + "DOMAIN-SUFFIX,tracfone.com,Proxies",
     645 + "DOMAIN-SUFFIX,uploader.jp,Proxies",
     646 + "DOMAIN-SUFFIX,vevo.com,Proxies",
     647 + "DOMAIN-SUFFIX,viu.tv,Proxies",
     648 + "DOMAIN-SUFFIX,vk.com,Proxies",
     649 + "DOMAIN-SUFFIX,vsco.co,Proxies",
     650 + "DOMAIN-SUFFIX,xfinity.com,Proxies",
     651 + "DOMAIN-SUFFIX,zattoo.com,Proxies",
     652 + "DOMAIN,abc.com,Proxies",
     653 + "DOMAIN,abc.go.com,Proxies",
     654 + "DOMAIN,abc.net.au,Proxies",
     655 + "DOMAIN,wego.here.com,Proxies",
     656 + // "USER-AGENT,Roam*,Proxies",
     657 + 
     658 + // (The Most Popular Sites)
     659 + // > Apple
     660 + // > Apple URL Shortener
     661 + "DOMAIN-SUFFIX,appsto.re,Proxies",
     662 + // > TestFlight
     663 + "DOMAIN,beta.itunes.apple.com,Proxies",
     664 + // > iBooks Store download
     665 + "DOMAIN,books.itunes.apple.com,Proxies",
     666 + // > iTunes Store Moveis Trailers
     667 + "DOMAIN,hls.itunes.apple.com,Proxies",
     668 + // App Store Preview
     669 + "DOMAIN,itunes.apple.com,Proxies",
     670 + // > Spotlight
     671 + "DOMAIN,api-glb-sea.smoot.apple.com,Proxies",
     672 + // > Dictionary
     673 + "DOMAIN,lookup-api.apple.com,Proxies",
     674 + "PROCESS-NAME,LookupViewService,Proxies",
     675 + // > Google
     676 + "DOMAIN-SUFFIX,abc.xyz,Proxies",
     677 + "DOMAIN-SUFFIX,android.com,Proxies",
     678 + "DOMAIN-SUFFIX,androidify.com,Proxies",
     679 + "DOMAIN-SUFFIX,dialogflow.com,Proxies",
     680 + "DOMAIN-SUFFIX,autodraw.com,Proxies",
     681 + "DOMAIN-SUFFIX,capitalg.com,Proxies",
     682 + "DOMAIN-SUFFIX,certificate-transparency.org,Proxies",
     683 + "DOMAIN-SUFFIX,chrome.com,Proxies",
     684 + "DOMAIN-SUFFIX,chromeexperiments.com,Proxies",
     685 + "DOMAIN-SUFFIX,chromestatus.com,Proxies",
     686 + "DOMAIN-SUFFIX,chromium.org,Proxies",
     687 + "DOMAIN-SUFFIX,creativelab5.com,Proxies",
     688 + "DOMAIN-SUFFIX,debug.com,Proxies",
     689 + "DOMAIN-SUFFIX,deepmind.com,Proxies",
     690 + "DOMAIN-SUFFIX,firebaseio.com,Proxies",
     691 + "DOMAIN-SUFFIX,getmdl.io,Proxies",
     692 + "DOMAIN-SUFFIX,ggpht.com,Proxies",
     693 + "DOMAIN-SUFFIX,gmail.com,Proxies",
     694 + "DOMAIN-SUFFIX,gmodules.com,Proxies",
     695 + "DOMAIN-SUFFIX,godoc.org,Proxies",
     696 + "DOMAIN-SUFFIX,golang.org,Proxies",
     697 + "DOMAIN-SUFFIX,gstatic.com,Proxies",
     698 + "DOMAIN-SUFFIX,gv.com,Proxies",
     699 + "DOMAIN-SUFFIX,gwtproject.org,Proxies",
     700 + "DOMAIN-SUFFIX,itasoftware.com,Proxies",
     701 + "DOMAIN-SUFFIX,madewithcode.com,Proxies",
     702 + "DOMAIN-SUFFIX,material.io,Proxies",
     703 + "DOMAIN-SUFFIX,polymer-project.org,Proxies",
     704 + "DOMAIN-SUFFIX,admin.recaptcha.net,Proxies",
     705 + "DOMAIN-SUFFIX,recaptcha.net,Proxies",
     706 + "DOMAIN-SUFFIX,shattered.io,Proxies",
     707 + "DOMAIN-SUFFIX,synergyse.com,Proxies",
     708 + "DOMAIN-SUFFIX,tensorflow.org,Proxies",
     709 + "DOMAIN-SUFFIX,tiltbrush.com,Proxies",
     710 + "DOMAIN-SUFFIX,waveprotocol.org,Proxies",
     711 + "DOMAIN-SUFFIX,waymo.com,Proxies",
     712 + "DOMAIN-SUFFIX,webmproject.org,Proxies",
     713 + "DOMAIN-SUFFIX,webrtc.org,Proxies",
     714 + "DOMAIN-SUFFIX,whatbrowser.org,Proxies",
     715 + "DOMAIN-SUFFIX,widevine.com,Proxies",
     716 + "DOMAIN-SUFFIX,x.company,Proxies",
     717 + "DOMAIN-SUFFIX,youtu.be,Proxies",
     718 + "DOMAIN-SUFFIX,yt.be,Proxies",
     719 + "DOMAIN-SUFFIX,ytimg.com,Proxies",
     720 + // > Steam
     721 + "DOMAIN-SUFFIX,steampowered.com,Direct",
     722 + // > Other
     723 + "DOMAIN-SUFFIX,0rz.tw,Proxies",
     724 + "DOMAIN-SUFFIX,4bluestones.biz,Proxies",
     725 + "DOMAIN-SUFFIX,9bis.net,Proxies",
     726 + "DOMAIN-SUFFIX,allconnected.co,Proxies",
     727 + "DOMAIN-SUFFIX,amazonaws.com,Proxies",
     728 + "DOMAIN-SUFFIX,aol.com,Proxies",
     729 + "DOMAIN-SUFFIX,bcc.com.tw,Proxies",
     730 + "DOMAIN-SUFFIX,bit.ly,Proxies",
     731 + "DOMAIN-SUFFIX,bitshare.com,Proxies",
     732 + "DOMAIN-SUFFIX,blog.jp,Proxies",
     733 + "DOMAIN-SUFFIX,blogimg.jp,Proxies",
     734 + "DOMAIN-SUFFIX,blogtd.org,Proxies",
     735 + "DOMAIN-SUFFIX,broadcast.co.nz,Proxies",
     736 + "DOMAIN-SUFFIX,camfrog.com,Proxies",
     737 + "DOMAIN-SUFFIX,cfos.de,Proxies",
     738 + "DOMAIN-SUFFIX,citypopulation.de,Proxies",
     739 + "DOMAIN-SUFFIX,cloudfront.net,Proxies",
     740 + "DOMAIN-SUFFIX,ctitv.com.tw,Proxies",
     741 + "DOMAIN-SUFFIX,cuhk.edu.hk,Proxies",
     742 + "DOMAIN-SUFFIX,cusu.hk,Proxies",
     743 + "DOMAIN-SUFFIX,discuss.com.hk,Proxies",
     744 + "DOMAIN-SUFFIX,dropboxapi.com,Proxies",
     745 + "DOMAIN-SUFFIX,edditstatic.com,Proxies",
     746 + "DOMAIN-SUFFIX,flickriver.com,Proxies",
     747 + "DOMAIN-SUFFIX,focustaiwan.tw,Proxies",
     748 + "DOMAIN-SUFFIX,free.fr,Proxies",
     749 + "DOMAIN-SUFFIX,ftchinese.com,Proxies",
     750 + "DOMAIN-SUFFIX,gigacircle.com,Proxies",
     751 + "DOMAIN-SUFFIX,gov,Proxies",
     752 + "DOMAIN-SUFFIX,hk-pub.com,Proxies",
     753 + "DOMAIN-SUFFIX,hosting.co.uk,Proxies",
     754 + "DOMAIN-SUFFIX,hwcdn.net,Proxies",
     755 + "DOMAIN-SUFFIX,jtvnw.net,Proxies",
     756 + "DOMAIN-SUFFIX,linksalpha.com,Proxies",
     757 + "DOMAIN-SUFFIX,manyvids.com,Proxies",
     758 + "DOMAIN-SUFFIX,myactimes.com,Proxies",
     759 + "DOMAIN-SUFFIX,newsblur.com,Proxies",
     760 + "DOMAIN-SUFFIX,now.im,Proxies",
     761 + "DOMAIN-SUFFIX,redditlist.com,Proxies",
     762 + "DOMAIN-SUFFIX,signal.org,Proxies",
     763 + "DOMAIN-SUFFIX,sparknotes.com,Proxies",
     764 + "DOMAIN-SUFFIX,streetvoice.com,Proxies",
     765 + "DOMAIN-SUFFIX,ttvnw.net,Proxies",
     766 + "DOMAIN-SUFFIX,tv.com,Proxies",
     767 + "DOMAIN-SUFFIX,twitchcdn.net,Proxies",
     768 + "DOMAIN-SUFFIX,typepad.com,Proxies",
     769 + "DOMAIN-SUFFIX,udnbkk.com,Proxies",
     770 + "DOMAIN-SUFFIX,whispersystems.org,Proxies",
     771 + "DOMAIN-SUFFIX,wikia.com,Proxies",
     772 + "DOMAIN-SUFFIX,wn.com,Proxies",
     773 + "DOMAIN-SUFFIX,wolframalpha.com,Proxies",
     774 + "DOMAIN-SUFFIX,x-art.com,Proxies",
     775 + "DOMAIN-SUFFIX,yimg.com,Proxies",
     776 + 
     777 + // Local Area Network
     778 + "DOMAIN-KEYWORD,announce,DIRECT",
     779 + "DOMAIN-KEYWORD,torrent,DIRECT",
     780 + "DOMAIN-KEYWORD,tracker,DIRECT",
     781 + "DOMAIN-SUFFIX,smtp,DIRECT",
     782 + "DOMAIN-SUFFIX,local,DIRECT",
     783 + "IP-CIDR,192.168.0.0/16,DIRECT",
     784 + "IP-CIDR,10.0.0.0/8,DIRECT",
     785 + "IP-CIDR,172.16.0.0/12,DIRECT",
     786 + "IP-CIDR,127.0.0.0/8,DIRECT",
     787 + "IP-CIDR,100.64.0.0/10,DIRECT",
     788 + 
     789 + // > IQIYI
     790 + "IP-CIDR,101.227.0.0/16,Direct",
     791 + "IP-CIDR,101.224.0.0/13,Direct",
     792 + "IP-CIDR,119.176.0.0/12,Direct",
     793 + 
     794 + // > Youku
     795 + "IP-CIDR,106.11.0.0/16,Direct",
     796 + 
     797 + // > Telegram
     798 + "IP-CIDR,67.198.55.0/24,Proxies",
     799 + "IP-CIDR,91.108.4.0/22,Proxies",
     800 + "IP-CIDR,91.108.8.0/22,Proxies",
     801 + "IP-CIDR,91.108.12.0/22,Proxies",
     802 + "IP-CIDR,91.108.16.0/22,Proxies",
     803 + "IP-CIDR,91.108.56.0/22,Proxies",
     804 + "IP-CIDR,109.239.140.0/24,Proxies",
     805 + "IP-CIDR,149.154.160.0/20,Proxies",
     806 + "IP-CIDR,205.172.60.0/22,Proxies",
     807 + 
     808 + // (Extra IP-CIRD)
     809 + // > Google
     810 + "IP-CIDR,35.190.247.0/24,Proxies",
     811 + "IP-CIDR,64.233.160.0/19,Proxies",
     812 + "IP-CIDR,66.102.0.0/20,Proxies",
     813 + "IP-CIDR,66.249.80.0/20,Proxies",
     814 + "IP-CIDR,72.14.192.0/18,Proxies",
     815 + "IP-CIDR,74.125.0.0/16,Proxies",
     816 + "IP-CIDR,108.177.8.0/21,Proxies",
     817 + "IP-CIDR,172.217.0.0/16,Proxies",
     818 + "IP-CIDR,173.194.0.0/16,Proxies",
     819 + "IP-CIDR,209.85.128.0/17,Proxies",
     820 + "IP-CIDR,216.58.192.0/19,Proxies",
     821 + "IP-CIDR,216.239.32.0/19,Proxies",
     822 + // > Facebook
     823 + "IP-CIDR,31.13.24.0/21,Proxies",
     824 + "IP-CIDR,31.13.64.0/18,Proxies",
     825 + "IP-CIDR,45.64.40.0/22,Proxies",
     826 + "IP-CIDR,66.220.144.0/20,Proxies",
     827 + "IP-CIDR,69.63.176.0/20,Proxies",
     828 + "IP-CIDR,69.171.224.0/19,Proxies",
     829 + "IP-CIDR,74.119.76.0/22,Proxies",
     830 + "IP-CIDR,103.4.96.0/22,Proxies",
     831 + "IP-CIDR,129.134.0.0/17,Proxies",
     832 + "IP-CIDR,157.240.0.0/17,Proxies",
     833 + "IP-CIDR,173.252.64.0/19,Proxies",
     834 + "IP-CIDR,173.252.96.0/19,Proxies",
     835 + "IP-CIDR,179.60.192.0/22,Proxies",
     836 + "IP-CIDR,185.60.216.0/22,Proxies",
     837 + "IP-CIDR,204.15.20.0/22,Proxies",
     838 + // > Twitter
     839 + "IP-CIDR,69.195.160.0/19,Proxies",
     840 + "IP-CIDR,104.244.42.0/21,Proxies",
     841 + "IP-CIDR,192.133.76.0/22,Proxies",
     842 + "IP-CIDR,199.16.156.0/22,Proxies",
     843 + "IP-CIDR,199.59.148.0/22,Proxies",
     844 + "IP-CIDR,199.96.56.0/21,Proxies",
     845 + "IP-CIDR,202.160.128.0/22,Proxies",
     846 + "IP-CIDR,209.237.192.0/19,Proxies",
     847 + 
     848 + // GeoIP China
     849 + "GEOIP,CN,Direct",
     850 +}
     851 + 
  • ■ ■ ■ ■ ■ ■
    pkg/api/models/tunnel.go
    1 1  package models
    2 2   
    3 3  import (
     4 + "encoding/base64"
     5 + "fmt"
    4 6   "time"
    5 7   
     8 + "gopkg.in/yaml.v3"
    6 9   "gorm.io/gorm"
    7 10   
    8 11   "github.com/DVKunion/SeaMoon/pkg/api/enum"
    skipped 97 lines
    106 109   res = append(res, api.(*TunnelApi))
    107 110   }
    108 111   return res
     112 +}
     113 + 
     114 +func (tl TunnelList) ToConfig(p string) []byte {
     115 + switch p {
     116 + case "clash":
     117 + cc := ClashConfig{
     118 + MixedPort: 7890,
     119 + AllowLan: false,
     120 + LogLevel: "info",
     121 + ExternalController: "127.0.0.1:9090",
     122 + Secret: "",
     123 + //DNS: ClashDNS{
     124 + // Enable: true,
     125 + // Ipv6: false,
     126 + // Listen: "127.0.0.1:5353",
     127 + // EnhancedMode: "fake-ip",
     128 + // FakeIPFilter: []string{"*.lan"},
     129 + // Nameserver:
     130 + //},
     131 + Proxies: make([]ClashProxies, 0),
     132 + ProxyGroups: []ClashProxyGroups{
     133 + {
     134 + Name: "Proxies",
     135 + Type: "select",
     136 + Proxies: make([]string, 0),
     137 + },
     138 + {
     139 + Name: "Direct",
     140 + Type: "select",
     141 + Proxies: []string{"DIRECT"},
     142 + },
     143 + },
     144 + Rules: BindingRules,
     145 + }
     146 + for _, t := range tl {
     147 + cc.Proxies = append(cc.Proxies, ClashProxies{
     148 + Name: *t.Name + "-" + t.Config.Region + "-vmess",
     149 + Type: "vmess",
     150 + Server: *t.Addr,
     151 + Port: func() int {
     152 + if t.Config.TLS {
     153 + return 443
     154 + }
     155 + return 80
     156 + }(),
     157 + UUID: t.Config.V2rayUid,
     158 + NetWork: "ws",
     159 + TLS: t.Config.TLS,
     160 + SkipCertVerify: !t.Config.TLS,
     161 + Cipher: "auto",
     162 + AlterId: 0,
     163 + WsOpts: struct {
     164 + Path string `yaml:"path,omitempty"`
     165 + }(struct{ Path string }{Path: "/vmess"}),
     166 + })
     167 + //cc.Proxies = append(cc.Proxies, ClashProxies{
     168 + // Name: *t.Name + "-" + t.Config.Region + "-vless",
     169 + // Type: "vless",
     170 + // Server: *t.Addr,
     171 + // Port: func() int {
     172 + // if t.Config.TLS {
     173 + // return 443
     174 + // }
     175 + // return 80
     176 + // }(),
     177 + // UUID: t.Config.V2rayUid,
     178 + // UDP: false,
     179 + // NetWork: "ws",
     180 + // TLS: t.Config.TLS,
     181 + // SkipCertVerify: !t.Config.TLS,
     182 + // Cipher: "auto",
     183 + // AlterId: 0,
     184 + // WsOpts: struct {
     185 + // Path string `yaml:"path,omitempty"`
     186 + // }(struct{ Path string }{Path: "/vless"}),
     187 + //})
     188 + cc.ProxyGroups[0].Proxies = append(cc.ProxyGroups[0].Proxies, *t.Name+"-"+t.Config.Region+"-vmess")
     189 + //cc.ProxyGroups[0].Proxies = append(cc.ProxyGroups[0].Proxies, *t.Name+"-"+t.Config.Region+"-vless")
     190 + }
     191 + data, err := yaml.Marshal(&cc)
     192 + if err != nil {
     193 + 
     194 + }
     195 + return data
     196 + case "shadowrocket":
     197 + res := ""
     198 + for _, t := range tl {
     199 + res += fmt.Sprintf("vmess://%s?remarks=%s&path=/vmess&obfs=%s&tls=%d&alterId=0\n",
     200 + base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("auto:%s@%s:%v", t.Config.V2rayUid, *t.Addr, func() int {
     201 + if t.Config.TLS {
     202 + return 443
     203 + }
     204 + return 80
     205 + }()))),
     206 + fmt.Sprintf("%s-%s-%s", *t.Name, t.Config.Region, "vmess"),
     207 + t.Type.String(),
     208 + func() int {
     209 + if t.Config.TLS {
     210 + return 1
     211 + }
     212 + return 0
     213 + }(),
     214 + )
     215 + res += fmt.Sprintf("vless://%s?remarks=%s&path=/vless&obfs=%s&tls=%d&alterId=0\n",
     216 + base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("auto:%s@%s:%v", t.Config.V2rayUid, *t.Addr, func() int {
     217 + if t.Config.TLS {
     218 + return 443
     219 + }
     220 + return 80
     221 + }()))),
     222 + fmt.Sprintf("%s-%s-%s", *t.Name, t.Config.Region, "vless"),
     223 + t.Type.String(),
     224 + func() int {
     225 + if t.Config.TLS {
     226 + return 1
     227 + }
     228 + return 0
     229 + }(),
     230 + )
     231 + }
     232 + return []byte(base64.URLEncoding.EncodeToString([]byte(res)))
     233 + }
     234 + return nil
    109 235  }
    110 236   
    111 237  func (ta TunnelCreateApi) ToModel(full bool) *Tunnel {
    skipped 9 lines
  • pkg/signal/handler_provider.go pkg/api/signal/handler_provider.go
    Content is identical
  • pkg/signal/handler_proxy.go pkg/api/signal/handler_proxy.go
    Content is identical
  • pkg/signal/handler_tunnel.go pkg/api/signal/handler_tunnel.go
    Content is identical
  • pkg/signal/signal.go pkg/api/signal/signal.go
    Content is identical
  • ■ ■ ■ ■ ■ ■
    pkg/listener/tcp.go
    skipped 9 lines
    10 10   "github.com/DVKunion/SeaMoon/pkg/network"
    11 11   "github.com/DVKunion/SeaMoon/pkg/system/errors"
    12 12   "github.com/DVKunion/SeaMoon/pkg/system/xlog"
     13 + "github.com/DVKunion/SeaMoon/pkg/transfer"
    13 14   "github.com/DVKunion/SeaMoon/pkg/tunnel/service"
    14 15  )
    15 16   
    skipped 6 lines
    22 23   if err != nil {
    23 24   return nil, err
    24 25   }
    25  - go listen(ctx, server, py.ID, py.Type, tun)
     26 + switch *py.Type {
     27 + case enum.ProxyTypeAUTO, enum.ProxyTypeHTTP, enum.ProxyTypeSOCKS5:
     28 + go listen(ctx, server, py.ID, py.Type, tun)
     29 + case enum.ProxyTypeShadowSocks, enum.ProxyTypeVmess, enum.ProxyTypeVless:
     30 + go v2rayListen(ctx, server, py.ID, py.Type, tun)
     31 + }
    26 32   return server, nil
    27 33  }
    28 34   
    skipped 36 lines
    65 71   }
    66 72  }
    67 73   
     74 +// 懒得写了,直接套一个 v2ray 的客户端去做 wrapper 原本的conn完事了
     75 +func v2rayListen(ctx context.Context, server net.Listener, id uint, t *enum.ProxyType, tun *models.Tunnel) {
     76 + config := transfer.NewV2rayConfig(
     77 + transfer.WithClientMod(),
     78 + transfer.WithNetAddr(*tun.Addr, func() uint32 {
     79 + if tun.Config.TLS {
     80 + return 443
     81 + }
     82 + return 80
     83 + }()),
     84 + transfer.WithTunnelType(t.String(), *tun.Type),
     85 + transfer.WithAuthInfo(tun.Config.V2rayUid, tun.Config.SSRCrypt, tun.Config.SSRPass),
     86 + transfer.WithExtra(tun.Config.Tor, tun.Config.TLS),
     87 + )
     88 + err := transfer.Init(config)
     89 + if err != nil {
     90 + xlog.Error(xlog.ListenerV2rayInitError, "err", err)
     91 + return
     92 + }
     93 + for {
     94 + conn, err := server.Accept()
     95 + if err != nil {
     96 + if errors.Is(err, net.ErrClosed) {
     97 + // 说明是 server 被外部 close 掉了,导致了此处的 accept 报错
     98 + // 正常现象,return 即可。
     99 + return
     100 + } else {
     101 + // 除此之外,都为异常。为了保证服务正常不出现 panic 和空指针,跳过该 conn
     102 + xlog.Error(xlog.ListenerAcceptError, "err", err)
     103 + continue
     104 + }
     105 + }
     106 + db_service.SVC.UpdateProxyConn(ctx, id, 1)
     107 + go func() {
     108 + defer db_service.SVC.UpdateProxyConn(ctx, id, -1)
     109 + if err := transfer.AutoTransportV2ray(conn); err != nil {
     110 + xlog.Error(xlog.ListenerV2rayTransportError, "err", err)
     111 + }
     112 + }()
     113 + }
     114 +}
     115 + 
  • ■ ■ ■ ■ ■ ■
    pkg/listener/v2ray.go
    1  -package listener
    2  - 
    3  -// 懒得写了,直接套一个 v2ray 的客户端去做 wrapper 原本的conn完事了
    4  - 
  • ■ ■ ■ ■
    pkg/network/transport.go
    skipped 56 lines
    57 57   // 忽略 websocket 正常断开
    58 58   if opErr, ok := e.(*net.OpError); ok {
    59 59   if closeErr, ok := opErr.Err.(*websocket.CloseError); ok {
    60  - if closeErr.Code == websocket.CloseNormalClosure {
     60 + if closeErr.Code == websocket.CloseNormalClosure || closeErr.Code == websocket.CloseAbnormalClosure {
    61 61   e = nil
    62 62   }
    63 63   }
    skipped 13 lines
  • ■ ■ ■ ■ ■ ■
    pkg/system/xlog/consts.go
    skipped 101 lines
    102 102   
    103 103  // LISTENER 相关错误
    104 104  const (
    105  - ListenerAcceptError = "listener unexpect error"
    106  - ListenerDailError = "listener dail to remote error"
     105 + ListenerAcceptError = "listener unexpect error"
     106 + ListenerDailError = "listener dail to remote error"
     107 + ListenerV2rayInitError = "listener init v2ray config error"
     108 + ListenerV2rayTransportError = "listener transport v2ray error"
    107 109  )
    108 110   
    109 111  // SIGNAL 相关错误
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    pkg/transfer/auto.go
    skipped 18 lines
    19 19   return Socks5Transport(br, true)
    20 20  }
    21 21   
     22 +func AutoTransportV2ray(conn net.Conn) error {
     23 + br := &network.BufferedConn{Conn: conn, Br: bufio.NewReader(conn)}
     24 + b, err := br.Peek(1)
     25 + 
     26 + if err != nil || b[0] != network.SOCKS5Version {
     27 + return V2rayTransport(br, "http")
     28 + }
     29 + 
     30 + return V2rayTransport(br, "socks5")
     31 +}
     32 + 
  • ■ ■ ■ ■ ■ ■
    pkg/transfer/http.go
    skipped 7 lines
    8 8   "github.com/DVKunion/SeaMoon/pkg/network"
    9 9  )
    10 10   
     11 +type HttpTransfer struct {
     12 +}
     13 + 
     14 +func UnWrapper() {
     15 + 
     16 +}
     17 + 
    11 18  func HttpTransport(conn net.Conn) error {
    12 19   // 接收客户端的连接,并从第一条消息中获取目标地址
    13 20   request, err := http.ReadRequest(bufio.NewReader(conn))
    skipped 53 lines
  • ■ ■ ■ ■ ■
    pkg/transfer/v2ray.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "context"
    5  - "encoding/json"
    6  - "fmt"
    7 5   
    8 6   core "github.com/v2fly/v2ray-core/v5"
    9 7   "github.com/v2fly/v2ray-core/v5/app/dispatcher"
    skipped 2 lines
    12 10   "github.com/v2fly/v2ray-core/v5/common/session"
    13 11   "github.com/v2fly/v2ray-core/v5/features/inbound"
    14 12   "github.com/v2fly/v2ray-core/v5/features/routing"
    15  - "github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon"
    16  - "github.com/v2fly/v2ray-core/v5/infra/conf/synthetic/log"
    17  - v4 "github.com/v2fly/v2ray-core/v5/infra/conf/v4"
    18 13   _ "github.com/v2fly/v2ray-core/v5/main/distro/all"
    19  - 
    20  - "github.com/DVKunion/SeaMoon/pkg/api/enum"
    21 14  )
    22 15   
    23  -const handleTag = "seamoon-"
    24  - 
    25  -var v2ray *core.Instance
    26  - 
    27  -func InitV2rayServer(port uint32, id, pass, crypt string, tp enum.TunnelType, tor bool, tls bool) error {
    28  - config, err := renderConfig(port, id, pass, crypt, tp, tor, tls)
     16 +func Init(cfg *v2rayConfig) error {
     17 + config, err := cfg.Build()
    29 18   if err != nil {
    30 19   return err
    31 20   }
    skipped 4 lines
    36 25   return nil
    37 26  }
    38 27   
    39  -// V2rayTransport v2ray 相关协议支持: vmess / vless
     28 +// V2rayTransport v2ray 相关协议支持: vmess / vless / shadowsock
    40 29  // 这是一个偷懒的版本,并没有详细的研究对应协议的具体通信解析方案, 直接集成了 v2ray-core, 并且实现的相当的简陋。
    41 30  // 还是期望能够和 socks5 一样保持一致是最好的
    42 31  // proto 来自己做 dispatch
    skipped 21 lines
    64 53   ctx = session.ContextWithContent(ctx, content)
    65 54   
    66 55   dispatch := v2ray.GetFeature(routing.DispatcherType()).(*dispatcher.DefaultDispatcher)
    67  - err = worker.Process(ctx, net.Network_TCP, conn, dispatch)
    68  - return err
    69  -}
    70  - 
    71  -func renderSetting(proto string, crypt string, pass string) *[]byte {
    72  - str := ""
    73  - switch proto {
    74  - case "vmess":
    75  - str = fmt.Sprintf(`{
    76  - "clients": [
    77  - {
    78  - "id": "%s",
    79  - "alterId": 0
    80  - }
    81  - ],
    82  - "decryption":"auto"
    83  -}`, pass)
    84  - case "vless":
    85  - str = fmt.Sprintf(`{
    86  - "clients": [
    87  - {
    88  - "id": "%s",
    89  - "alterId": 0
    90  - }
    91  - ],
    92  - "decryption":"none"
    93  -}`, pass)
    94  - case "shadowsocks":
    95  - str = fmt.Sprintf(`{
    96  - "method": "%s",
    97  - "password": "%s",
    98  - "network": "tcp"
    99  -}`, crypt, pass)
    100  - }
    101  - s := []byte(str)
    102  - return &s
    103  -}
    104  - 
    105  -func renderConfig(port uint32, id string, pass string, crypt string, tp enum.TunnelType, tor bool, tls bool) (*core.Config, error) {
    106  - t := v4.Config{
    107  - LogConfig: &log.LogConfig{
    108  - AccessLog: "v2ray_access.log",
    109  - ErrorLog: "v2ray_error.log",
    110  - LogLevel: "ERROR",
    111  - },
    112  - InboundConfigs: make([]v4.InboundDetourConfig, 0),
    113  - OutboundConfigs: []v4.OutboundDetourConfig{
    114  - outboundConfig(tor),
    115  - },
    116  - }
    117  - if id != "" {
    118  - t.InboundConfigs = append(t.InboundConfigs, v4.InboundDetourConfig{
    119  - Protocol: "vmess",
    120  - // 暂时不支持动态端口
    121  - PortRange: &cfgcommon.PortRange{
    122  - From: port,
    123  - To: port,
    124  - },
    125  - Settings: (*json.RawMessage)(renderSetting("vmess", crypt, id)),
    126  - Tag: handleTag + "vmess",
    127  - StreamSetting: &v4.StreamConfig{
    128  - Network: (*v4.TransportProtocol)(tp.ToPtr()),
    129  - Security: func() string {
    130  - if tls {
    131  - return "tls"
    132  - }
    133  - return ""
    134  - }(),
    135  - WSSettings: &v4.WebSocketConfig{
    136  - Path: "/vmess",
    137  - },
    138  - },
    139  - })
    140  - t.InboundConfigs = append(t.InboundConfigs, v4.InboundDetourConfig{
    141  - Protocol: "vless",
    142  - // 暂时不支持动态端口
    143  - PortRange: &cfgcommon.PortRange{
    144  - From: port,
    145  - To: port,
    146  - },
    147  - Settings: (*json.RawMessage)(renderSetting("vless", crypt, id)),
    148  - Tag: handleTag + "vless",
    149  - StreamSetting: &v4.StreamConfig{
    150  - Network: (*v4.TransportProtocol)(tp.ToPtr()),
    151  - Security: "tls",
    152  - WSSettings: &v4.WebSocketConfig{
    153  - Path: "/vless",
    154  - },
    155  - },
    156  - })
    157  - }
    158  - 
    159  - if pass != "" && crypt != "" {
    160  - t.InboundConfigs = append(t.InboundConfigs, v4.InboundDetourConfig{
    161  - Protocol: "shadowsocks",
    162  - PortRange: &cfgcommon.PortRange{
    163  - From: port,
    164  - To: port,
    165  - },
    166  - Settings: (*json.RawMessage)(renderSetting("shadowsocks", crypt, pass)),
    167  - Tag: handleTag + "shadowsocks",
    168  - StreamSetting: &v4.StreamConfig{
    169  - Network: (*v4.TransportProtocol)(tp.ToPtr()),
    170  - Security: "tls",
    171  - WSSettings: &v4.WebSocketConfig{
    172  - Path: "/v-shadowsocks",
    173  - },
    174  - },
    175  - })
    176  - }
    177  - return t.Build()
    178  -}
    179  - 
    180  -func outboundConfig(tor bool) v4.OutboundDetourConfig {
    181  - torSetting := []byte(`{
    182  - "servers": [
    183  - {
    184  - "address": "127.0.0.1",
    185  - "port": 9050
    186  - }
    187  - ]
    188  - }`)
    189  - empty := []byte("{}")
    190  - if tor {
    191  - return v4.OutboundDetourConfig{
    192  - Protocol: "socks",
    193  - Settings: (*json.RawMessage)(&torSetting),
    194  - }
    195  - }
    196  - return v4.OutboundDetourConfig{
    197  - Protocol: "freedom",
    198  - Settings: (*json.RawMessage)(&empty),
    199  - }
     56 + return worker.Process(ctx, net.Network_TCP, conn, dispatch)
    200 57  }
    201 58   
  • ■ ■ ■ ■ ■ ■
    pkg/transfer/v2ray_config.go
     1 +package transfer
     2 + 
     3 +import (
     4 + "encoding/json"
     5 + "fmt"
     6 + 
     7 + core "github.com/v2fly/v2ray-core/v5"
     8 + "github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon"
     9 + "github.com/v2fly/v2ray-core/v5/infra/conf/synthetic/log"
     10 + v4 "github.com/v2fly/v2ray-core/v5/infra/conf/v4"
     11 + 
     12 + "github.com/DVKunion/SeaMoon/pkg/api/enum"
     13 +)
     14 + 
     15 +const handleTag = "seamoon-"
     16 + 
     17 +var (
     18 + v2ray *core.Instance
     19 + empty = []byte("{}")
     20 +)
     21 + 
     22 +type v2rayConfig struct {
     23 + mode string
     24 + addr string // 用于出站的协议地址
     25 + port uint32 //
     26 + 
     27 + id string
     28 + pass string
     29 + crypt string
     30 + 
     31 + proto string
     32 + tp enum.TunnelType
     33 + 
     34 + tor bool
     35 + tls bool
     36 +}
     37 + 
     38 +type ConfigOpt func(config *v2rayConfig)
     39 + 
     40 +func WithServerMod() ConfigOpt {
     41 + return func(config *v2rayConfig) {
     42 + config.mode = "server"
     43 + }
     44 +}
     45 + 
     46 +func WithClientMod() ConfigOpt {
     47 + return func(config *v2rayConfig) {
     48 + config.mode = "client"
     49 + }
     50 +}
     51 + 
     52 +func WithNetAddr(addr string, port uint32) ConfigOpt {
     53 + return func(config *v2rayConfig) {
     54 + config.addr = addr
     55 + config.port = port
     56 + }
     57 +}
     58 + 
     59 +func WithAuthInfo(id, crypt, pass string) ConfigOpt {
     60 + return func(config *v2rayConfig) {
     61 + config.id = id
     62 + config.crypt = crypt
     63 + config.pass = pass
     64 + }
     65 +}
     66 + 
     67 +func WithExtra(tor, tls bool) ConfigOpt {
     68 + return func(config *v2rayConfig) {
     69 + config.tor = tor
     70 + config.tls = tls
     71 + }
     72 +}
     73 + 
     74 +func WithTunnelType(proto string, tp enum.TunnelType) ConfigOpt {
     75 + return func(config *v2rayConfig) {
     76 + config.proto = proto
     77 + config.tp = tp
     78 + }
     79 +}
     80 + 
     81 +func NewV2rayConfig(opts ...ConfigOpt) *v2rayConfig {
     82 + v := &v2rayConfig{}
     83 + for _, o := range opts {
     84 + o(v)
     85 + }
     86 + return v
     87 +}
     88 + 
     89 +func (v v2rayConfig) Build() (*core.Config, error) {
     90 + t := &v4.Config{
     91 + LogConfig: &log.LogConfig{
     92 + AccessLog: "v2ray_access.log",
     93 + ErrorLog: "v2ray_error.log",
     94 + LogLevel: "ERROR",
     95 + },
     96 + InboundConfigs: v.InboundConfig(),
     97 + OutboundConfigs: v.OutboundConfig(),
     98 + }
     99 + 
     100 + return t.Build()
     101 +}
     102 + 
     103 +func (v v2rayConfig) InboundConfig() []v4.InboundDetourConfig {
     104 + cs := make([]v4.InboundDetourConfig, 0)
     105 + switch v.mode {
     106 + case "server":
     107 + if v.id != "" {
     108 + cs = append(cs, v.vlessInboundConfig())
     109 + cs = append(cs, v.vmessInboundConfig())
     110 + }
     111 + if v.crypt != "" && v.pass != "" {
     112 + cs = append(cs, v.shadowsocksInboundConfig())
     113 + }
     114 + case "client":
     115 + cs = append(cs, v.httpInboundConfig())
     116 + cs = append(cs, v.socks5InboundConfig())
     117 + }
     118 + return cs
     119 +}
     120 + 
     121 +func (v v2rayConfig) OutboundConfig() []v4.OutboundDetourConfig {
     122 + cs := make([]v4.OutboundDetourConfig, 0)
     123 + switch v.mode {
     124 + case "server":
     125 + if v.tor {
     126 + cs = append(cs, v.torOutboundConfig())
     127 + } else {
     128 + cs = append(cs, v.freedomOutboundConfig())
     129 + }
     130 + case "client":
     131 + switch v.proto {
     132 + case "vmess":
     133 + cs = append(cs, v.vmessOutboundConfig())
     134 + case "vless":
     135 + cs = append(cs, v.vlessOutboundConfig())
     136 + case "shadowsocks":
     137 + cs = append(cs, v.shadowsocksOutboundConfig())
     138 + }
     139 + }
     140 + return cs
     141 +}
     142 + 
     143 +func (v v2rayConfig) StreamSetting(proto string) *v4.StreamConfig {
     144 + switch v.tp {
     145 + case enum.TunnelTypeWST:
     146 + return v.streamWebsocketSetting(proto)
     147 + case enum.TunnelTypeGRT:
     148 + return v.streamGrpcSetting(proto)
     149 + }
     150 + return nil
     151 +}
     152 + 
     153 +func (v v2rayConfig) streamGrpcSetting(proto string) *v4.StreamConfig {
     154 + return nil
     155 +}
     156 + 
     157 +func (v v2rayConfig) streamWebsocketSetting(proto string) *v4.StreamConfig {
     158 + return &v4.StreamConfig{
     159 + Network: (*v4.TransportProtocol)(v.tp.ToPtr()),
     160 + Security: func() string {
     161 + if v.tls {
     162 + return "tls"
     163 + }
     164 + return ""
     165 + }(),
     166 + WSSettings: &v4.WebSocketConfig{
     167 + Path: "/" + proto,
     168 + },
     169 + }
     170 +}
     171 + 
     172 +func (v v2rayConfig) httpInboundConfig() v4.InboundDetourConfig {
     173 + vc := []byte(`{
     174 + "accounts": [],
     175 + "allowTransparent": false
     176 + }`)
     177 + return v4.InboundDetourConfig{
     178 + Protocol: "http",
     179 + PortRange: &cfgcommon.PortRange{
     180 + From: v.port,
     181 + To: v.port,
     182 + },
     183 + Settings: (*json.RawMessage)(&vc),
     184 + Tag: handleTag + "http",
     185 + }
     186 +}
     187 + 
     188 +func (v v2rayConfig) socks5InboundConfig() v4.InboundDetourConfig {
     189 + vc := []byte(`{
     190 + "auth": "noauth",
     191 + "accounts": [],
     192 + "udp": false
     193 + }`)
     194 + return v4.InboundDetourConfig{
     195 + Protocol: "socks",
     196 + PortRange: &cfgcommon.PortRange{
     197 + From: v.port,
     198 + To: v.port,
     199 + },
     200 + Settings: (*json.RawMessage)(&vc),
     201 + Tag: handleTag + "socks5",
     202 + }
     203 +}
     204 + 
     205 +func (v v2rayConfig) shadowsocksOutboundConfig() v4.OutboundDetourConfig {
     206 + vc := []byte(fmt.Sprintf(`{
     207 + "servers": [
     208 + {
     209 + "address": "%s",
     210 + "port": %d,
     211 + "method": "%s"
     212 + "password": "%s"
     213 + }
     214 + ]
     215 +}`, v.addr, v.port, v.crypt, v.pass))
     216 + return v4.OutboundDetourConfig{
     217 + Protocol: "shadowsocks",
     218 + Settings: (*json.RawMessage)(&vc),
     219 + Tag: handleTag + "shadowsocks",
     220 + StreamSetting: v.StreamSetting("v-shadowsocks"),
     221 + }
     222 +}
     223 + 
     224 +func (v v2rayConfig) shadowsocksInboundConfig() v4.InboundDetourConfig {
     225 + vc := []byte(fmt.Sprintf(`{
     226 + "method": "%s",
     227 + "password": "%s",
     228 + "network": "tcp"
     229 +}`, v.crypt, v.pass))
     230 + return v4.InboundDetourConfig{
     231 + Protocol: "shadowsocks",
     232 + PortRange: &cfgcommon.PortRange{
     233 + From: v.port,
     234 + To: v.port,
     235 + },
     236 + Settings: (*json.RawMessage)(&vc),
     237 + Tag: handleTag + "shadowsocks",
     238 + StreamSetting: v.StreamSetting("v-shadowsocks"),
     239 + }
     240 +}
     241 + 
     242 +func (v v2rayConfig) vmessOutboundConfig() v4.OutboundDetourConfig {
     243 + outSetting := []byte(fmt.Sprintf(`{
     244 + "vnext": [
     245 + {
     246 + "address": "%s",
     247 + "port": %d,
     248 + "users": [
     249 + {
     250 + "alterId": 0,
     251 + "id": "%s",
     252 + "security": "auto"
     253 + }
     254 + ]
     255 + }
     256 + ]
     257 +}`, v.addr, v.port, v.id))
     258 + return v4.OutboundDetourConfig{
     259 + Protocol: "vmess",
     260 + Tag: handleTag + "vmess",
     261 + Settings: (*json.RawMessage)(&outSetting),
     262 + StreamSetting: v.StreamSetting("vmess"),
     263 + }
     264 +}
     265 + 
     266 +func (v v2rayConfig) vmessInboundConfig() v4.InboundDetourConfig {
     267 + vc := []byte(fmt.Sprintf(`{
     268 + "clients": [
     269 + {
     270 + "id": "%s",
     271 + "alterId": 0
     272 + }
     273 + ],
     274 + "decryption":"auto"
     275 +}`, v.id))
     276 + return v4.InboundDetourConfig{
     277 + Protocol: "vmess",
     278 + PortRange: &cfgcommon.PortRange{
     279 + From: v.port,
     280 + To: v.port,
     281 + },
     282 + Settings: (*json.RawMessage)(&vc),
     283 + Tag: handleTag + "vmess",
     284 + StreamSetting: v.StreamSetting("vmess"),
     285 + }
     286 +}
     287 + 
     288 +func (v v2rayConfig) vlessOutboundConfig() v4.OutboundDetourConfig {
     289 + outSetting := []byte(fmt.Sprintf(`{
     290 + "vnext": [
     291 + {
     292 + "address": "%s",
     293 + "port": %d,
     294 + "users": [
     295 + {
     296 + "alterId": 0,
     297 + "id": "%s",
     298 + "security": "auto",
     299 + "encryption": "none"
     300 + }
     301 + ]
     302 + }
     303 + ]
     304 +}`, v.addr, v.port, v.id))
     305 + return v4.OutboundDetourConfig{
     306 + Protocol: "vless",
     307 + Settings: (*json.RawMessage)(&outSetting),
     308 + Tag: handleTag + "vless",
     309 + StreamSetting: v.StreamSetting("vless"),
     310 + }
     311 +}
     312 + 
     313 +func (v v2rayConfig) vlessInboundConfig() v4.InboundDetourConfig {
     314 + vc := []byte(fmt.Sprintf(`{
     315 + "clients": [
     316 + {
     317 + "id": "%s",
     318 + "alterId": 0
     319 + }
     320 + ],
     321 + "decryption":"none"
     322 +}`, v.id))
     323 + return v4.InboundDetourConfig{
     324 + Protocol: "vless",
     325 + PortRange: &cfgcommon.PortRange{
     326 + From: v.port,
     327 + To: v.port,
     328 + },
     329 + Settings: (*json.RawMessage)(&vc),
     330 + Tag: handleTag + "vless",
     331 + StreamSetting: v.StreamSetting("vless"),
     332 + }
     333 +}
     334 + 
     335 +func (v v2rayConfig) torOutboundConfig() v4.OutboundDetourConfig {
     336 + torSetting := []byte(`{
     337 + "servers": [
     338 + {
     339 + "address": "127.0.0.1",
     340 + "port": 9050
     341 + }
     342 + ]
     343 + }`)
     344 + return v4.OutboundDetourConfig{
     345 + Protocol: "socks",
     346 + Settings: (*json.RawMessage)(&torSetting),
     347 + }
     348 +}
     349 + 
     350 +func (v v2rayConfig) freedomOutboundConfig() v4.OutboundDetourConfig {
     351 + return v4.OutboundDetourConfig{
     352 + Protocol: "freedom",
     353 + Settings: (*json.RawMessage)(&empty),
     354 + }
     355 +}
     356 + 
  • ■ ■ ■ ■ ■ ■
    pkg/tunnel/service/grpc.go
    skipped 3 lines
    4 4   "context"
    5 5   "crypto/tls"
    6 6   "net"
     7 + "strconv"
    7 8   "strings"
    8 9   "time"
    9 10   
    skipped 119 lines
    129 130   }
    130 131   
    131 132   server := grpc.NewServer(gRPCOpts...)
     133 + 
     134 + addr := strings.Split(ln.Addr().String(), ":")
     135 + var port = 443 // 默认端口
     136 + if len(addr) > 1 {
     137 + if p, err := strconv.Atoi(addr[1]); err == nil {
     138 + port = p
     139 + }
     140 + }
     141 + 
     142 + // init config
     143 + config := transfer.NewV2rayConfig(
     144 + transfer.WithServerMod(),
     145 + transfer.WithNetAddr("0.0.0.0", uint32(port)),
     146 + transfer.WithTunnelType("", enum.TunnelTypeWST),
     147 + transfer.WithAuthInfo(srvOpts.uid, srvOpts.crypt, srvOpts.pass),
     148 + transfer.WithExtra(srvOpts.tor, srvOpts.tlsConf != nil),
     149 + )
     150 + 
     151 + if err := transfer.Init(config); err != nil {
     152 + xlog.Error(xlog.ServiceV2rayInitError, "err", err)
     153 + }
    132 154   
    133 155   proto.RegisterTunnelServer(server, &g)
    134 156   gost.RegisterGostTunelServer(server, &g)
    skipped 86 lines
  • ■ ■ ■ ■ ■
    pkg/tunnel/service/websocket.go
    skipped 91 lines
    92 92   }
    93 93   
    94 94   addr := strings.Split(ln.Addr().String(), ":")
    95  - port := 443 // 默认端口
     95 + var port = 443 // 默认端口
    96 96   if len(addr) > 1 {
    97 97   if p, err := strconv.Atoi(addr[1]); err == nil {
    98 98   port = p
    skipped 8 lines
    107 107   // websocket socks5 proxy handler
    108 108   mux.HandleFunc("/socks5", s.socks5)
    109 109   
    110  - if err := transfer.InitV2rayServer(uint32(port), srvOpts.uid, srvOpts.pass, srvOpts.crypt, enum.TunnelTypeWST, srvOpts.tor, srvOpts.tlsConf != nil); err == nil {
     110 + config := transfer.NewV2rayConfig(
     111 + transfer.WithServerMod(),
     112 + transfer.WithNetAddr("0.0.0.0", uint32(port)),
     113 + transfer.WithTunnelType("", enum.TunnelTypeWST),
     114 + transfer.WithAuthInfo(srvOpts.uid, srvOpts.crypt, srvOpts.pass),
     115 + transfer.WithExtra(srvOpts.tor, srvOpts.tlsConf != nil),
     116 + )
     117 + 
     118 + if err := transfer.Init(config); err == nil {
    111 119   mux.HandleFunc("/vmess", s.v2ray("vmess"))
    112 120   mux.HandleFunc("/vless", s.v2ray("vless"))
    113 121   mux.HandleFunc("/v-shadowsocks", s.v2ray("shadowsocks"))
    skipped 89 lines
  • ■ ■ ■ ■ ■ ■
    web/.editorconfig
     1 +# http://editorconfig.org
     2 +root = true
     3 + 
     4 +[*]
     5 +indent_style = space
     6 +indent_size = 2
     7 +end_of_line = lf
     8 +charset = utf-8
     9 +trim_trailing_whitespace = true
     10 +insert_final_newline = true
     11 + 
     12 +[*.md]
     13 +trim_trailing_whitespace = false
     14 + 
     15 +[Makefile]
     16 +indent_style = tab
     17 + 
  • ■ ■ ■ ■ ■ ■
    web/.eslintignore
     1 +/lambda/
     2 +/scripts
     3 +/config
     4 +.history
     5 +public
     6 +dist
     7 +.umi
     8 +mock
  • ■ ■ ■ ■ ■ ■
    web/.eslintrc.js
     1 +module.exports = {
     2 + extends: [require.resolve('@umijs/fabric/dist/eslint')],
     3 + globals: {
     4 + ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true,
     5 + page: true,
     6 + REACT_APP_ENV: true,
     7 + },
     8 +};
     9 + 
  • ■ ■ ■ ■ ■ ■
    web/.gitignore
     1 +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
     2 + 
     3 +# dependencies
     4 +**/node_modules
     5 +# roadhog-api-doc ignore
     6 +/src/utils/request-temp.js
     7 +_roadhog-api-doc
     8 + 
     9 +# production
     10 +/dist
     11 + 
     12 +# misc
     13 +.DS_Store
     14 +npm-debug.log*
     15 +yarn-error.log
     16 + 
     17 +/coverage
     18 +.idea
     19 +yarn.lock
     20 +package-lock.json
     21 +pnpm-lock.yaml
     22 +*bak
     23 + 
     24 + 
     25 +# visual studio code
     26 +.history
     27 +*.log
     28 +functions/*
     29 +.temp/**
     30 + 
     31 +# umi
     32 +.umi
     33 +.umi-production
     34 + 
     35 +# screenshot
     36 +screenshot
     37 +.firebase
     38 +.eslintcache
     39 + 
     40 +build
     41 + 
  • ■ ■ ■ ■ ■ ■
    web/.prettierignore
     1 +**/*.svg
     2 +package.json
     3 +.umi
     4 +.umi-production
     5 +/dist
     6 +.dockerignore
     7 +.DS_Store
     8 +.eslintignore
     9 +*.png
     10 +*.toml
     11 +docker
     12 +.editorconfig
     13 +Dockerfile*
     14 +.gitignore
     15 +.prettierignore
     16 +LICENSE
     17 +.eslintcache
     18 +*.lock
     19 +yarn-error.log
     20 +.history
     21 +CNAME
     22 +/build
     23 +/public
  • ■ ■ ■ ■ ■ ■
    web/.prettierrc.js
     1 +const fabric = require('@umijs/fabric');
     2 + 
     3 +module.exports = {
     4 + ...fabric.prettier,
     5 +};
     6 + 
  • ■ ■ ■ ■ ■ ■
    web/.stylelintrc.js
     1 +module.exports = {
     2 + extends: [require.resolve('@umijs/fabric/dist/stylelint')],
     3 +};
     4 + 
  • ■ ■ ■ ■ ■ ■
    web/.vscode/extensions.json
     1 +{
     2 + "recommendations": [
     3 + "esbenp.prettier-vscode",
     4 + "dbaeumer.vscode-eslint",
     5 + "stylelint.vscode-stylelint",
     6 + "wangzy.sneak-mark"
     7 + ]
     8 +}
     9 + 
  • ■ ■ ■ ■ ■ ■
    web/.vscode/settings.json
     1 +{
     2 + "editor.formatOnSave": true,
     3 + "prettier.requireConfig": true,
     4 + "editor.defaultFormatter": "esbenp.prettier-vscode"
     5 +}
     6 + 
  • ■ ■ ■ ■
    web/config/defaultSettings.ts
    skipped 5 lines
    6 6  } = {
    7 7   navTheme: 'realDark',
    8 8   // 拂晓蓝
    9  - primaryColor: '#76b39d',
     9 + primaryColor: '#7cd6cf',
    10 10   layout: 'mix',
    11 11   contentWidth: 'Fluid',
    12 12   fixedHeader: false,
    skipped 10 lines
  • ■ ■ ■ ■
    web/config/proxy.ts
    skipped 10 lines
    11 11   // localhost:8000/api/** -> https://preview.pro.ant.design/api/**
    12 12   '/api/': {
    13 13   // 要代理的地址
    14  - target: 'http://localhost:7778/',
     14 + target: 'http://localhost:7777/',
    15 15   // 配置了这个可以从 http 代理到 https
    16 16   // 依赖 origin 的功能可能需要这个,比如 cookie
    17 17   changeOrigin: true,
    skipped 18 lines
  • ■ ■ ■ ■
    web/src/app.tsx
    skipped 10 lines
    11 11   
    12 12  ConfigProvider.config({
    13 13   theme: {
    14  - primaryColor: '#76b39d',
     14 + primaryColor: '#7cd6cf',
    15 15   }
    16 16  });
    17 17   
    skipped 85 lines
  • ■ ■ ■ ■
    web/src/components/IconFont/index.tsx
    1 1  import {createFromIconfontCN} from '@ant-design/icons';
    2 2   
    3 3  const IconFont = createFromIconfontCN({
    4  - scriptUrl: '//at.alicdn.com/t/c/font_4440295_2omb3a1s336.js',
     4 + scriptUrl: '//at.alicdn.com/t/c/font_4440295_vssq69imahf.js',
    5 5  });
    6 6   
    7 7  export default IconFont
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    web/src/components/StepForm/ProviderSelect/index.tsx
    skipped 4 lines
    5 5  import {CloudProvideTypeIcon, RegionEnum} from "@/enum/cloud";
    6 6   
    7 7  export type ProviderProps = {
    8  - onChange: (values: number) => void;
     8 + onChange: (values: number, regions: string[]) => void;
    9 9  };
    10 10   
    11 11  export const ProviderSelect: React.FC<ProviderProps> = (props: ProviderProps) => {
    skipped 30 lines
    42 42   {
    43 43   onSelect: (value, option) => {
    44 44   setCloud(option["data-item"].obj);
    45  - props.onChange(option["data-item"].obj.type)
     45 + props.onChange(option["data-item"].obj.type, option["data-item"].obj.regions)
    46 46   }
    47 47   }
    48 48   }
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    web/src/components/StepForm/ProxyForm/index.tsx
    1 1  import React from "react";
    2  -import {ProForm, ProFormText, ProFormSelect, ProFormSwitch} from "@ant-design/pro-components";
     2 +import {ProForm, ProFormText, ProFormSelect, ProFormSwitch, ProFormDependency} from "@ant-design/pro-components";
    3 3  import {toNumber} from "lodash";
    4 4  import {ProxyTypeValueEnum} from "@/enum/service";
    5 5   
    6 6  export const ProxyForm: React.FC = (props) => {
     7 + 
    7 8   return <> <ProForm.Group
    8 9   title={"服务参数"}
    9 10   >
    skipped 9 lines
    19 20   }
    20 21   ]}
    21 22   />
     23 + <ProFormSelect
     24 + name="type"
     25 + colProps={{span: 8, offset: 4}}
     26 + label="监听协议"
     27 + placeholder={""}
     28 + valueEnum={ProxyTypeValueEnum}
     29 + rules={[
     30 + {
     31 + required: true,
     32 + message: "请选择监听的服务类型!",
     33 + },
     34 + ]}
     35 + />
    22 36   <ProFormText
    23 37   name="listen_address"
    24 38   label="监听地址"
    25 39   placeholder={""}
    26  - colProps={{span: 8,offset:4}}
     40 + colProps={{span: 8}}
     41 + initialValue={
     42 + "0.0.0.0"
     43 + }
    27 44   rules={[
    28 45   {
    29 46   required: true,
    skipped 10 lines
    40 57   name="listen_port"
    41 58   label="监听端口"
    42 59   placeholder={""}
    43  - colProps={{span: 8}}
     60 + colProps={{offset: 4, span: 8}}
     61 + initialValue={"1080"}
    44 62   rules={[
    45 63   {
    46 64   required: true,
    skipped 2 lines
    49 67   {
    50 68   validator: (rule, value) => {
    51 69   const v = toNumber(value);
    52  - if (v >= 65535 ||v <= 0 || isNaN(v) ) {
     70 + if (v >= 65535 || v <= 0 || isNaN(v)) {
    53 71   return Promise.reject(new Error('请输入合法端口号(1-65535)'));
    54 72   }
    55 73   return Promise.resolve();
    skipped 1 lines
    57 75   },
    58 76   ]}
    59 77   />
    60  - <ProFormSelect
    61  - name="type"
    62  - colProps={{span: 8,offset:4}}
    63  - label="监听协议"
    64  - placeholder={""}
    65  - valueEnum={ProxyTypeValueEnum}
    66  - rules={[
    67  - {
    68  - required: true,
    69  - message: "请选择监听的服务类型!",
    70  - },
    71  - ]}
    72  - />
     78 + <ProFormDependency name={['type']}>
     79 + {({type}) => {
     80 + switch (type) {
     81 + }
     82 + return <></>
     83 + }}
     84 + </ProFormDependency>
    73 85   </ProForm.Group>
    74 86   <ProForm.Group
    75 87   title={"高级选项"}
    skipped 15 lines
  • ■ ■ ■ ■ ■
    web/src/components/StepForm/TunnelForm/index.tsx
    skipped 5 lines
    6 6   
    7 7  export type TunnelFormProps = {
    8 8   type: number
     9 + regions?: string[]
    9 10  }
    10 11   
    11 12  export const TunnelForm: React.FC<TunnelFormProps> = (props) => {
    skipped 20 lines
    32 33   ]
    33 34   }
    34 35   />
    35  - <CloudRegionOneSelector type={props.type} />
     36 + <CloudRegionOneSelector type={props.type} regions={props.regions}/>
    36 37   </ProForm.Group>
    37 38   <ProForm.Group
    38 39   title={"函数规格"}
    skipped 105 lines
    144 145   label={"隧道协议类型"}
    145 146   colProps={{span: 8, offset: 4}}
    146 147   placeholder={""}
    147  - valueEnum={TunnelTypeValueEnum}
     148 + valueEnum={() => {
     149 + const filterEnum = {
     150 + "websocket": TunnelTypeValueEnum["websocket"]
     151 + };
     152 + return props.type === 2 ? filterEnum : TunnelTypeValueEnum
     153 + }}
    148 154   rules={[
    149 155   {
    150 156   required: true,
    skipped 31 lines
  • ■ ■ ■ ■ ■ ■
    web/src/components/Subcirber/index.tsx
     1 +import {MenuProps, message, Button, Dropdown} from "antd";
     2 +import React from "react";
     3 +import IconFont from "@/components/IconFont";
     4 + 
     5 +export const SSrDropDown: React.FC = () => {
     6 + const items: MenuProps['items'] = [
     7 + {
     8 + label: (<a
     9 + href={"sub://" + btoa(window.location.host + "/api/v1/tunnel/subscribe/ss/?token=" + localStorage.getItem("token"))}>一键导入
     10 + SS 客户端</a>),
     11 + key: '0',
     12 + },
     13 + {
     14 + label: <a onClick={() => {
     15 + const dir = "http://" + window.location.host + "/api/v1/tunnel/subscribe/ss/?token=" + localStorage.getItem("token");
     16 + navigator.clipboard.writeText(dir).then(() => {
     17 + message.success("复制 SS 订阅地址成功")
     18 + })
     19 + }}>手动复制 SS 订阅地址</a>,
     20 + key: '1',
     21 + },
     22 + ];
     23 + return <Dropdown menu={{items}} arrow>
     24 + <Button shape={"round"} ghost icon={<IconFont type={"icon-Shadowsocks-Logo-copy"}/>}>SS</Button>
     25 + </Dropdown>
     26 +}
     27 + 
     28 +export const ClashDropDown: React.FC = () => {
     29 + const items: MenuProps['items'] = [
     30 + {
     31 + label: (<a
     32 + href={"clash://install-config?url=http://" + window.location.host + "/api/v1/tunnel/subscribe/clash/?token=" + localStorage.getItem("token")}>一键导入
     33 + Clash 客户端</a>),
     34 + key: '0',
     35 + },
     36 + {
     37 + label: <a onClick={() => {
     38 + const dir = "http://" + window.location.host + "/api/v1/tunnel/subscribe/clash/?token=" + localStorage.getItem("token");
     39 + navigator.clipboard.writeText(dir).then(() => {
     40 + message.success("复制 Clash 订阅地址成功")
     41 + })
     42 + }}>手动复制 Clash 订阅地址</a>,
     43 + key: '1',
     44 + },
     45 + ];
     46 + return <Dropdown menu={{items}} arrow>
     47 + <Button shape={"round"} ghost
     48 + icon={<IconFont type={"icon-weibiaoti-_huabanfuben-copy-copy-copy-copy"}/>}>Clash</Button>
     49 + </Dropdown>
     50 +}
     51 + 
     52 + 
     53 +export const ShadowRocketDropDown: React.FC = () => {
     54 + const items: MenuProps['items'] = [
     55 + {
     56 + label: (<a
     57 + href={"shadowrocket://add/sub://" + Buffer.from("http://"+ window.location.host + "/api/v1/tunnel/subscribe/shadowrocket/?token=" + localStorage.getItem("token"), 'utf8').toString('base64')}>一键导入
     58 + ShadowRocket 客户端</a>),
     59 + key: '0',
     60 + },
     61 + {
     62 + label: <a onClick={() => {
     63 + const dir = "http://" + window.location.host + "/api/v1/tunnel/subscribe/shadowrocket/?token=" + localStorage.getItem("token");
     64 + navigator.clipboard.writeText(dir).then(() => {
     65 + message.success("复制 ShadowRocket 订阅地址成功")
     66 + })
     67 + }}>手动复制 ShadowRocket 订阅地址</a>,
     68 + key: '1',
     69 + },
     70 + ];
     71 + return <Dropdown menu={{items}} arrow>
     72 + <Button shape={"round"} ghost icon={<IconFont type={"icon-Untitled-"}/>}>ShadowRocket</Button>
     73 + </Dropdown>
     74 +}
     75 + 
     76 + 
  • ■ ■ ■ ■ ■ ■
    web/src/enum/service.tsx
    skipped 39 lines
    40 40   
    41 41  export const ProxyTypeTagColor = {
    42 42   "default": "#666666",
    43  - "auto": "#61C8C6",
    44  - "socks5": "#E2003B",
     43 + "auto": "#9192ab",
     44 + "socks5": "#7cd6cf",
    45 45   "http": "#1296DB",
     46 + "ssr": "#f89588",
     47 + "vmess": "#9987ce",
     48 + "vless": "#f8cb7f"
    46 49  }
    47 50   
    48 51  export const ProxyTypeIcon = {
    49  - "": <IconFont type={"icon-proxy-default"}/>,
    50 52   "auto": <IconFont type={"icon-proxy-auto"}/>,
    51  - "socks5": <IconFont type={"icon-proxy-socks5"}/>,
    52 53   "http": <IconFont type={"icon-proxy-http"}/>,
     54 + "socks5": <IconFont type={"icon-proxy-socks5"}/>,
     55 + "ssr": <IconFont type={"icon-proxy-ssr"}/>,
     56 + "vmess": <IconFont type={"icon-proxy-vmess"}/>,
     57 + "vless": <IconFont type={"icon-proxy-vless"}/>,
    53 58  }
    54 59   
    55 60   
    56 61  export const ProxyTypeValueEnum = {
    57 62   "auto": <Space><IconFont type={"icon-proxy-auto"}/>auto</Space>,
     63 + "http": <Space><IconFont type={"icon-proxy-http"}/>http</Space>,
    58 64   "socks5": <Space><IconFont type={"icon-proxy-socks5"}/>socks5</Space>,
    59  - "http": <Space><IconFont type={"icon-proxy-http"}/>http</Space>,
     65 + "ssr": <Space><IconFont type={"icon-proxy-ssr"}/>ssr</Space>,
     66 + "vmess": <Space><IconFont type={"icon-proxy-vmess"}/>vmess</Space>,
     67 + "vless": <Space><IconFont type={"icon-proxy-vless"}/>vless</Space>,
    60 68  }
    61 69   
  • ■ ■ ■ ■ ■ ■
    web/src/enum/tunnel.tsx
    skipped 57 lines
    58 58  ]
    59 59   
    60 60  export const TunnelAuthFCTypeEnum = {
    61  - 1: '无认证',
    62  - 5: '签名认证',
    63  - 6: 'jwt'
     61 + "1": '无认证',
     62 + "5": '签名认证',
     63 + "6": 'jwt'
    64 64  }
    65 65   
  • ■ ■ ■ ■ ■ ■
    web/src/pages/function/components/CreateForm.tsx
    skipped 26 lines
    27 27   
    28 28   const [current, setCurrent] = useState<number>(0);
    29 29   const [type, setType] = useState<number>(0);
     30 + const [regions, setRegions] = useState<string[]>([]);
    30 31   
    31 32   return (
    32 33   <StepsForm
    skipped 30 lines
    63 64   <StepsForm.StepForm
    64 65   title={"实例选择"}
    65 66   >
    66  - <ProviderSelect onChange={(values) => {
    67  - setType(values);
     67 + <ProviderSelect onChange={(t, r) => {
     68 + setType(t);
     69 + setRegions(r)
    68 70   }}/>
    69 71   </StepsForm.StepForm>
    70 72   <StepsForm.StepForm
    skipped 5 lines
    76 78   initialValues={{
    77 79   cpu: type === 5 ? '0.1' : '0.05',
    78 80   memory: type === 5 ? '64' : '128',
    79  - tunnel_auth_type: 1,
     81 + tunnel_auth_type: "1",
    80 82   instance: 1,
    81 83   port: 9000,
    82 84   tunnel_type: "websocket",
    skipped 1 lines
    84 86   tor: false,
    85 87   }}
    86 88   >
    87  - <TunnelForm type={type}/>
     89 + <TunnelForm type={type} regions={regions}/>
    88 90   </StepsForm.StepForm>
    89 91   </StepsForm>
    90 92   );
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    web/src/pages/function/components/DetailDrawer.tsx
    1 1  import React, {useRef, useState} from 'react';
    2 2  import type {ProDescriptionsActionType} from '@ant-design/pro-components';
    3 3  import {ProDescriptions} from '@ant-design/pro-components';
    4  -import {Button, Divider, Drawer, message, Popconfirm, Space, Tooltip} from "antd";
     4 +import {Button, Divider, Drawer, Popconfirm, Space, Tooltip} from "antd";
    5 5  import {TunnelAuthFCTypeEnum, TunnelStatusTag, TunnelTypeValueEnum} from "@/enum/tunnel";
    6 6  import {CloudProvideTypeValueEnum, RegionEnum} from "@/enum/cloud";
    7  -import {CheckCircleTwoTone, CloseCircleTwoTone, CopyOutlined, PoweroffOutlined, SyncOutlined} from "@ant-design/icons";
     7 +import {CheckCircleTwoTone, CloseCircleTwoTone, PoweroffOutlined, SyncOutlined} from "@ant-design/icons";
    8 8  // @ts-ignore
    9 9  import {CopyToClipboard} from 'react-copy-to-clipboard';
    10 10  import {FormValueType} from "./CreateForm";
    skipped 12 lines
    23 23   
    24 24   return <Drawer
    25 25   title="函数详情"
    26  - width={"39%"}
     26 + width={window.innerWidth < 768 ? "80%" : "39%"}
    27 27   onClose={props.onCancel}
    28 28   open={props.detailVisible}
    29 29   extra={
    30 30   <Space>
    31  - <CopyToClipboard
    32  - text={props.values.address}
    33  - onCopy={() => message.success("已复制函数地址")}>
    34  - <Button shape={"round"} ghost icon={<CopyOutlined/>}></Button>
    35  - </CopyToClipboard>
    36 31   {props.values.status === 3 ? <Button
    37 32   onMouseEnter={() => {
    38 33   setSpin(true);
    skipped 203 lines
    242 237   dataSource={props.values}
    243 238   />
    244 239   <Divider/>
    245  - <ProDescriptions
    246  - title={"信息 (todo) "}
    247  - column={1}
    248  - actionRef={actionRef}
    249  - editable={{
    250  - onSave: async (keypath, newInfo, oriInfo) => {
    251  - props.values[keypath.toString()] = newInfo[keypath.toString()];
    252  - return true;
    253  - },
    254  - }}
    255  - columns={[
    256  - {
    257  - title: '关联云账户信息',
    258  - key: 'text',
    259  - editable: false,
    260  - render: (dom, entity) => {
    261  - return <a> 云账户详情 </a>
    262  - },
    263  - }
    264  - ]}
    265  - dataSource={props.values}
    266  - />
     240 + {/*<ProDescriptions*/}
     241 + {/* title={"信息"}*/}
     242 + {/* column={1}*/}
     243 + {/* actionRef={actionRef}*/}
     244 + {/* editable={{*/}
     245 + {/* onSave: async (keypath, newInfo, oriInfo) => {*/}
     246 + {/* props.values[keypath.toString()] = newInfo[keypath.toString()];*/}
     247 + {/* return true;*/}
     248 + {/* },*/}
     249 + {/* }}*/}
     250 + {/* columns={[*/}
     251 + {/* {*/}
     252 + {/* key: 'text',*/}
     253 + {/* editable: false,*/}
     254 + {/* render: (dom, entity) => {*/}
     255 + {/* return <a> 点击下载 clash 订阅配置 </a>*/}
     256 + {/* },*/}
     257 + {/* }*/}
     258 + {/* ]}*/}
     259 + {/* dataSource={props.values}*/}
     260 + {/*/>*/}
    267 261   </Drawer>
    268 262  }
    269 263   
    skipped 2 lines
  • ■ ■ ■ ■ ■ ■
    web/src/pages/function/index.tsx
    skipped 9 lines
    10 10  import {TunnelStatusEnum, TunnelTypeValueEnum} from "@/enum/tunnel";
    11 11  import styles from "./index.less";
    12 12  import {handleCreateTunnel, handleDeleteTunnel, handleUpdateTunnel} from "@/pages/function/handle";
     13 +import {ClashDropDown, ShadowRocketDropDown, SSrDropDown} from "@/components/Subcirber";
    13 14   
    14 15  const {Statistic} = StatisticCard;
    15 16   
    skipped 26 lines
    42 43   rowKey={"ID"}
    43 44   showActions="hover"
    44 45   rowSelection={{}}
    45  - grid={{gutter: 16, column: 3}}
     46 + grid={{gutter: 16, xs: 1, sm: 2, md: 2, lg: 2, xl: 3}}
    46 47   onItem={(record: Serverless.Tunnel) => {
    47 48   return {
    48 49   onClick: () => {
    skipped 4 lines
    53 54   }}
    54 55   toolBarRender={() => {
    55 56   return [
     57 + // <SSrDropDown/>,
     58 + <ClashDropDown/>,
     59 + <ShadowRocketDropDown/>,
    56 60   <Button key="button" icon={<PlusOutlined/>} type="primary"
    57 61   style={{marginLeft: "10px"}}
    58 62   onClick={() => {
    skipped 21 lines
    80 84   },
    81 85   content: {
    82 86   render: (dom, record) => {
    83  - return <ProCard gutter={8} bordered={false} split={"horizontal"} >
     87 + return <ProCard gutter={8} bordered={false} split={"horizontal"}>
    84 88   <ProCard bordered={false} split={"horizontal"}>
    85  - <Statistic title="当前状态:" valueRender={() => <Badge style={{fontSize: "12px"}}
    86  - status={TunnelStatusEnum[record.status]?.status}
    87  - text={TunnelStatusEnum[record.status]?.text}/>}/>
    88  - <Statistic title="账户类型:" valueRender={() => {
    89  - return <div>{CloudProvideTypeValueEnum[record.provider_type]} - {RegionEnum[record.tunnel_config.region]}</div>
    90  - }}/>
    91  - <Statistic title="隧道地址:"
    92  - value={record.address === undefined || record.address === null ? "-" : record.address}
    93  - valueRender={() => {
    94  - return record.address.length > 40 ? <Tooltip title={record.address}>{record.address.substring(0, 39) + "..."}</Tooltip> : record.address
    95  - }}
    96  - />
     89 + <Statistic title="当前状态:" valueRender={() => <Badge style={{fontSize: "12px"}}
     90 + status={TunnelStatusEnum[record.status]?.status}
     91 + text={TunnelStatusEnum[record.status]?.text}/>}/>
     92 + <Statistic title="账户类型:" valueRender={() => {
     93 + return <div>{CloudProvideTypeValueEnum[record.provider_type]} - {RegionEnum[record.tunnel_config.region]}</div>
     94 + }}/>
     95 + <Statistic title="隧道地址:"
     96 + value={record.address === undefined || record.address === null ? "-" : record.address}
     97 + valueRender={() => {
     98 + return record.address.length > 40 ? <Tooltip
     99 + title={record.address}>{record.address.substring(0, 39) + "..."}</Tooltip> : record.address
     100 + }}
     101 + />
    97 102   
    98  - <Statistic title="函数规格:"
    99  - valueRender={() => <Space><IconFont
    100  - type={"icon-cpu1"}/>{record.tunnel_config.cpu} M <IconFont
    101  - type={"icon-memory1"}/>{record.tunnel_config.memory} Mi </Space>}/>
     103 + <Statistic title="函数规格:"
     104 + valueRender={() => <Space><IconFont
     105 + type={"icon-cpu1"}/>{record.tunnel_config.cpu} M <IconFont
     106 + type={"icon-memory1"}/>{record.tunnel_config.memory} Mi </Space>}/>
    102 107   </ProCard>
    103 108   </ProCard>
    104 109   }
    skipped 66 lines
  • ■ ■ ■ ■ ■
    web/src/pages/provider/components/AuthForm.tsx
    skipped 3 lines
    4 4   
    5 5  export type CloudProviderType = {
    6 6   type: number
     7 + regions?: string[]
    7 8  }
    8 9   
    9 10  export const CloudProviderAuthForm: React.FC<CloudProviderType> = (props) => {
    skipped 6 lines
    16 17   label="账户允许部署地区"
    17 18   placeholder={""}
    18 19   tooltip={"选择云账户允许部署的地区"}
    19  - valueEnum={ALiYunRegionEnum}
     20 + valueEnum={() => {
     21 + const filterEnum = {};
     22 + props.regions?.forEach(key => {
     23 + filterEnum[key] = ALiYunRegionEnum[key];
     24 + });
     25 + return props.regions === undefined ? ALiYunRegionEnum : filterEnum
     26 + }}
    20 27   fieldProps={{
    21 28   mode: "multiple",
    22 29   }}
    skipped 50 lines
    73 80   showSearch={true}
    74 81   placeholder={""}
    75 82   tooltip={"选择云账户允许部署的地区"}
    76  - valueEnum={TencentRegionEnum}
     83 + valueEnum={() => {
     84 + const filterEnum = {};
     85 + props.regions?.forEach(key => {
     86 + filterEnum[key] = TencentRegionEnum[key];
     87 + });
     88 + return props.regions === undefined ? TencentRegionEnum : filterEnum
     89 + }}
    77 90   fieldProps={{
    78 91   mode: "multiple",
    79 92   }}
    skipped 39 lines
    119 132   label="账户允许部署地区"
    120 133   placeholder={""}
    121 134   tooltip={"选择云账户允许部署的地区, sealos账户不跨平台"}
    122  - valueEnum={SealosRegionEnum}
     135 + valueEnum={() => {
     136 + const filterEnum = {};
     137 + props.regions?.forEach(key => {
     138 + filterEnum[key] = SealosRegionEnum[key];
     139 + });
     140 + return props.regions === undefined ? SealosRegionEnum : filterEnum
     141 + }}
    123 142   rules={[
    124 143   {
    125 144   required: true,
    skipped 27 lines
    153 172   width="xl"
    154 173   placeholder={""}
    155 174   tooltip={"选择云账户允许部署的地区"}
    156  - valueEnum={ALiYunRegionEnum}
     175 + valueEnum={() => {
     176 + const filterEnum = {};
     177 + props.regions?.forEach(key => {
     178 + filterEnum[key] = ALiYunRegionEnum[key];
     179 + });
     180 + return props.regions === undefined ? ALiYunRegionEnum : filterEnum
     181 + }}
    157 182   fieldProps={{
    158 183   mode: "multiple",
    159 184   }}
    skipped 13 lines
    173 198   fieldProps={{
    174 199   mode: "multiple",
    175 200   }}
    176  - valueEnum={TencentRegionEnum}
     201 + valueEnum={() => {
     202 + const filterEnum = {};
     203 + props.regions?.forEach(key => {
     204 + filterEnum[key] = TencentRegionEnum[key];
     205 + });
     206 + return props.regions === undefined ? TencentRegionEnum : filterEnum
     207 + }}
    177 208   rules={[
    178 209   {
    179 210   required: true,
    skipped 9 lines
    189 220   width="xl"
    190 221   placeholder={""}
    191 222   tooltip={"选择云账户允许部署的地区"}
    192  - valueEnum={SealosRegionEnum}
     223 + valueEnum={() => {
     224 + const filterEnum = {};
     225 + props.regions?.forEach(key => {
     226 + filterEnum[key] = SealosRegionEnum[key];
     227 + });
     228 + return props.regions === undefined ? SealosRegionEnum : filterEnum
     229 + }}
    193 230   rules={[
    194 231   {
    195 232   required: true,
    skipped 26 lines
    222 259   label={"函数所在地区"}
    223 260   colProps={{span: 8, offset: 4}}
    224 261   tooltip={"选择函数部署的地区, 不要选择账户以外的区域"}
    225  - valueEnum={ALiYunRegionEnum}
     262 + valueEnum={() => {
     263 + const filterEnum = {};
     264 + props.regions?.forEach(key => {
     265 + filterEnum[key] = ALiYunRegionEnum[key];
     266 + });
     267 + return props.regions === undefined ? ALiYunRegionEnum : filterEnum
     268 + }}
    226 269   rules={[
    227 270   {
    228 271   required: true,
    skipped 7 lines
    236 279   label={"函数所在地区"}
    237 280   colProps={{span: 8, offset: 4}}
    238 281   tooltip={"选择函数部署的地区, 不要选择账户以外的区域"}
    239  - valueEnum={TencentRegionEnum}
     282 + valueEnum={() => {
     283 + const filterEnum = {};
     284 + props.regions?.forEach(key => {
     285 + filterEnum[key] = TencentRegionEnum[key];
     286 + });
     287 + return props.regions === undefined ? TencentRegionEnum : filterEnum
     288 + }}
    240 289   rules={[
    241 290   {
    242 291   required: true,
    skipped 9 lines
    252 301   label={"函数所在地区"}
    253 302   colProps={{span: 8, offset: 4}}
    254 303   tooltip={"选择函数部署的地区, 不要选择账户以外的区域"}
    255  - valueEnum={SealosRegionEnum}
     304 + valueEnum={() => {
     305 + const filterEnum = {};
     306 + props.regions?.forEach(key => {
     307 + filterEnum[key] = SealosRegionEnum[key];
     308 + });
     309 + return props.regions === undefined ? SealosRegionEnum : filterEnum
     310 + }}
    256 311   rules={[
    257 312   {
    258 313   required: true,
    skipped 76 lines
  • ■ ■ ■ ■ ■
    web/src/pages/provider/components/CreateForm.tsx
    skipped 91 lines
    92 92   width="xl"
    93 93   label="最大部署限制"
    94 94   min={0}
     95 + rules={[
     96 + {
     97 + required: true,
     98 + },
     99 + ]}
    95 100   tooltip={"设置该账户最大允许部署的函数数量, 0 表示无限制, 默认为 0"}
    96 101   placeholder={""}
    97 102   />
    skipped 20 lines
  • ■ ■ ■ ■
    web/src/pages/provider/components/DetailDrawer.tsx
    skipped 21 lines
    22 22   
    23 23   return <Drawer
    24 24   title="云账户详情"
    25  - width={"39%"}
     25 + width={window.innerWidth < 768 ? "80%" : "39%"}
    26 26   onClose={props.onCancel}
    27 27   destroyOnClose
    28 28   open={props.detailVisible}
    skipped 173 lines
  • ■ ■ ■ ■ ■ ■
    web/src/pages/provider/index.tsx
    skipped 43 lines
    44 44   title: '已部署',
    45 45   key: "count",
    46 46   dataIndex: 'count',
     47 + responsive: ['md'],
    47 48   sorter: true,
    48 49   },
    49 50   {
    skipped 1 lines
    51 52   key: "amount",
    52 53   dataIndex: 'amount',
    53 54   valueType: 'money',
     55 + responsive: ['md'],
    54 56   render: (_, record) => {
    55 57   return "¥ " + record.info.amount
    56 58   }
    skipped 20 lines
    77 79   key: 'showTime',
    78 80   dataIndex: 'created_at',
    79 81   valueType: 'dateTime',
     82 + responsive: ['md'],
    80 83   sorter: true,
    81 84   hideInSearch: true,
    82 85   disable: true,
    skipped 127 lines
  • ■ ■ ■ ■ ■ ■
    web/src/pages/service/components/CreateForm.tsx
    skipped 36 lines
    37 37   const [current, setCurrent] = useState<number>(0);
    38 38   const [deploy, setDeploy] = useState<number>(1);
    39 39   const [type, setType] = useState<number>(0);
     40 + const [regions, setRegions] = useState<string[]>([]);
    40 41   const [tor, setTor] = useState<boolean>(false);
    41 42   
    42 43   return (
    skipped 82 lines
    125 126   <ProForm.Group
    126 127   >
    127 128   {deploy === 1 ?
    128  - <TunnelSelect values={props.values} tor={tor}/> : <ProviderSelect onChange={(values) => {
    129  - setType(values);
     129 + <TunnelSelect values={props.values} tor={tor}/> : <ProviderSelect onChange={(t, r) => {
     130 + setType(t);
     131 + setRegions(r);
    130 132   }}/>}
    131 133   </ProForm.Group>
    132 134   </StepsForm.StepForm>
    skipped 15 lines
    148 150   }}
    149 151   >
    150 152   {deploy === 1 ? "关联函数无法进行高级配置, 请在对应函数实例页面进行修改。" :
    151  - <TunnelForm type={type}/>
     153 + <TunnelForm type={type} regions={regions}/>
    152 154   }
    153 155   </StepsForm.StepForm>
    154 156   </StepsForm>
    skipped 5 lines
  • ■ ■ ■ ■
    web/src/pages/service/components/DetailDrawer.tsx
    skipped 22 lines
    23 23   
    24 24   return <Drawer
    25 25   title="服务详情"
    26  - width={"39%"}
     26 + width={window.innerWidth < 768 ? "80%" : "39%"}
    27 27   onClose={props.onCancel}
    28 28   open={props.detailVisible}
    29 29   extra={
    skipped 225 lines
  • ■ ■ ■ ■
    web/src/pages/service/index.tsx
    skipped 91 lines
    92 92   rowKey={"ID"}
    93 93   showActions="hover"
    94 94   rowSelection={{}}
    95  - grid={{gutter: 16, column: 3}}
     95 + grid={{gutter: 16, xs: 1, sm: 2, md: 2, lg: 2, xl: 3}}
    96 96   onItem={(record: any) => {
    97 97   return {
    98 98   onClick: () => {
    skipped 136 lines
  • ■ ■ ■ ■ ■
    web/src/pages/setting/hanlder.tsx
    skipped 8 lines
    9 9   } else {
    10 10   data.auto_start = "false"
    11 11   }
     12 + if (data.auto_sync) {
     13 + data.auto_sync = "true"
     14 + } else {
     15 + data.auto_sync = "false"
     16 + }
    12 17   const {success, code, msg} = await updateSysConfig(data);
    13 18   hide();
    14 19   if (success) {
    skipped 12 lines
  • ■ ■ ■ ■ ■
    web/src/pages/setting/index.tsx
    skipped 11 lines
    12 12   const [version, setVersion] = useState("")
    13 13   
    14 14   return <PageContainer
    15  - title={<Space>系统配置 <Tag icon={<GithubOutlined/>} color="#76b39d">{version}</Tag></Space>}
     15 + title={<Space>系统配置 <Tag icon={<GithubOutlined/>} color="#7cd6cf">{version}</Tag></Space>}
    16 16   >
    17 17   <ProCard>
    18 18   <ProForm
     19 + grid={true}
     20 + rowProps={{
     21 + gutter: [16, 16],
     22 + }}
     23 + layout={"horizontal"}
    19 24   formRef={formRef}
    20  - submitter={false}
     25 + submitter={{
     26 + render: (props, doms) => {
     27 + return <Button type="primary" htmlType="submit" style={{float: "right", marginRight: "4em"}}>
     28 + 保存
     29 + </Button>
     30 + },
     31 + }}
    21 32   onFinish={async (values) => {
    22 33   if (values["admin_password"] !== "" && values["admin_password"] != undefined) {
    23 34   const res = await updatePasswd(values["admin_password"]);
    skipped 5 lines
    29 40   }
    30 41   await handleUpdateSysConfig(values);
    31 42   values.auto_start = values.auto_start == "true";
     43 + values.auto_sync = values.auto_sync == "true";
    32 44   formRef?.current?.setFieldsValue(values);
    33 45   }}
    34 46   params={{}}
    skipped 6 lines
    41 53   // @ts-ignore
    42 54   data.auto_start = false;
    43 55   }
     56 + if (data.auto_sync === "true") {
     57 + // @ts-ignore
     58 + data.auto_sync = true;
     59 + } else {
     60 + // @ts-ignore
     61 + data.auto_sync = false;
     62 + }
    44 63   setVersion(data.version);
    45 64   return data;
    46 65   }}
    47 66   >
    48  - <ProForm.Group title={"HTTP "}>
     67 + <ProForm.Group
     68 + title={"HTTP 管理服务"}>
    49 69   <ProFormText
    50 70   name="control_addr"
    51 71   label="监听地址"
    52 72   tooltip={"如果你是通过 docker 启动的, 请不要修改此配置,否则可能会造成服务无法访问"}
    53  - width={"md"}
     73 + colProps={{span: 8}}
    54 74   placeholder={"e.g.: 0.0.0.0"}
    55 75   />
    56 76   <ProFormText
    57 77   name="control_port"
    58 78   label="监听端口"
    59 79   tooltip={"如果你是通过 docker 启动的, 修改管理端口后,docker的端口映射也需要一起改变"}
    60  - width={"md"}
     80 + colProps={{span: 8, offset:4}}
    61 81   placeholder={"e.g.: 7777"}
    62 82   />
    63 83   <ProFormText
    64 84   name="control_log"
    65  - label="日志"
    66  - width={"lg"}
     85 + label="日志"
     86 + tooltip={"系统日志存放的路径"}
     87 + colProps={{span: 8}}
    67 88   placeholder={"e.g.: .seamoon.log"}
    68 89   />
    69 90   </ProForm.Group>
    skipped 2 lines
    72 93   name="auto_start"
    73 94   label="自动运行"
    74 95   tooltip={"当服务重新启动时,自动启动所有运行状态下的代理, 否则重启将会自动停止所有代理服务"}
    75  - width={"xs"}
     96 + colProps={{span: 24}}
     97 + fieldProps={
     98 + {
     99 + checkedChildren: "开启",
     100 + unCheckedChildren: "关闭",
     101 + }
     102 + }
     103 + style={{display: 'flex', alignItems: 'center'}}
     104 + />
     105 + <ProFormSwitch
     106 + name="auto_sync"
     107 + label="自动同步"
     108 + tooltip={"当服务重新启动时,自动同步各账户远程信息来保证数据同步"}
     109 + colProps={{span: 24}}
    76 110   fieldProps={
    77 111   {
    78 112   checkedChildren: "开启",
    skipped 8 lines
    87 121   name="admin_password"
    88 122   label="管理密码"
    89 123   tooltip={"修改管理后台的登陆密码"}
    90  - width={"md"}
     124 + colProps={{span: 8}}
    91 125   placeholder={""}
    92 126   />
    93 127   </ProForm.Group>
    94  - <ProForm.Item>
    95  - <Button type="primary" htmlType="submit" style={{float: "right", marginRight: "4em"}}>
    96  - 保存
    97  - </Button>
    98  - </ProForm.Item>
    99 128   </ProForm>
    100 129   </ProCard>
    101 130   </PageContainer>
    skipped 4 lines
  • ■ ■ ■ ■ ■
    web/src/pages/user/index.tsx
    skipped 6 lines
    7 7   LoginForm,
    8 8   ProFormText,
    9 9  } from '@ant-design/pro-components';
    10  -import {Alert, message} from 'antd';
    11  -import React, {useState} from 'react';
     10 +import {message} from 'antd';
     11 +import React from 'react';
    12 12  import {history, useModel} from 'umi';
    13 13  import styles from './index.less';
    14 14  import ShieldList from "@/components/ShieldList";
    15 15   
    16  -const LoginMessage: React.FC<{
    17  - content: string;
    18  -}> = ({content}) => (
    19  - <Alert
    20  - style={{
    21  - marginBottom: 24,
    22  - }}
    23  - message={content}
    24  - type="error"
    25  - showIcon
    26  - />
    27  -);
    28  - 
    29 16  const Login: React.FC = () => {
    30  - const [userLoginState, setUserLoginState] = useState<Auth.Response>({
    31  - code: 0,
    32  - data: "",
    33  - msg: "",
    34  - success: false,
    35  - total: 0
    36  - });
    37 17   const {initialState, setInitialState} = useModel('@@initialState');
    38 18   
    39 19   const fetchUserInfo = async () => {
    skipped 24 lines
    64 44   } else {
    65 45   message.error(msg.code + ":" + msg.msg)
    66 46   }
    67  - // 如果失败去设置用户错误信息
    68  - setUserLoginState(msg);
    69 47   } catch (error) {
    70 48   message.error('登录失败,请重试!');
    71 49   }
    skipped 12 lines
    84 62   await handleSubmit(values as Auth.User);
    85 63   }}
    86 64   >
    87  - {!userLoginState.success && userLoginState.msg !== "" ? (
    88  - <LoginMessage
    89  - content={userLoginState.msg === undefined ? "登陆失败" : userLoginState.msg}
    90  - />
    91  - ) : ""}
    92  - 
    93 65   <>
    94 66   <ProFormText
    95 67   name="Username"
    skipped 35 lines
  • ■ ■ ■ ■ ■
    web/src/services/setting/types.d.ts
    skipped 3 lines
    4 4   control_port: string,
    5 5   control_log: string,
    6 6   auto_start: string,
     7 + auto_sync: string,
    7 8   version: string
    8 9   }
    9 10  }
    skipped 1 lines
Please wait...
Page is in error, reload to recover