├── data └── .gitkeep ├── .gitignore ├── .golangci.yml ├── .github ├── update_dependencies.sh └── workflows │ ├── build.yaml │ └── release.yaml ├── LICENSE ├── go.mod ├── README.md ├── go.sum └── main.go /data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /vendor/ 3 | *.db 4 | *.dat 5 | .git-credentials 6 | /geosite.db 7 | /rule-set/ -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | disable-all: true 3 | enable: 4 | - gofumpt 5 | - govet 6 | - gci 7 | - staticcheck 8 | -------------------------------------------------------------------------------- /.github/update_dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROJECTS=$(dirname "$0")/../.. 4 | 5 | go get -x github.com/sagernet/sing-box@$(git -C $PROJECTS/sing-box rev-parse HEAD) 6 | go mod tidy 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2022 by nekohasekai 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bootmortis/sing-geosite 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/sagernet/sing v0.2.20-0.20231212123824-8836b6754226 7 | github.com/sagernet/sing-box v1.8.0-beta.3 8 | github.com/v2fly/v2ray-core/v5 v5.13.0 9 | google.golang.org/protobuf v1.31.0 10 | ) 11 | 12 | require ( 13 | github.com/adrg/xdg v0.4.0 // indirect 14 | github.com/golang/protobuf v1.5.3 // indirect 15 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 16 | github.com/miekg/dns v1.1.57 // indirect 17 | github.com/sagernet/sing-dns v0.1.11 // indirect 18 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect 19 | golang.org/x/mod v0.14.0 // indirect 20 | golang.org/x/net v0.19.0 // indirect 21 | golang.org/x/sys v0.15.0 // indirect 22 | golang.org/x/tools v0.16.0 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - main 6 | jobs: 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Get latest go version 16 | id: version 17 | run: | 18 | echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') 19 | - name: Setup Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: ${{ steps.version.outputs.go_version }} 23 | - name: Build geosite 24 | id: build 25 | env: 26 | NO_SKIP: true 27 | run: | 28 | go run -v . 29 | - name: Upload artifacts 30 | uses: actions/upload-artifact@v2 31 | with: 32 | name: geosite.db 33 | path: geosite.db -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IR sing-geosite 2 | 3 | Used in https://github.com/bootmortis/iran-hosted-domains 4 | 5 | 6 | ## Categories 7 | - `ir`: handpicked `.ir` domains, use as `direct`. 8 | - `other`: non `.ir` domains, use as `direct`. 9 | - `tld-ir`: all `.ir` domains, use as `direct`. 10 | - `all`: a combination of `other` and `tld-ir`, should be used as `direct`. 11 | - `ads`: Iran-related advertising services that need to be `blocked`. 12 | - `proxy`: Iran-related domains that are blocked inside of iran and need to be `proxied`. 13 | 14 | ## Usage 15 | 16 | For more info check [here](https://github.com/bootmortis/iran-hosted-domains?tab=readme-ov-file#sing-box). 17 | 18 | ### Geosite 19 | `https://github.com/bootmortis/iran-hosted-domains/releases/latest/download/iran-geosite.db` 20 | 21 | ### Rule-set (v1.8 and later) 22 | `https://github.com/bootmortis/sing-geosite/releases/latest/download/geosite-.srs` 23 | 24 | replace `` with the category name. example URL: 25 | 26 | `https://github.com/bootmortis/sing-geosite/releases/latest/download/geosite-ir.srs` 27 | 28 | ## Documents 29 | - [How to use geosite](https://github.com/bootmortis/iran-hosted-domains?tab=readme-ov-file#sing-box) 30 | - [Migrate Geosite to rule sets (v1.8)](https://sing-box.sagernet.org/migration/#migrate-geosite-to-rule-sets) 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | workflow_dispatch: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | jobs: 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | fetch-depth: 0 15 | - name: Get latest go version 16 | id: version 17 | run: | 18 | echo ::set-output name=go_version::$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') 19 | - name: Setup Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: ${{ steps.version.outputs.go_version }} 23 | - name: Build geosite 24 | id: build 25 | run: | 26 | go run -v . 27 | - name: Generate sha256 hash 28 | if: steps.build.outputs.skip != 'true' 29 | run: | 30 | sha256sum geosite.db > geosite.db.sha256sum 31 | - name: Create a release 32 | if: steps.build.outputs.skip != 'true' 33 | id: create_release 34 | uses: actions/create-release@v1 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | with: 38 | tag_name: ${{ steps.build.outputs.tag }} 39 | release_name: ${{ steps.build.outputs.tag }} 40 | draft: false 41 | prerelease: false 42 | - name: Release geosite.db 43 | if: steps.build.outputs.skip != 'true' 44 | uses: actions/upload-release-asset@v1 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | with: 48 | upload_url: ${{ steps.create_release.outputs.upload_url }} 49 | asset_path: ./geosite.db 50 | asset_name: geosite.db 51 | asset_content_type: application/octet-stream 52 | - name: Release geosite.db sha256sum 53 | if: steps.build.outputs.skip != 'true' 54 | uses: actions/upload-release-asset@v1 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 57 | with: 58 | upload_url: ${{ steps.create_release.outputs.upload_url }} 59 | asset_path: ./geosite.db.sha256sum 60 | asset_name: geosite.db.sha256sum 61 | asset_content_type: text/plain -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= 2 | github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 6 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 7 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 8 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 9 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 10 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 11 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 12 | github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= 13 | github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= 14 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/sagernet/sing v0.2.20-0.20231212123824-8836b6754226 h1:rcII71ho6F/7Nyx7n2kESLcnvNMdcU4i8ZUGF2Fi7yA= 17 | github.com/sagernet/sing v0.2.20-0.20231212123824-8836b6754226/go.mod h1:Ce5LNojQOgOiWhiD8pPD6E9H7e2KgtOe3Zxx4Ou5u80= 18 | github.com/sagernet/sing-box v1.8.0-beta.3 h1:2uotZm8GQrx6lztuHzdmHR4Fk/FV6G7eETWmcTmCWi8= 19 | github.com/sagernet/sing-box v1.8.0-beta.3/go.mod h1:WOnlC8xcuQDYvkSXi0k6Szfqn5CXSV2nmQzCIiZ/nRI= 20 | github.com/sagernet/sing-dns v0.1.11 h1:PPrMCVVrAeR3f5X23I+cmvacXJ+kzuyAsBiWyUKhGSE= 21 | github.com/sagernet/sing-dns v0.1.11/go.mod h1:zJ/YjnYB61SYE+ubMcMqVdpaSvsyQ2iShQGO3vuLvvE= 22 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 23 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 24 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 25 | github.com/v2fly/v2ray-core/v5 v5.13.0 h1:BDJfi3Ftx1NpQlZZPpeWJe3RDqRNyIVBs+YGG4RRMDU= 26 | github.com/v2fly/v2ray-core/v5 v5.13.0/go.mod h1:Bc3gmQWLr8UR7xBSCYI9FbfKuVvqA9lbkeBTWNRRAS4= 27 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= 28 | go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 29 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 30 | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 31 | golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= 32 | golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= 33 | golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= 34 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 35 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 36 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 37 | golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= 38 | golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 39 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 40 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 41 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 42 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 43 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 44 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 45 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 46 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 47 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "sort" 7 | "strings" 8 | 9 | "github.com/sagernet/sing-box/common/geosite" 10 | "github.com/sagernet/sing-box/common/srs" 11 | C "github.com/sagernet/sing-box/constant" 12 | "github.com/sagernet/sing-box/log" 13 | "github.com/sagernet/sing-box/option" 14 | "github.com/sagernet/sing/common" 15 | 16 | "github.com/v2fly/v2ray-core/v5/app/router/routercommon" 17 | "google.golang.org/protobuf/proto" 18 | ) 19 | 20 | type filteredCodePair struct { 21 | code string 22 | badCode string 23 | } 24 | 25 | func filterTags(data map[string][]geosite.Item) { 26 | var codeList []string 27 | for code := range data { 28 | codeList = append(codeList, code) 29 | } 30 | var badCodeList []filteredCodePair 31 | var filteredCodeMap []string 32 | var mergedCodeMap []string 33 | for _, code := range codeList { 34 | codeParts := strings.Split(code, "@") 35 | if len(codeParts) != 2 { 36 | continue 37 | } 38 | leftParts := strings.Split(codeParts[0], "-") 39 | var lastName string 40 | if len(leftParts) > 1 { 41 | lastName = leftParts[len(leftParts)-1] 42 | } 43 | if lastName == "" { 44 | lastName = codeParts[0] 45 | } 46 | if lastName == codeParts[1] { 47 | delete(data, code) 48 | filteredCodeMap = append(filteredCodeMap, code) 49 | continue 50 | } 51 | if "!"+lastName == codeParts[1] { 52 | badCodeList = append(badCodeList, filteredCodePair{ 53 | code: codeParts[0], 54 | badCode: code, 55 | }) 56 | } else if lastName == "!"+codeParts[1] { 57 | badCodeList = append(badCodeList, filteredCodePair{ 58 | code: codeParts[0], 59 | badCode: code, 60 | }) 61 | } 62 | } 63 | for _, it := range badCodeList { 64 | badList := data[it.badCode] 65 | if badList == nil { 66 | panic("bad list not found: " + it.badCode) 67 | } 68 | delete(data, it.badCode) 69 | newMap := make(map[geosite.Item]bool) 70 | for _, item := range data[it.code] { 71 | newMap[item] = true 72 | } 73 | for _, item := range badList { 74 | delete(newMap, item) 75 | } 76 | newList := make([]geosite.Item, 0, len(newMap)) 77 | for item := range newMap { 78 | newList = append(newList, item) 79 | } 80 | data[it.code] = newList 81 | mergedCodeMap = append(mergedCodeMap, it.badCode) 82 | } 83 | sort.Strings(filteredCodeMap) 84 | sort.Strings(mergedCodeMap) 85 | os.Stderr.WriteString("filtered " + strings.Join(filteredCodeMap, ",") + "\n") 86 | os.Stderr.WriteString("merged " + strings.Join(mergedCodeMap, ",") + "\n") 87 | } 88 | 89 | func parse(vGeositeData []byte) (map[string][]geosite.Item, error) { 90 | vGeositeList := routercommon.GeoSiteList{} 91 | err := proto.Unmarshal(vGeositeData, &vGeositeList) 92 | if err != nil { 93 | return nil, err 94 | } 95 | domainMap := make(map[string][]geosite.Item) 96 | for _, vGeositeEntry := range vGeositeList.Entry { 97 | code := strings.ToLower(vGeositeEntry.CountryCode) 98 | domains := make([]geosite.Item, 0, len(vGeositeEntry.Domain)*2) 99 | attributes := make(map[string][]*routercommon.Domain) 100 | for _, domain := range vGeositeEntry.Domain { 101 | if len(domain.Attribute) > 0 { 102 | for _, attribute := range domain.Attribute { 103 | attributes[attribute.Key] = append(attributes[attribute.Key], domain) 104 | } 105 | } 106 | switch domain.Type { 107 | case routercommon.Domain_Plain: 108 | domains = append(domains, geosite.Item{ 109 | Type: geosite.RuleTypeDomainKeyword, 110 | Value: domain.Value, 111 | }) 112 | case routercommon.Domain_Regex: 113 | domains = append(domains, geosite.Item{ 114 | Type: geosite.RuleTypeDomainRegex, 115 | Value: domain.Value, 116 | }) 117 | case routercommon.Domain_RootDomain: 118 | if strings.Contains(domain.Value, ".") { 119 | domains = append(domains, geosite.Item{ 120 | Type: geosite.RuleTypeDomain, 121 | Value: domain.Value, 122 | }) 123 | } 124 | domains = append(domains, geosite.Item{ 125 | Type: geosite.RuleTypeDomainSuffix, 126 | Value: "." + domain.Value, 127 | }) 128 | case routercommon.Domain_Full: 129 | domains = append(domains, geosite.Item{ 130 | Type: geosite.RuleTypeDomain, 131 | Value: domain.Value, 132 | }) 133 | } 134 | } 135 | domainMap[code] = common.Uniq(domains) 136 | for attribute, attributeEntries := range attributes { 137 | attributeDomains := make([]geosite.Item, 0, len(attributeEntries)*2) 138 | for _, domain := range attributeEntries { 139 | switch domain.Type { 140 | case routercommon.Domain_Plain: 141 | attributeDomains = append(attributeDomains, geosite.Item{ 142 | Type: geosite.RuleTypeDomainKeyword, 143 | Value: domain.Value, 144 | }) 145 | case routercommon.Domain_Regex: 146 | attributeDomains = append(attributeDomains, geosite.Item{ 147 | Type: geosite.RuleTypeDomainRegex, 148 | Value: domain.Value, 149 | }) 150 | case routercommon.Domain_RootDomain: 151 | if strings.Contains(domain.Value, ".") { 152 | attributeDomains = append(attributeDomains, geosite.Item{ 153 | Type: geosite.RuleTypeDomain, 154 | Value: domain.Value, 155 | }) 156 | } 157 | attributeDomains = append(attributeDomains, geosite.Item{ 158 | Type: geosite.RuleTypeDomainSuffix, 159 | Value: "." + domain.Value, 160 | }) 161 | case routercommon.Domain_Full: 162 | attributeDomains = append(attributeDomains, geosite.Item{ 163 | Type: geosite.RuleTypeDomain, 164 | Value: domain.Value, 165 | }) 166 | } 167 | } 168 | domainMap[code+"@"+attribute] = common.Uniq(attributeDomains) 169 | } 170 | } 171 | return domainMap, nil 172 | } 173 | 174 | func getData() ([]byte, error) { 175 | data, err := os.ReadFile("data/iran.dat") 176 | if err != nil { 177 | return nil, err 178 | } 179 | return data, nil 180 | } 181 | 182 | func generate(output string, ruleSetOutput string) error { 183 | outputFile, err := os.Create(output) 184 | if err != nil { 185 | return err 186 | } 187 | defer outputFile.Close() 188 | vData, err := getData() 189 | if err != nil { 190 | return err 191 | } 192 | domainMap, err := parse(vData) 193 | if err != nil { 194 | return err 195 | } 196 | filterTags(domainMap) 197 | outputPath, _ := filepath.Abs(output) 198 | os.Stderr.WriteString("write " + outputPath + "\n") 199 | err = geosite.Write(outputFile, domainMap) 200 | if err != nil { 201 | return err 202 | } 203 | os.RemoveAll(ruleSetOutput) 204 | err = os.MkdirAll(ruleSetOutput, 0o755) 205 | if err != nil { 206 | return err 207 | } 208 | 209 | for code, domains := range domainMap { 210 | var headlessRule option.DefaultHeadlessRule 211 | defaultRule := geosite.Compile(domains) 212 | headlessRule.Domain = defaultRule.Domain 213 | headlessRule.DomainSuffix = defaultRule.DomainSuffix 214 | headlessRule.DomainKeyword = defaultRule.DomainKeyword 215 | headlessRule.DomainRegex = defaultRule.DomainRegex 216 | var plainRuleSet option.PlainRuleSet 217 | plainRuleSet.Rules = []option.HeadlessRule{ 218 | { 219 | Type: C.RuleTypeDefault, 220 | DefaultOptions: headlessRule, 221 | }, 222 | } 223 | srsPath, _ := filepath.Abs(filepath.Join(ruleSetOutput, "geosite-"+code+".srs")) 224 | //os.Stderr.WriteString("write " + srsPath + "\n") 225 | outputRuleSet, err := os.Create(srsPath) 226 | if err != nil { 227 | return err 228 | } 229 | err = srs.Write(outputRuleSet, plainRuleSet) 230 | if err != nil { 231 | outputRuleSet.Close() 232 | return err 233 | } 234 | outputRuleSet.Close() 235 | } 236 | 237 | return nil 238 | } 239 | 240 | func main() { 241 | err := generate("iran-geosite.db", "rule-set") 242 | if err != nil { 243 | log.Fatal(err) 244 | } 245 | } 246 | --------------------------------------------------------------------------------