diff --git a/configuration.md b/configuration.md index a6044823341..2a6d6ea440a 100644 --- a/configuration.md +++ b/configuration.md @@ -728,6 +728,7 @@ - **outputDir**:(可选)输出目录 - **outputExtension**:(可选)输出文件的扩展名 - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` ```jsonc @@ -774,6 +775,18 @@ } ``` +```jsonc +{ + "type": "clashRuleSet", + "action": "output", + "args": { + "outputDir": "./clash/ipcidr", // 输出文件到目录 ./clash/ipcidr + "outputExtension": ".yaml", // 输出文件的扩展名为 .yaml + "excludedList": ["cn", "us", "jp"] // 不输出名为 cn、us、jp 这三个类别的 IPv4 和 IPv6 地址 + } +} +``` + ### **clashRuleSetClassical** - **type**:(必须)输入格式的名称 @@ -782,6 +795,7 @@ - **outputDir**:(可选)输出目录 - **outputExtension**:(可选)输出文件的扩展名 - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` ```jsonc @@ -828,6 +842,18 @@ } ``` +```jsonc +{ + "type": "clashRuleSetClassical", + "action": "output", + "args": { + "outputDir": "./clash/classical", // 输出文件到目录 ./clash/classical + "outputExtension": ".yaml", // 输出文件的扩展名为 .yaml + "excludedList": ["cn", "us", "jp"] // 不输出名为 cn、us、jp 这三个类别的 IPv4 和 IPv6 地址 + } +} +``` + ### **lookup** - **type**:(必须)输入格式的名称 @@ -868,11 +894,14 @@ - **outputDir**:(可选)输出目录 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **overwriteList**:(可选,数组)指定最后写入的类别(原因见👇) -> 由于 MaxMind mmdb 文件格式的限制,当不同列表的 IP 或 CIDR 数据有交集或重复项时,后写入的列表的 IP 或 CIDR 数据会覆盖(overwrite)之前已写入的列表的数据。譬如,IP 1.1.1.1 同属于列表 `AU` 和列表 `Cloudflare`。如果 `Cloudflare` 在 `AU` 之后写入,则 IP `1.1.1.1` 归属于列表 `Cloudflare`。 +> 由于 MaxMind mmdb 文件格式的限制,当不同列表的 IP 或 CIDR 数据有交集或重复项时,后写入的列表的 IP 或 CIDR 数据会覆盖(overwrite)之前已写入的列表的数据。譬如,IP `1.1.1.1` 同属于列表 `AU` 和列表 `Cloudflare`。如果 `Cloudflare` 在 `AU` 之后写入,则 IP `1.1.1.1` 最终归属于列表 `Cloudflare`。 > > 为了确保某些指定的列表、被修改的列表一定囊括属于它的所有 IP 或 CIDR 数据,可在 output 输出格式为 `maxmindMMDB` 的配置中增加选项 `overwriteList`,该选项中指定的列表会在最后逐一写入,列表中最后一项优先级最高。若已设置选项 `wantedList`,则无需设置 `overwriteList`。`wantedList` 中指定的列表会在最后逐一写入,列表中最后一项优先级最高。 +> +> `wantedList`、`overwriteList`、`excludedList` 三者中,`excludedList` 优先级最高。即:若设置了选项 `excludedList`,最终不会输出存在于 `excludedList` 中的列表。 ```jsonc // 默认输出目录 ./output/maxmind @@ -894,13 +923,25 @@ } ``` +```jsonc +{ + "type": "maxmindMMDB", + "action": "output", + "args": { + "outputDir": "./output", // 输出文件到 output 目录 + "outputName": "Country-without-cn-private.mmdb", // 输出文件名为 Country-without-cn-private.mmdb + "excludedList": ["cn", "private"] // 不输出 cn、private 类别 + } +} +``` + ```jsonc { "type": "maxmindMMDB", "action": "output", "args": { "outputName": "Country.mmdb", // 输出文件名为 Country.mmdb - "overwriteList": ["cn", "google"] // 确保 cn、google 类别后写入,且 google 最后写入 + "overwriteList": ["cn", "google"] // 确保 cn、google 类别最后写入,且 google 比 cn 后写入 } } ``` @@ -911,12 +952,36 @@ "action": "output", "args": { "outputName": "Country.mmdb", // 输出文件名为 Country.mmdb - "overwriteList": ["cn", "google"], // 确保 cn、google 类别后写入,且 google 最后写入 + "overwriteList": ["cn", "google"], // 确保 cn、google 类别最后写入,且 google 比 cn 后写入 "onlyIPType": "ipv4" // 只输出 cn、private 类别的 IPv4 地址 } } ``` +```jsonc +{ + "type": "maxmindMMDB", + "action": "output", + "args": { + "outputName": "Country.mmdb", // 输出文件名为 Country.mmdb + "excludedList": ["private"], // 最终不输出 private 类别 + "wantedList": ["private" ,"au", "cloudflare"] // 只输出 au、cloudflare 类别,并确保 cloudflare 比 au 后写入。但由于 private 存在于 excludedList 中,最终不输出 private 类别 + } +} +``` + +```jsonc +{ + "type": "maxmindMMDB", + "action": "output", + "args": { + "outputName": "Country.mmdb", // 输出文件名为 Country.mmdb + "excludedList": ["private"], // 最终不输出 private 类别 + "overwriteList": ["private" ,"cn", "google"] // 确保 cn、google 类别最后写入,且 google 比 cn 后写入。但由于 private 存在于 excludedList 中,最终不输出 private 类别 + } +} +``` + ### **mihomoMRS** - **type**:(必须)输入格式的名称 @@ -924,6 +989,7 @@ - **args**:(可选) - **outputDir**:(可选)输出目录 - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` ```jsonc @@ -945,6 +1011,17 @@ } ``` +```jsonc +{ + "type": "mihomoMRS", + "action": "output", + "args": { + "outputDir": "./output", // 输出文件到 output 目录 + "excludedList": ["cn", "private"] // 不输出 cn、private 类别 + } +} +``` + ```jsonc { "type": "mihomoMRS", @@ -962,6 +1039,7 @@ - **args**:(可选) - **outputDir**:(可选)输出目录 - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` ```jsonc @@ -983,6 +1061,17 @@ } ``` +```jsonc +{ + "type": "singboxSRS", + "action": "output", + "args": { + "outputDir": "./output", // 输出文件到 output 目录 + "excludedList": ["cn", "private"] // 不输出 cn、private 类别 + } +} +``` + ```jsonc { "type": "singboxSRS", @@ -999,6 +1088,7 @@ - **action**:(必须)操作类型,值必须为 `output` - **args**:(可选) - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` ```jsonc @@ -1018,6 +1108,16 @@ } ``` +```jsonc +{ + "type": "stdout", + "action": "output", + "args": { + "excludedList": ["cn", "private"] // 不输出 cn、private 类别到 standard output + } +} +``` + ```jsonc { "type": "stdout", @@ -1036,6 +1136,7 @@ - **outputDir**:(可选)输出目录 - **outputExtension**:(可选)输出的文件的扩展名 - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` ```jsonc @@ -1069,6 +1170,18 @@ } ``` +```jsonc +{ + "type": "surgeRuleSet", + "action": "output", + "args": { + "outputDir": "./surge", // 输出文件到目录 ./surge + "outputExtension": ".conf", // 输出文件的扩展名为 .conf + "excludedList": ["cn", "us", "jp"] // 不输出名为 cn、us、jp 这三个类别的 IPv4 和 IPv6 地址 + } +} +``` + ```jsonc { "type": "surgeRuleSet", @@ -1090,6 +1203,7 @@ - **outputDir**:(可选)输出目录 - **outputExtension**:(可选)输出的文件的扩展名 - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` - **addPrefixInLine**:(可选)给输出的每一行添加的字符串前缀 - **addSuffixInLine**:(可选)给输出的每一行添加的字符串后缀 @@ -1134,6 +1248,19 @@ } ``` +```jsonc +{ + "type": "text", + "action": "output", + "args": { + "outputDir": "./text", // 输出文件到目录 ./text + "outputExtension": ".conf", // 输出文件的扩展名为 .conf + "excludedList": ["cn", "us", "jp"], // 不输出名为 cn、us、jp 这三个类别的 IPv4 和 IPv6 地址 + "addPrefixInLine": "HOST," + } +} +``` + ```jsonc { "type": "text", @@ -1156,6 +1283,7 @@ - **outputName**:(可选)输出的文件名 - **outputDir**:(可选)输出目录 - **wantedList**:(可选,数组)指定需要输出的类别 + - **excludedList**:(可选,数组)指定不需要输出的类别 - **onlyIPType**:(可选)输出的 IP 地址类型,值为 `ipv4` 或 `ipv6` - **oneFilePerList**:(可选)每个类别输出为一个单独的文件,值为 `true` 或 `false`(默认值) @@ -1189,6 +1317,18 @@ } ``` +```jsonc +{ + "type": "v2rayGeoIPDat", + "action": "output", + "args": { + "outputDir": "./output", // 输出文件到 output 目录 + "outputName": "geoip-without-cn-private.dat", // 输出文件名为 geoip-without-cn-private.dat + "excludedList": ["cn", "private"] // 不输出 cn、private 类别 + } +} +``` + ```jsonc { "type": "v2rayGeoIPDat", diff --git a/plugin/maxmind/mmdb_out.go b/plugin/maxmind/mmdb_out.go index 964971d7558..3b8ba20c2b5 100644 --- a/plugin/maxmind/mmdb_out.go +++ b/plugin/maxmind/mmdb_out.go @@ -2,7 +2,6 @@ package maxmind import ( "encoding/json" - "fmt" "log" "net" "os" @@ -40,6 +39,7 @@ func newMMDBOut(action lib.Action, data json.RawMessage) (lib.OutputConverter, e OutputDir string `json:"outputDir"` Want []string `json:"wantedList"` Overwrite []string `json:"overwriteList"` + Exclude []string `json:"excludedList"` OnlyIPType lib.IPType `json:"onlyIPType"` } @@ -65,6 +65,7 @@ func newMMDBOut(action lib.Action, data json.RawMessage) (lib.OutputConverter, e OutputDir: tmp.OutputDir, Want: tmp.Want, Overwrite: tmp.Overwrite, + Exclude: tmp.Exclude, OnlyIPType: tmp.OnlyIPType, }, nil } @@ -77,6 +78,7 @@ type mmdbOut struct { OutputDir string Want []string Overwrite []string + Exclude []string OnlyIPType lib.IPType } @@ -106,30 +108,28 @@ func (m *mmdbOut) Output(container lib.Container) error { } updated := false - for _, name := range m.getEntryNameListInOrder(container) { + for _, name := range m.filterAndSortList(container) { entry, found := container.GetEntry(name) if !found { - log.Printf("❌ entry %s not found", name) + log.Printf("❌ entry %s not found\n", name) continue } + if err := m.marshalData(writer, entry); err != nil { return err } + updated = true } if updated { - if err := m.writeFile(m.OutputName, writer); err != nil { - return err - } - } else { - return fmt.Errorf("❌ [type %s | action %s] failed to write file", m.Type, m.Action) + return m.writeFile(m.OutputName, writer) } return nil } -func (m *mmdbOut) getEntryNameListInOrder(container lib.Container) []string { +func (m *mmdbOut) filterAndSortList(container lib.Container) []string { /* Note: The IPs and/or CIDRs of the latter list will overwrite those of the former one when duplicated data found due to MaxMind mmdb file format constraint. @@ -140,9 +140,16 @@ func (m *mmdbOut) getEntryNameListInOrder(container lib.Container) []string { The order of names in wantedList has a higher priority than which of the overwriteList. */ + excludeMap := make(map[string]bool) + for _, exclude := range m.Exclude { + if exclude = strings.ToUpper(strings.TrimSpace(exclude)); exclude != "" { + excludeMap[exclude] = true + } + } + wantList := make([]string, 0, len(m.Want)) for _, want := range m.Want { - if want = strings.ToUpper(strings.TrimSpace(want)); want != "" { + if want = strings.ToUpper(strings.TrimSpace(want)); want != "" && !excludeMap[want] { wantList = append(wantList, want) } } @@ -154,7 +161,7 @@ func (m *mmdbOut) getEntryNameListInOrder(container lib.Container) []string { overwriteList := make([]string, 0, len(m.Overwrite)) overwriteMap := make(map[string]bool) for _, overwrite := range m.Overwrite { - if overwrite = strings.ToUpper(strings.TrimSpace(overwrite)); overwrite != "" { + if overwrite = strings.ToUpper(strings.TrimSpace(overwrite)); overwrite != "" && !excludeMap[overwrite] { overwriteList = append(overwriteList, overwrite) overwriteMap[overwrite] = true } @@ -163,8 +170,7 @@ func (m *mmdbOut) getEntryNameListInOrder(container lib.Container) []string { list := make([]string, 0, 300) for entry := range container.Loop() { name := entry.GetName() - _, found := overwriteMap[name] - if found { + if excludeMap[name] || overwriteMap[name] { continue } list = append(list, name) diff --git a/plugin/mihomo/mrs_out.go b/plugin/mihomo/mrs_out.go index fe9480dab32..28b922b3d4e 100644 --- a/plugin/mihomo/mrs_out.go +++ b/plugin/mihomo/mrs_out.go @@ -38,6 +38,7 @@ func newMRSOut(action lib.Action, data json.RawMessage) (lib.OutputConverter, er var tmp struct { OutputDir string `json:"outputDir"` Want []string `json:"wantedList"` + Exclude []string `json:"excludedList"` OnlyIPType lib.IPType `json:"onlyIPType"` } @@ -51,20 +52,13 @@ func newMRSOut(action lib.Action, data json.RawMessage) (lib.OutputConverter, er tmp.OutputDir = defaultOutputDir } - // Filter want list - wantList := make([]string, 0, len(tmp.Want)) - for _, want := range tmp.Want { - if want = strings.ToUpper(strings.TrimSpace(want)); want != "" { - wantList = append(wantList, want) - } - } - return &mrsOut{ Type: typeMRSOut, Action: action, Description: descMRSOut, OutputDir: tmp.OutputDir, - Want: wantList, + Want: tmp.Want, + Exclude: tmp.Exclude, OnlyIPType: tmp.OnlyIPType, }, nil } @@ -75,6 +69,7 @@ type mrsOut struct { Description string OutputDir string Want []string + Exclude []string OnlyIPType lib.IPType } @@ -91,45 +86,55 @@ func (m *mrsOut) GetDescription() string { } func (m *mrsOut) Output(container lib.Container) error { - switch len(m.Want) { - case 0: - list := make([]string, 0, 300) - for entry := range container.Loop() { - list = append(list, entry.GetName()) + for _, name := range m.filterAndSortList(container) { + entry, found := container.GetEntry(name) + if !found { + log.Printf("❌ entry %s not found\n", name) + continue } - // Sort the list - slices.Sort(list) - - for _, name := range list { - entry, found := container.GetEntry(name) - if !found { - log.Printf("❌ entry %s not found", name) - continue - } - if err := m.generate(entry); err != nil { - return err - } + if err := m.generate(entry); err != nil { + return err } + } - default: + return nil +} + +func (m *mrsOut) filterAndSortList(container lib.Container) []string { + excludeMap := make(map[string]bool) + for _, exclude := range m.Exclude { + if exclude = strings.ToUpper(strings.TrimSpace(exclude)); exclude != "" { + excludeMap[exclude] = true + } + } + + wantList := make([]string, 0, len(m.Want)) + for _, want := range m.Want { + if want = strings.ToUpper(strings.TrimSpace(want)); want != "" && !excludeMap[want] { + wantList = append(wantList, want) + } + } + + if len(wantList) > 0 { // Sort the list - slices.Sort(m.Want) - - for _, name := range m.Want { - entry, found := container.GetEntry(name) - if !found { - log.Printf("❌ entry %s not found", name) - continue - } - - if err := m.generate(entry); err != nil { - return err - } + slices.Sort(wantList) + return wantList + } + + list := make([]string, 0, 300) + for entry := range container.Loop() { + name := entry.GetName() + if excludeMap[name] { + continue } + list = append(list, name) } - return nil + // Sort the list + slices.Sort(list) + + return list } func (m *mrsOut) generate(entry *lib.Entry) error { diff --git a/plugin/plaintext/common_out.go b/plugin/plaintext/common_out.go index b8f1bb55977..117d48c72b5 100644 --- a/plugin/plaintext/common_out.go +++ b/plugin/plaintext/common_out.go @@ -7,7 +7,6 @@ import ( "net" "os" "path/filepath" - "strings" "github.com/Loyalsoldier/geoip/lib" ) @@ -26,6 +25,7 @@ type textOut struct { OutputDir string OutputExt string Want []string + Exclude []string OnlyIPType lib.IPType AddPrefixInLine string @@ -37,6 +37,7 @@ func newTextOut(iType string, action lib.Action, data json.RawMessage) (lib.Outp OutputDir string `json:"outputDir"` OutputExt string `json:"outputExtension"` Want []string `json:"wantedList"` + Exclude []string `json:"excludedList"` OnlyIPType lib.IPType `json:"onlyIPType"` AddPrefixInLine string `json:"addPrefixInLine"` @@ -66,21 +67,14 @@ func newTextOut(iType string, action lib.Action, data json.RawMessage) (lib.Outp tmp.OutputExt = ".txt" } - // Filter want list - wantList := make([]string, 0, len(tmp.Want)) - for _, want := range tmp.Want { - if want = strings.ToUpper(strings.TrimSpace(want)); want != "" { - wantList = append(wantList, want) - } - } - return &textOut{ Type: iType, Action: action, Description: descTextOut, OutputDir: tmp.OutputDir, OutputExt: tmp.OutputExt, - Want: wantList, + Want: tmp.Want, + Exclude: tmp.Exclude, OnlyIPType: tmp.OnlyIPType, AddPrefixInLine: tmp.AddPrefixInLine, diff --git a/plugin/plaintext/text_out.go b/plugin/plaintext/text_out.go index dce92ce888b..cc51da95c56 100644 --- a/plugin/plaintext/text_out.go +++ b/plugin/plaintext/text_out.go @@ -36,52 +36,59 @@ func (t *textOut) GetDescription() string { } func (t *textOut) Output(container lib.Container) error { - switch len(t.Want) { - case 0: - list := make([]string, 0, 300) - for entry := range container.Loop() { - list = append(list, entry.GetName()) + for _, name := range t.filterAndSortList(container) { + entry, found := container.GetEntry(name) + if !found { + log.Printf("❌ entry %s not found\n", name) + continue } - // Sort the list - slices.Sort(list) - - for _, name := range list { - entry, found := container.GetEntry(name) - if !found { - log.Printf("❌ entry %s not found", name) - continue - } - data, err := t.marshalBytes(entry) - if err != nil { - return err - } - filename := strings.ToLower(entry.GetName()) + t.OutputExt - if err := t.writeFile(filename, data); err != nil { - return err - } + data, err := t.marshalBytes(entry) + if err != nil { + return err } - default: - // Sort the list - slices.Sort(t.Want) - - for _, name := range t.Want { - entry, found := container.GetEntry(name) - if !found { - log.Printf("❌ entry %s not found", name) - continue - } - data, err := t.marshalBytes(entry) - if err != nil { - return err - } - filename := strings.ToLower(entry.GetName()) + t.OutputExt - if err := t.writeFile(filename, data); err != nil { - return err - } + filename := strings.ToLower(entry.GetName()) + t.OutputExt + if err := t.writeFile(filename, data); err != nil { + return err } } return nil } + +func (t *textOut) filterAndSortList(container lib.Container) []string { + excludeMap := make(map[string]bool) + for _, exclude := range t.Exclude { + if exclude = strings.ToUpper(strings.TrimSpace(exclude)); exclude != "" { + excludeMap[exclude] = true + } + } + + wantList := make([]string, 0, len(t.Want)) + for _, want := range t.Want { + if want = strings.ToUpper(strings.TrimSpace(want)); want != "" && !excludeMap[want] { + wantList = append(wantList, want) + } + } + + if len(wantList) > 0 { + // Sort the list + slices.Sort(wantList) + return wantList + } + + list := make([]string, 0, 300) + for entry := range container.Loop() { + name := entry.GetName() + if excludeMap[name] { + continue + } + list = append(list, name) + } + + // Sort the list + slices.Sort(list) + + return list +} diff --git a/plugin/singbox/srs_out.go b/plugin/singbox/srs_out.go index d2950ee559f..4c4de1f762d 100644 --- a/plugin/singbox/srs_out.go +++ b/plugin/singbox/srs_out.go @@ -37,6 +37,7 @@ func newSRSOut(action lib.Action, data json.RawMessage) (lib.OutputConverter, er var tmp struct { OutputDir string `json:"outputDir"` Want []string `json:"wantedList"` + Exclude []string `json:"excludedList"` OnlyIPType lib.IPType `json:"onlyIPType"` } @@ -50,20 +51,13 @@ func newSRSOut(action lib.Action, data json.RawMessage) (lib.OutputConverter, er tmp.OutputDir = defaultOutputDir } - // Filter want list - wantList := make([]string, 0, len(tmp.Want)) - for _, want := range tmp.Want { - if want = strings.ToUpper(strings.TrimSpace(want)); want != "" { - wantList = append(wantList, want) - } - } - return &srsOut{ Type: typeSRSOut, Action: action, Description: descSRSOut, OutputDir: tmp.OutputDir, - Want: wantList, + Want: tmp.Want, + Exclude: tmp.Exclude, OnlyIPType: tmp.OnlyIPType, }, nil } @@ -74,6 +68,7 @@ type srsOut struct { Description string OutputDir string Want []string + Exclude []string OnlyIPType lib.IPType } @@ -90,49 +85,59 @@ func (s *srsOut) GetDescription() string { } func (s *srsOut) Output(container lib.Container) error { - switch len(s.Want) { - case 0: - list := make([]string, 0, 300) - for entry := range container.Loop() { - list = append(list, entry.GetName()) + for _, name := range s.filterAndSortList(container) { + entry, found := container.GetEntry(name) + if !found { + log.Printf("❌ entry %s not found\n", name) + continue } - // Sort the list - slices.Sort(list) - - for _, name := range list { - entry, found := container.GetEntry(name) - if !found { - log.Printf("❌ entry %s not found", name) - continue - } - if err := s.run(entry); err != nil { - return err - } + if err := s.generate(entry); err != nil { + return err } + } - default: + return nil +} + +func (s *srsOut) filterAndSortList(container lib.Container) []string { + excludeMap := make(map[string]bool) + for _, exclude := range s.Exclude { + if exclude = strings.ToUpper(strings.TrimSpace(exclude)); exclude != "" { + excludeMap[exclude] = true + } + } + + wantList := make([]string, 0, len(s.Want)) + for _, want := range s.Want { + if want = strings.ToUpper(strings.TrimSpace(want)); want != "" && !excludeMap[want] { + wantList = append(wantList, want) + } + } + + if len(wantList) > 0 { // Sort the list - slices.Sort(s.Want) - - for _, name := range s.Want { - entry, found := container.GetEntry(name) - if !found { - log.Printf("❌ entry %s not found", name) - continue - } - - if err := s.run(entry); err != nil { - return err - } + slices.Sort(wantList) + return wantList + } + + list := make([]string, 0, 300) + for entry := range container.Loop() { + name := entry.GetName() + if excludeMap[name] { + continue } + list = append(list, name) } - return nil + // Sort the list + slices.Sort(list) + + return list } -func (s *srsOut) run(entry *lib.Entry) error { - ruleset, err := s.generateRuleSet(entry) +func (s *srsOut) generate(entry *lib.Entry) error { + ruleset, err := s.marshalRuleSet(entry) if err != nil { return err } @@ -145,7 +150,7 @@ func (s *srsOut) run(entry *lib.Entry) error { return nil } -func (s *srsOut) generateRuleSet(entry *lib.Entry) (*option.PlainRuleSet, error) { +func (s *srsOut) marshalRuleSet(entry *lib.Entry) (*option.PlainRuleSet, error) { var entryCidr []string var err error switch s.OnlyIPType { diff --git a/plugin/special/stdout.go b/plugin/special/stdout.go index b0f9320a232..8c57cb58670 100644 --- a/plugin/special/stdout.go +++ b/plugin/special/stdout.go @@ -5,6 +5,7 @@ import ( "errors" "io" "os" + "slices" "strings" "github.com/Loyalsoldier/geoip/lib" @@ -27,6 +28,7 @@ func init() { func newStdout(action lib.Action, data json.RawMessage) (lib.OutputConverter, error) { var tmp struct { Want []string `json:"wantedList"` + Exclude []string `json:"excludedList"` OnlyIPType lib.IPType `json:"onlyIPType"` } @@ -36,19 +38,12 @@ func newStdout(action lib.Action, data json.RawMessage) (lib.OutputConverter, er } } - // Filter want list - wantList := make(map[string]bool) - for _, want := range tmp.Want { - if want = strings.ToUpper(strings.TrimSpace(want)); want != "" { - wantList[want] = true - } - } - return &stdout{ Type: typeStdout, Action: action, Description: descStdout, - Want: wantList, + Want: tmp.Want, + Exclude: tmp.Exclude, OnlyIPType: tmp.OnlyIPType, }, nil } @@ -57,7 +52,8 @@ type stdout struct { Type string Action lib.Action Description string - Want map[string]bool + Want []string + Exclude []string OnlyIPType lib.IPType } @@ -74,8 +70,9 @@ func (s *stdout) GetDescription() string { } func (s *stdout) Output(container lib.Container) error { - for entry := range container.Loop() { - if len(s.Want) > 0 && !s.Want[entry.GetName()] { + for _, name := range s.filterAndSortList(container) { + entry, found := container.GetEntry(name) + if !found { continue } @@ -83,6 +80,7 @@ func (s *stdout) Output(container lib.Container) error { if err != nil { continue } + for _, cidr := range cidrList { io.WriteString(os.Stdout, cidr+"\n") } @@ -91,6 +89,42 @@ func (s *stdout) Output(container lib.Container) error { return nil } +func (s *stdout) filterAndSortList(container lib.Container) []string { + excludeMap := make(map[string]bool) + for _, exclude := range s.Exclude { + if exclude = strings.ToUpper(strings.TrimSpace(exclude)); exclude != "" { + excludeMap[exclude] = true + } + } + + wantList := make([]string, 0, len(s.Want)) + for _, want := range s.Want { + if want = strings.ToUpper(strings.TrimSpace(want)); want != "" && !excludeMap[want] { + wantList = append(wantList, want) + } + } + + if len(wantList) > 0 { + // Sort the list + slices.Sort(wantList) + return wantList + } + + list := make([]string, 0, 300) + for entry := range container.Loop() { + name := entry.GetName() + if excludeMap[name] { + continue + } + list = append(list, name) + } + + // Sort the list + slices.Sort(list) + + return list +} + func (s *stdout) generateCIDRList(entry *lib.Entry) ([]string, error) { var entryList []string var err error diff --git a/plugin/v2ray/dat_out.go b/plugin/v2ray/dat_out.go index 1e64ede3990..3b12027740c 100644 --- a/plugin/v2ray/dat_out.go +++ b/plugin/v2ray/dat_out.go @@ -7,6 +7,7 @@ import ( "net/netip" "os" "path/filepath" + "slices" "sort" "strings" @@ -38,6 +39,7 @@ func newGeoIPDat(action lib.Action, data json.RawMessage) (lib.OutputConverter, OutputName string `json:"outputName"` OutputDir string `json:"outputDir"` Want []string `json:"wantedList"` + Exclude []string `json:"excludedList"` OneFilePerList bool `json:"oneFilePerList"` OnlyIPType lib.IPType `json:"onlyIPType"` } @@ -56,21 +58,14 @@ func newGeoIPDat(action lib.Action, data json.RawMessage) (lib.OutputConverter, tmp.OutputDir = defaultOutputDir } - // Filter want list - wantList := make([]string, 0, len(tmp.Want)) - for _, want := range tmp.Want { - if want = strings.ToUpper(strings.TrimSpace(want)); want != "" { - wantList = append(wantList, want) - } - } - return &geoIPDatOut{ Type: typeGeoIPdatOut, Action: action, Description: descGeoIPdatOut, OutputName: tmp.OutputName, OutputDir: tmp.OutputDir, - Want: wantList, + Want: tmp.Want, + Exclude: tmp.Exclude, OneFilePerList: tmp.OneFilePerList, OnlyIPType: tmp.OnlyIPType, }, nil @@ -83,6 +78,7 @@ type geoIPDatOut struct { OutputName string OutputDir string Want []string + Exclude []string OneFilePerList bool OnlyIPType lib.IPType } @@ -104,70 +100,32 @@ func (g *geoIPDatOut) Output(container lib.Container) error { geoIPList.Entry = make([]*GeoIP, 0, 300) updated := false - switch len(g.Want) { - case 0: - list := make([]string, 0, 300) - for entry := range container.Loop() { - list = append(list, entry.GetName()) + for _, name := range g.filterAndSortList(container) { + entry, found := container.GetEntry(name) + if !found { + log.Printf("❌ entry %s not found\n", name) + continue } - // Sort the list - sort.Strings(list) + geoIP, err := g.generateGeoIP(entry) + if err != nil { + return err + } + geoIPList.Entry = append(geoIPList.Entry, geoIP) + updated = true - for _, name := range list { - entry, found := container.GetEntry(name) - if !found { - log.Printf("❌ entry %s not found", name) - continue - } - geoIP, err := g.generateGeoIP(entry) + if g.OneFilePerList { + geoIPBytes, err := proto.Marshal(geoIPList) if err != nil { return err } - geoIPList.Entry = append(geoIPList.Entry, geoIP) - updated = true - - if g.OneFilePerList { - geoIPBytes, err := proto.Marshal(geoIPList) - if err != nil { - return err - } - filename := strings.ToLower(entry.GetName()) + ".dat" - if err := g.writeFile(filename, geoIPBytes); err != nil { - return err - } - geoIPList.Entry = nil - } - } - - default: - // Sort the list - sort.Strings(g.Want) - for _, name := range g.Want { - entry, found := container.GetEntry(name) - if !found { - log.Printf("❌ entry %s not found", name) - continue - } - geoIP, err := g.generateGeoIP(entry) - if err != nil { + filename := strings.ToLower(entry.GetName()) + ".dat" + if err := g.writeFile(filename, geoIPBytes); err != nil { return err } - geoIPList.Entry = append(geoIPList.Entry, geoIP) - updated = true - - if g.OneFilePerList { - geoIPBytes, err := proto.Marshal(geoIPList) - if err != nil { - return err - } - filename := strings.ToLower(entry.GetName()) + ".dat" - if err := g.writeFile(filename, geoIPBytes); err != nil { - return err - } - geoIPList.Entry = nil - } + + geoIPList.Entry = nil } } @@ -187,6 +145,42 @@ func (g *geoIPDatOut) Output(container lib.Container) error { return nil } +func (g *geoIPDatOut) filterAndSortList(container lib.Container) []string { + excludeMap := make(map[string]bool) + for _, exclude := range g.Exclude { + if exclude = strings.ToUpper(strings.TrimSpace(exclude)); exclude != "" { + excludeMap[exclude] = true + } + } + + wantList := make([]string, 0, len(g.Want)) + for _, want := range g.Want { + if want = strings.ToUpper(strings.TrimSpace(want)); want != "" && !excludeMap[want] { + wantList = append(wantList, want) + } + } + + if len(wantList) > 0 { + // Sort the list + slices.Sort(wantList) + return wantList + } + + list := make([]string, 0, 300) + for entry := range container.Loop() { + name := entry.GetName() + if excludeMap[name] { + continue + } + list = append(list, name) + } + + // Sort the list + slices.Sort(list) + + return list +} + func (g *geoIPDatOut) generateGeoIP(entry *lib.Entry) (*GeoIP, error) { var entryCidr []netip.Prefix var err error