|
|
@@ -9,6 +9,7 @@ import (
|
|
|
"github.com/sagernet/sing-box/adapter"
|
|
|
"github.com/sagernet/sing-box/common/urltest"
|
|
|
"github.com/sagernet/sing-box/outbound"
|
|
|
+ E "github.com/sagernet/sing/common/exceptions"
|
|
|
"github.com/sagernet/sing/common/rw"
|
|
|
"github.com/sagernet/sing/service"
|
|
|
)
|
|
|
@@ -18,6 +19,7 @@ type OutboundGroup struct {
|
|
|
Type string
|
|
|
Selectable bool
|
|
|
Selected string
|
|
|
+ isExpand int8
|
|
|
items []*OutboundGroupItem
|
|
|
}
|
|
|
|
|
|
@@ -25,6 +27,19 @@ func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
|
|
|
return newIterator(g.items)
|
|
|
}
|
|
|
|
|
|
+func (g *OutboundGroup) IsExpand() bool {
|
|
|
+ switch g.isExpand {
|
|
|
+ case -1:
|
|
|
+ return g.Selectable
|
|
|
+ case 0:
|
|
|
+ return false
|
|
|
+ case 1:
|
|
|
+ return true
|
|
|
+ default:
|
|
|
+ panic("unexpected expand value")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
type OutboundGroupIterator interface {
|
|
|
Next() *OutboundGroup
|
|
|
HasNext() bool
|
|
|
@@ -114,6 +129,11 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
|
|
|
return nil, err
|
|
|
}
|
|
|
|
|
|
+ err = binary.Read(reader, binary.BigEndian, &group.isExpand)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
var itemLength uint16
|
|
|
err = binary.Read(reader, binary.BigEndian, &itemLength)
|
|
|
if err != nil {
|
|
|
@@ -152,6 +172,10 @@ func readGroups(reader io.Reader) (OutboundGroupIterator, error) {
|
|
|
|
|
|
func writeGroups(writer io.Writer, boxService *BoxService) error {
|
|
|
historyStorage := service.PtrFromContext[urltest.HistoryStorage](boxService.ctx)
|
|
|
+ var cacheFile adapter.ClashCacheFile
|
|
|
+ if clashServer := boxService.instance.Router().ClashServer(); clashServer != nil {
|
|
|
+ cacheFile = clashServer.CacheFile()
|
|
|
+ }
|
|
|
|
|
|
outbounds := boxService.instance.Router().Outbounds()
|
|
|
var iGroups []adapter.OutboundGroup
|
|
|
@@ -167,6 +191,15 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
|
|
group.Type = iGroup.Type()
|
|
|
_, group.Selectable = iGroup.(*outbound.Selector)
|
|
|
group.Selected = iGroup.Now()
|
|
|
+ if cacheFile != nil {
|
|
|
+ if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); !loaded {
|
|
|
+ group.isExpand = -1
|
|
|
+ } else if isExpand {
|
|
|
+ group.isExpand = 1
|
|
|
+ } else {
|
|
|
+ group.isExpand = 0
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
for _, itemTag := range iGroup.All() {
|
|
|
itemOutbound, isLoaded := boxService.instance.Router().Outbound(itemTag)
|
|
|
@@ -207,6 +240,10 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
+ err = binary.Write(writer, binary.BigEndian, group.isExpand)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
err = binary.Write(writer, binary.BigEndian, uint16(len(group.items)))
|
|
|
if err != nil {
|
|
|
return err
|
|
|
@@ -232,3 +269,50 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
|
|
}
|
|
|
return nil
|
|
|
}
|
|
|
+
|
|
|
+func (c *CommandClient) SetGroupExpand(groupTag string, isExpand bool) error {
|
|
|
+ conn, err := c.directConnect()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer conn.Close()
|
|
|
+ err = binary.Write(conn, binary.BigEndian, uint8(CommandGroupExpand))
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ err = rw.WriteVString(conn, groupTag)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ err = binary.Write(conn, binary.BigEndian, isExpand)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return readError(conn)
|
|
|
+}
|
|
|
+
|
|
|
+func (s *CommandServer) handleSetGroupExpand(conn net.Conn) error {
|
|
|
+ defer conn.Close()
|
|
|
+ groupTag, err := rw.ReadVString(conn)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ var isExpand bool
|
|
|
+ err = binary.Read(conn, binary.BigEndian, &isExpand)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ service := s.service
|
|
|
+ if service == nil {
|
|
|
+ return writeError(conn, E.New("service not ready"))
|
|
|
+ }
|
|
|
+ if clashServer := service.instance.Router().ClashServer(); clashServer != nil {
|
|
|
+ if cacheFile := clashServer.CacheFile(); cacheFile != nil {
|
|
|
+ err = cacheFile.StoreGroupExpand(groupTag, isExpand)
|
|
|
+ if err != nil {
|
|
|
+ return writeError(conn, err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return writeError(conn, nil)
|
|
|
+}
|