mirror of
https://github.com/v2fly/domain-list-community.git
synced 2026-08-27 13:40:16 +03:00
* Fix panic on empty inclusion attribute, rule loss in selective inclusion and other bugs * Exercise domain/list-name validators in tests * Consolidate parsed list state into ParsedList and simplify inclusion parsing Co-authored-by: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com>
176 lines
4.4 KiB
Go
176 lines
4.4 KiB
Go
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, "%s:\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)
|
|
}
|
|
}
|