Compare commits

...

5 Commits

Author SHA1 Message Date
EpLiar
85edae7ba1 Add new Binance API endpoint 'binanceru.net' (#3210) 2026-01-23 14:57:08 +08:00
MkQtS
1bd07b2e76 Support to export all lists to a plain yml (#3211)
* Refactor: improve deduplicate

* Feat: support to export all lists to a plain yml

use: `--exportlists=_all_`

* Docs: add links for dlc plain yml

[skip ci]
2026-01-23 14:56:35 +08:00
Kusu
614a880a55 okx: add okx.cab (#3209) 2026-01-22 22:14:50 +08:00
MkQtS
676832d14a Improve value checkers and docs (#3208)
* Refactor: improve value checkers

* Docs: small improvements

[skip ci]
2026-01-22 18:46:53 +08:00
MkQtS
a2f08a142c Docs: update for selective inclusion and affiliations (#3207)
[skip ci]
2026-01-22 14:30:10 +08:00
6 changed files with 91 additions and 43 deletions

View File

@@ -33,15 +33,16 @@ jobs:
echo "TAG_NAME=$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV echo "TAG_NAME=$(date +%Y%m%d%H%M%S)" >> $GITHUB_ENV
shell: bash shell: bash
- name: Build dlc.dat file - name: Build dlc.dat and plain lists
run: | run: |
cd code || exit 1 cd code || exit 1
go run ./ --outputdir=../ --exportlists=category-ads-all,tld-cn,cn,tld-\!cn,geolocation-\!cn,apple,icloud go run ./ --outputdir=../ --exportlists=_all_,category-ads-all,tld-cn,cn,tld-\!cn,geolocation-\!cn,apple,icloud
cd ../ && rm -rf code cd ../ && rm -rf code
- name: Generate dlc.dat sha256 hash - name: Generate dlc.dat sha256 hash
run: | run: |
sha256sum dlc.dat > dlc.dat.sha256sum sha256sum dlc.dat > dlc.dat.sha256sum
sha256sum dlc.dat_plain.yml > dlc.dat_plain.yml.sha256sum
- name: Generate Zip - name: Generate Zip
run: | run: |
@@ -66,6 +67,6 @@ jobs:
- name: Release and upload assets - name: Release and upload assets
run: | run: |
gh release create ${{ env.TAG_NAME }} --generate-notes --latest --title ${{ env.RELEASE_NAME }} ./dlc.dat ./dlc.dat.* gh release create ${{ env.TAG_NAME }} --generate-notes --latest --title ${{ env.RELEASE_NAME }} ./dlc.dat ./dlc.dat.* ./dlc.dat_plain.yml ./dlc.dat_plain.yml.*
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -8,4 +8,5 @@
dlc.dat dlc.dat
# Exported plaintext lists. # Exported plaintext lists.
dlc.dat_plain.yml
/*.txt /*.txt

View File

@@ -10,12 +10,12 @@ This project is not opinionated. In other words, it does NOT endorse, claim or i
- **dlc.dat**[https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat](https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat) - **dlc.dat**[https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat](https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat)
- **dlc.dat.sha256sum**[https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat.sha256sum](https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat.sha256sum) - **dlc.dat.sha256sum**[https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat.sha256sum](https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat.sha256sum)
- **dlc.dat_plain.yml**[https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml](https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml)
- **dlc.dat_plain.yml.sha256sum**[https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml.sha256sum](https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat_plain.yml.sha256sum)
## Notice ## Notice
Rules with `@!cn` attribute has been cast out from cn lists. `geosite:geolocation-cn@!cn` is no longer available. Rules with `@!cn` attribute has been cast out from cn lists. `geosite:geolocation-cn@!cn` is no longer available. Check [#390](https://github.com/v2fly/domain-list-community/issues/390), [#3119](https://github.com/v2fly/domain-list-community/pull/3119) and [#3198](https://github.com/v2fly/domain-list-community/pull/3198) for more information.
Check [#390](https://github.com/v2fly/domain-list-community/issues/390), [#3119](https://github.com/v2fly/domain-list-community/pull/3119) and [#3198](https://github.com/v2fly/domain-list-community/pull/3198) for more information.
Please report if you have any problems or questions. Please report if you have any problems or questions.
@@ -93,38 +93,45 @@ All data are under `data` directory. Each file in the directory represents a sub
# comments # comments
include:another-file include:another-file
domain:google.com @attr1 @attr2 domain:google.com @attr1 @attr2
full:analytics.google.com @ads
keyword:google keyword:google
regexp:www\.google\.com$ regexp:^odd[1-7]\.example\.org(\.[a-z]{2})?$
full:www.google.com
``` ```
**Syntax:** **Syntax:**
> [!NOTE]
> Adding new `regexp` and `keyword` rules is discouraged because it is easy to use them incorrectly, and proxy software cannot efficiently match these types of rules.
> [!NOTE]
> The following types of rules are **NOT** fully compatible with the ones that defined by user in V2Ray config file. Do **Not** copy and paste directly. > The following types of rules are **NOT** fully compatible with the ones that defined by user in V2Ray config file. Do **Not** copy and paste directly.
- Comment begins with `#`. It may begin anywhere in the file. The content in the line after `#` is treated as comment and ignored in production. - Comment begins with `#`. It may begin anywhere in the file. The content in the line after `#` is treated as comment and ignored in production.
- Inclusion begins with `include:`, followed by the file name of an existing file in the same directory.
- Subdomain begins with `domain:`, followed by a valid domain name. The prefix `domain:` may be omitted. - Subdomain begins with `domain:`, followed by a valid domain name. The prefix `domain:` may be omitted.
- Keyword begins with `keyword:`, followed by a string.
- Regular expression begins with `regexp:`, followed by a valid regular expression (per Golang's standard).
- Full domain begins with `full:`, followed by a complete and valid domain name. - Full domain begins with `full:`, followed by a complete and valid domain name.
- Domains (including `domain`, `keyword`, `regexp` and `full`) may have one or more attributes. Each attribute begins with `@` and followed by the name of the attribute. - Keyword begins with `keyword:`, followed by a substring of a valid domain name.
- Regular expression begins with `regexp:`, followed by a valid regular expression (per Golang's standard).
> **Note:** Adding new `regexp` and `keyword` rules is discouraged because it is easy to use them incorrectly, and proxy software cannot efficiently match these types of rules. - Domain rules (including `domain`, `full`, `keyword`, and `regexp`) may have none, one or more attributes. Each attribute begins with `@` and followed by the name of the attribute. Attributes will remain available in final lists and `dlc.dat`.
- Domain rules may have none, one or more affiliations, which additionally adds the domain rule into the affiliated target list. Each affiliation begins with `&` and followed by the name of the target list (nomatter whether the target has a dedicated file in data path). This is a method for data management, and will not remain in the final lists or `dlc.dat`.
- Inclusion begins with `include:`, followed by the name of another valid domain list. A simple `include:listb` in file `lista` means adding all domain rules of `listb` into `lista`. Inclusions with attributes stands for selective inclusion. `include:listb @attr1 @-attr2` means only adding those domain rules *with* `@attr1` **and** *without* `@attr2`. This is a special type for data management, and will not remain in the final lists or `dlc.dat`.
## How it works ## How it works
The entire `data` directory will be built into an external `geosite` file for Project V. Each file in the directory represents a section in the generated file. The entire `data` directory will be built into an external `geosite` file for Project V. Each file in the directory represents a section in the generated file.
To generate a section: **General steps:**
1. Remove all the comments in the file. 1. Read files in the data path (ignore all comments and empty lines).
2. Replace `include:` lines with the actual content of the file. 2. Parse and resolve source data, turn affiliations and inclusions into actual domain rules in proper lists.
3. Omit all empty lines. 3. Deduplicate and sort rules in every list.
4. Generate each `domain:` line into a [sub-domain routing rule](https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto#L21). 4. Export desired plain text lists.
5. Generate each `full:` line into a [full domain routing rule](https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto#L23). 5. Generate `dlc.dat`:
6. Generate each `keyword:` line into a [plain domain routing rule](https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto#L17). - turn each `domain:` line into a [sub-domain routing rule](https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto#L21).
7. Generate each `regexp:` line into a [regex domain routing rule](https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto#L19). - turn each `full:` line into a [full domain routing rule](https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto#L23).
- turn each `keyword:` line into a [plain domain routing rule](https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto#L17).
- turn each `regexp:` line into a [regex domain routing rule](https://github.com/v2fly/v2ray-core/blob/master/app/router/routercommon/common.proto#L19).
Read [main.go](./main.go) for details.
## How to organize domains ## How to organize domains

View File

@@ -28,6 +28,7 @@ binancezh.top
# API # API
binanceapi.com binanceapi.com
binanceru.net
bnbstatic.com bnbstatic.com
bntrace.com bntrace.com
nftstatic.com nftstatic.com

View File

@@ -1,8 +1,9 @@
okex.com okex.com
okx.com
okx-dns.com okx-dns.com
okx-dns1.com okx-dns1.com
okx-dns2.com okx-dns2.com
okx.cab
okx.com
# OKC Browser # OKC Browser
oklink.com @cn oklink.com @cn

65
main.go
View File

@@ -31,7 +31,7 @@ const (
var ( var (
TypeChecker = regexp.MustCompile(`^(domain|full|keyword|regexp|include)$`) TypeChecker = regexp.MustCompile(`^(domain|full|keyword|regexp|include)$`)
ValueChecker = regexp.MustCompile(`^[a-z0-9!\.-]+$`) DomainChecker = regexp.MustCompile(`^[a-z0-9\.-]+$`)
AttrChecker = regexp.MustCompile(`^[a-z0-9!-]+$`) AttrChecker = regexp.MustCompile(`^[a-z0-9!-]+$`)
SiteChecker = regexp.MustCompile(`^[A-Z0-9!-]+$`) SiteChecker = regexp.MustCompile(`^[A-Z0-9!-]+$`)
) )
@@ -92,6 +92,25 @@ func makeProtoList(listName string, entries []*Entry) (*router.GeoSite, error) {
return site, nil return site, nil
} }
func writePlainAll(siteList *[]string) error {
file, err := os.Create(filepath.Join(*outputDir, *outputName + "_plain.yml"))
if err != nil {
return err
}
defer file.Close()
w := bufio.NewWriter(file)
w.WriteString("lists:\n")
for _, site := range *siteList {
fmt.Fprintf(w, " - name: %s\n", strings.ToLower(site))
fmt.Fprintf(w, " length: %d\n", len(finalMap[site]))
w.WriteString(" rules:\n")
for _, entry := range finalMap[site] {
fmt.Fprintf(w, " - %s\n", entry.Plain)
}
}
return w.Flush()
}
func writePlainList(exportedName string) error { func writePlainList(exportedName string) error {
targetList, exist := finalMap[strings.ToUpper(exportedName)] targetList, exist := finalMap[strings.ToUpper(exportedName)]
if !exist || len(targetList) == 0 { if !exist || len(targetList) == 0 {
@@ -123,6 +142,8 @@ func parseEntry(line string) (Entry, error) {
entry.Type = strings.ToLower(kv[0]) entry.Type = strings.ToLower(kv[0])
if entry.Type == RuleTypeRegexp { if entry.Type == RuleTypeRegexp {
entry.Value = kv[1] entry.Value = kv[1]
} else if entry.Type == RuleTypeInclude {
entry.Value = strings.ToUpper(kv[1])
} else { } else {
entry.Value = strings.ToLower(kv[1]) entry.Value = strings.ToLower(kv[1])
} }
@@ -133,12 +154,19 @@ func parseEntry(line string) (Entry, error) {
if !TypeChecker.MatchString(entry.Type) { if !TypeChecker.MatchString(entry.Type) {
return entry, fmt.Errorf("invalid type: %s", entry.Type) return entry, fmt.Errorf("invalid type: %s", entry.Type)
} }
if entry.Type == RuleTypeRegexp { switch entry.Type {
case RuleTypeRegexp:
if _, err := regexp.Compile(entry.Value); err != nil { if _, err := regexp.Compile(entry.Value); err != nil {
return entry, fmt.Errorf("invalid regexp: %s", entry.Value) return entry, fmt.Errorf("invalid regexp: %s", entry.Value)
} }
} else if !ValueChecker.MatchString(entry.Value) { case RuleTypeInclude:
return entry, fmt.Errorf("invalid value: %s", entry.Value) if !SiteChecker.MatchString(entry.Value) {
return entry, fmt.Errorf("invalid included list name: %s", entry.Value)
}
default: // `full`, `domain` and `keyword` are all (parts of) domains
if !DomainChecker.MatchString(entry.Value) {
return entry, fmt.Errorf("invalid domain: %s", entry.Value)
}
} }
// Parse/Check attributes and affiliations // Parse/Check attributes and affiliations
@@ -214,7 +242,7 @@ func parseList(refName string, refList []*Entry) error {
if len(entry.Affs) != 0 { if len(entry.Affs) != 0 {
return fmt.Errorf("affiliation is not allowed for include:%s", entry.Value) return fmt.Errorf("affiliation is not allowed for include:%s", entry.Value)
} }
inc := &Inclusion{Source: strings.ToUpper(entry.Value)} inc := &Inclusion{Source: entry.Value}
for _, attr := range entry.Attrs { for _, attr := range entry.Attrs {
if strings.HasPrefix(attr, "-") { if strings.HasPrefix(attr, "-") {
inc.BanAttrs = append(inc.BanAttrs, attr[1:]) // Trim attribute prefix `-` character inc.BanAttrs = append(inc.BanAttrs, attr[1:]) // Trim attribute prefix `-` character
@@ -266,7 +294,10 @@ func polishList(roughMap *map[string]*Entry) []*Entry {
// Remove redundant subdomains for full/domain without attr // Remove redundant subdomains for full/domain without attr
for _, qentry := range queuingList { for _, qentry := range queuingList {
isRedundant := false isRedundant := false
pd := qentry.Value // Parent domain pd := qentry.Value // To be parent domain
if qentry.Type == RuleTypeFullDomain {
pd = "." + pd // So that `domain:example.org` overrides `full:example.org`
}
for { for {
idx := strings.Index(pd, ".") idx := strings.Index(pd, ".")
if idx == -1 { break } if idx == -1 { break }
@@ -364,13 +395,16 @@ func main() {
} }
} }
// Generate finalMap // Generate finalMap and sorted list of site names
siteList := make([]string, 0 ,len(plMap))
for _, pl := range plMap { for _, pl := range plMap {
siteList = append(siteList, pl.Name)
if err := resolveList(pl); err != nil { if err := resolveList(pl); err != nil {
fmt.Println("Failed to resolveList:", err) fmt.Println("Failed to resolveList:", err)
os.Exit(1) os.Exit(1)
} }
} }
slices.Sort(siteList)
// Create output directory if not exist // Create output directory if not exist
if _, err := os.Stat(*outputDir); os.IsNotExist(err) { if _, err := os.Stat(*outputDir); os.IsNotExist(err) {
@@ -384,28 +418,31 @@ func main() {
if *exportLists != "" { if *exportLists != "" {
exportedListSlice := strings.Split(*exportLists, ",") exportedListSlice := strings.Split(*exportLists, ",")
for _, exportedList := range exportedListSlice { for _, exportedList := range exportedListSlice {
if exportedList == "_all_" {
if err := writePlainAll(&siteList); err != nil {
fmt.Println("Failed to writePlainAll:", err)
continue
}
} else {
if err := writePlainList(exportedList); err != nil { if err := writePlainList(exportedList); err != nil {
fmt.Println("Failed to write list:", err) fmt.Println("Failed to write list:", err)
continue continue
} }
}
fmt.Printf("list: '%s' has been generated successfully.\n", exportedList) fmt.Printf("list: '%s' has been generated successfully.\n", exportedList)
} }
} }
// Generate dat file // Generate dat file
protoList := new(router.GeoSiteList) protoList := new(router.GeoSiteList)
for siteName, siteEntries := range finalMap { for _, siteName := range siteList { // So that protoList.Entry is sorted
site, err := makeProtoList(siteName, siteEntries) site, err := makeProtoList(siteName, finalMap[siteName])
if err != nil { if err != nil {
fmt.Println("Failed:", err) fmt.Println("Failed to makeProtoList:", err)
os.Exit(1) os.Exit(1)
} }
protoList.Entry = append(protoList.Entry, site) protoList.Entry = append(protoList.Entry, site)
} }
// Sort protoList so the marshaled list is reproducible
slices.SortFunc(protoList.Entry, func(a, b *router.GeoSite) int {
return strings.Compare(a.CountryCode, b.CountryCode)
})
protoBytes, err := proto.Marshal(protoList) protoBytes, err := proto.Marshal(protoList)
if err != nil { if err != nil {