package main import ( "bufio" "flag" "fmt" "os" "path/filepath" "strings" "github.com/v2fly/domain-list-community/internal/dlc" router "github.com/v2fly/v2ray-core/v5/app/router/routercommon" "google.golang.org/protobuf/proto" ) var ( inputData = flag.String("inputdata", "dlc.dat", "Name of the geosite dat file") outputDir = flag.String("outputdir", "./", "Directory to place all generated files") exportLists = flag.String("exportlists", "", "Lists to be exported, separated by ',' (empty for _all_)") ) type GeoSites struct { Sites []*router.GeoSite SiteIdx map[string]int } func loadGeosite(path string) (*GeoSites, error) { data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("failed to read geosite file: %w", err) } vgeositeList := new(router.GeoSiteList) if err := proto.Unmarshal(data, vgeositeList); err != nil { return nil, fmt.Errorf("failed to unmarshal: %w", err) } gs := &GeoSites{Sites: vgeositeList.Entry} gs.SiteIdx = make(map[string]int, len(gs.Sites)) for i, site := range gs.Sites { gs.SiteIdx[strings.ToUpper(site.CountryCode)] = i } return gs, nil } func domain2Builder(d *router.Domain, b *strings.Builder) error { switch d.Type { case router.Domain_RootDomain: b.WriteString(dlc.RuleTypeDomain) case router.Domain_Full: b.WriteString(dlc.RuleTypeFullDomain) case router.Domain_Plain: b.WriteString(dlc.RuleTypeKeyword) case router.Domain_Regex: b.WriteString(dlc.RuleTypeRegexp) default: return fmt.Errorf("invalid rule type: %+v", d.Type) } b.WriteByte(':') b.WriteString(d.Value) for i, attr := range d.Attribute { if i == 0 { b.WriteByte(':') } else { b.WriteByte(',') } b.WriteByte('@') b.WriteString(attr.Key) } return nil } func exportSite(name string, gs *GeoSites) error { idx, ok := gs.SiteIdx[strings.ToUpper(name)] if !ok { return fmt.Errorf("list %q does not exist", name) } vDomains := gs.Sites[idx].Domain if len(vDomains) == 0 { return fmt.Errorf("list %q is empty", name) } file, err := os.Create(filepath.Join(*outputDir, name+".yml")) if err != nil { return err } defer file.Close() w := bufio.NewWriter(file) fmt.Fprintf(w, "%q:\n", name) var b strings.Builder b.Grow(64) for _, vdomain := range vDomains { b.Reset() if err := domain2Builder(vdomain, &b); err != nil { return err } fmt.Fprintf(w, " - %q\n", b.String()) } return w.Flush() } func exportAll(filename string, gs *GeoSites) error { file, err := os.Create(filepath.Join(*outputDir, filename)) if err != nil { return err } defer file.Close() w := bufio.NewWriter(file) w.WriteString("lists:\n") var b strings.Builder b.Grow(64) for _, site := range gs.Sites { fmt.Fprintf(w, " - name: %q\n", strings.ToLower(site.CountryCode)) fmt.Fprintf(w, " length: %d\n", len(site.Domain)) w.WriteString(" rules:\n") for _, vdomain := range site.Domain { b.Reset() if err := domain2Builder(vdomain, &b); err != nil { return err } fmt.Fprintf(w, " - %q\n", b.String()) } } return w.Flush() } func run() error { // Make sure output directory exists if err := os.MkdirAll(*outputDir, 0755); err != nil { return fmt.Errorf("failed to create output directory: %w", err) } fmt.Printf("loading source data %q...\n", *inputData) geoSites, err := loadGeosite(*inputData) if err != nil { return fmt.Errorf("failed to loadGeosite: %w", err) } var exportListSlice []string for raw := range strings.SplitSeq(*exportLists, ",") { if trimmed := strings.TrimSpace(raw); trimmed != "" { exportListSlice = append(exportListSlice, trimmed) } } if len(exportListSlice) == 0 { exportListSlice = []string{"_all_"} } failedCount := 0 for _, eplistname := range exportListSlice { if strings.EqualFold(eplistname, "_all_") { if err := exportAll(filepath.Base(*inputData)+"_plain.yml", geoSites); err != nil { fmt.Printf("[Error] failed to exportAll: %v\n", err) failedCount++ continue } } else { if err := exportSite(eplistname, geoSites); err != nil { fmt.Printf("[Error] failed to exportSite: %v\n", err) failedCount++ continue } } fmt.Printf("list: %q has been exported successfully\n", eplistname) } if failedCount > 0 { return fmt.Errorf("%d list(s) failed to be exported", failedCount) } return nil } func main() { flag.Parse() if err := run(); err != nil { fmt.Printf("[Fatal] critical error: %v\n", err) os.Exit(1) } }