From f2562a838d1405e65c78e1680ed1190539169c84 Mon Sep 17 00:00:00 2001 From: sinspred Date: Wed, 24 Jul 2024 02:17:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ip=20asn=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E5=8F=8Aip=E7=B1=BB=E5=9E=8B=E5=88=A4?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 6 +- go.sum | 38 + main.go | 762 +++++++++++------- task/getASN.go | 195 +++++ task/unZip2txtFile.go | 3 +- tool/QueryDownload.go | 145 ++++ mergeCSV.go => tool/mergeCSV.go | 0 tool/reParseResultTxtFile.go | 480 +++++++++++ tcpTest.go => tool/tcpTest.go | 41 +- .../tcpTestWithProxy.go | 0 update.go | 21 +- 11 files changed, 1365 insertions(+), 326 deletions(-) create mode 100644 task/getASN.go create mode 100644 tool/QueryDownload.go rename mergeCSV.go => tool/mergeCSV.go (100%) create mode 100644 tool/reParseResultTxtFile.go rename tcpTest.go => tool/tcpTest.go (71%) rename tcpTestWithProxy.go => tool/tcpTestWithProxy.go (100%) diff --git a/go.mod b/go.mod index f2532a8..80ed267 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,14 @@ module github.com/sinspired/CloudflareBestIP go 1.22.4 require ( + github.com/PuerkitoBio/goquery v1.9.2 github.com/VividCortex/ewma v1.2.0 github.com/mattn/go-ieproxy v0.0.12 golang.org/x/sys v0.22.0 golang.org/x/text v0.16.0 ) -require golang.org/x/net v0.25.0 // indirect +require ( + github.com/andybalholm/cascadia v1.3.2 // indirect + golang.org/x/net v0.25.0 // indirect +) diff --git a/go.sum b/go.sum index c317999..c2986c5 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,48 @@ +github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE= +github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk= github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= +github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= github.com/mattn/go-ieproxy v0.0.12 h1:OZkUFJC3ESNZPQ+6LzC3VJIFSnreeFLQyqvBWtvfL2M= github.com/mattn/go-ieproxy v0.0.12/go.mod h1:Vn+N61199DAnVeTgaF8eoB9PvLO8P3OBnG95ENh7B7c= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go index 0a7ee0d..7216938 100644 --- a/main.go +++ b/main.go @@ -39,38 +39,42 @@ var ( maxThreads = flag.Int("max", 200, "并发请求最大协程数") // 最大协程数 speedTestThreads = flag.Int("speedtest", 5, "下载测速协程数量,设为0禁用测速") // 下载测速协程数量 speedLimit = flag.Float64("speedlimit", 5, "最低下载速度(MB/s)") // 最低下载速度 - speedTestURL = flag.String("url", "st.notime.icu/200000000", "测速文件地址") // 测速文件地址 + speedTestURL = flag.String("url", "speed.cloudflare.com/200000000", "测速文件地址") // 测速文件地址 // speedTestURL = flag.String("url", "speed.cloudflare.com/__down?bytes=500000000", "测速文件地址") // 测速文件地址 - enableTLS = flag.Bool("tls", true, "是否启用TLS") // TLS是否启用 - multipleNum = flag.Float64("mulnum", 1, "多协程测速造成测速不准,可进行倍数补偿") // speedTest比较大时修改 - tcpLimit = flag.Int("tcplimit", 9999, "TCP最大延迟(ms)") // TCP最大延迟(ms) - httpLimit = flag.Int("httplimit", 9999, "HTTP最大延迟(ms)") // HTTP最大延迟(ms) - countryCodes = flag.String("country", "", "国家代码(US,SG,JP,DE...),以逗号分隔,留空时检测所有") // 国家代码数组 - DownloadipLab = flag.Bool("iplab", false, "为true时检查ip库中的文件并依次下载") // 自动下载一些知名的反代IP列表 - Domain = flag.String("domain", "", "上传地址,默认为空,用Text2KV项目建立的简易文件存储storage.example.com") - Token = flag.String("token", "", "上传地址的token,默认为空") - RequestNum = flag.Int("num", 6, "测速结果数量") // 测速结果数量 - DownloadTestAll = flag.Bool("dlall", false, "为true对有效满足延迟检测的有效ip测速,可能需要很长时间") + enableTLS = flag.Bool("tls", true, "是否启用TLS") // TLS是否启用 + multipleNum = flag.Float64("mulnum", 1, "多协程测速造成测速不准,可进行倍数补偿") // speedTest比较大时修改 + tcpLimit = flag.Int("tcplimit", 2000, "TCP最大延迟(ms)") // TCP最大延迟(ms) + httpLimit = flag.Int("httplimit", 9999, "HTTP最大延迟(ms)") // HTTP最大延迟(ms) + countryCodes = flag.String("country", "", "国家代码(US,SG,JP,DE...),以逗号分隔,留空时检测所有") // 国家代码数组 + ExcludedCounties = flag.String("not", "", "排除的国家代码(US,SG,JP,DE...)") // 排除的国家代码数组 + DownloadipLib = flag.Bool("iplib", false, "为true时检查ip库中的文件并依次下载") // 自动下载一些知名的反代IP列表 + Domain = flag.String("domain", "", "上传地址,默认为空,用Text2KV项目建立的简易文件存储storage.example.com") + Token = flag.String("token", "", "上传地址的token,默认为空") + RequestNum = flag.Int("num", 6, "测速结果数量") // 测速结果数量 + DownloadTestAll = flag.Bool("dlall", false, "为true对有效满足延迟检测的有效ip测速,可能需要很长时间") + IPInfoApiKey = flag.String("api", "", "ip信息查询api,免费申请,api.ipapi.is,如留空则使用免费接口") ) -var ipLabs = map[string]string{ - "txt.zip": "https://zip.baipiao.eu.org/", - "baipiaoge.zip": "https://zip.baipiao.eu.org/", - "ip_ProxyIPDB.txt": "https://ipdb.api.030101.xyz/?type=proxy", - "ip_CFv4IPDB.txt": "https://ipdb.api.030101.xyz/?type=cfv4", +var ipLibs = map[string]string{ + "txt.zip": "https://zip.baipiao.eu.org/", + "baipiaoge.zip": "https://zip.baipiao.eu.org/", + "ip_ProxyIPDB.txt": "https://ipdb.api.030101.xyz/?type=proxy", + "ip_CFv4IPDB.txt": "https://ipdb.api.030101.xyz/?type=cfv4", + "ip_BihaiCFIP.txt": "https://cfip.bihai.cf", + "ip_BihaiCloudCFIP.txt": "https://cloudcfip.bihai.cf", } const ( bufferSize = 1024 - tcpTimeout = 1 * time.Second // 超时时间 - httpTimeout = 2 * time.Second // 超时时间 - maxDuration = 5 * time.Second // 最大延迟 + tcpTimeout = 2 * time.Second // 超时时间 + httpTimeout = 4 * time.Second // 超时时间 + maxDuration = 6 * time.Second // 最大延迟 dlTimeout = 10 * time.Second // 下载超时 ) var ( - // requestURL = "cf.xiu2.xyz/url" // 请求trace URLcf.xiu2.xyz/url - requestURL = "st.notime.icu/cdn-cgi/trace" // 请求trace URL + // requestURL = "st.notime.icu/cdn-cgi/trace" // 请求trace URL,自建的貌似会造成数据中心识别错误 + requestURL = "speed.cloudflare.com/cdn-cgi/trace" // 请求trace URL locationsJsonUrl = "https://speed.cloudflare.com/locations" // location.json下载 URL ) @@ -78,13 +82,15 @@ var ( var ( startTime = time.Now() // 标记开始时间 countries []string // 国家代码数组 + exceludedCountries []string // 排除的国家代码数组 locationMap map[string]location // IP位置数据 totalIPs int // IP总数 countProcessedInt32 int32 // 延迟检测已处理进度计数,使用原子计数 countAlive int // 延迟检测存活ip percentage float64 // 检测进度百分比 totalResultChan []latencyTestResult // 存储延迟测速结果 - countQualified int // 优质ip数量 + countQualified int // 存储合格ip数量 + baseLens int // 命令行输出基准长度 ) // 延迟检测结果结构体 @@ -154,190 +160,6 @@ func clearScreen() { cmd.Run() } -// main 主程序 -func main() { - flag.Parse() // 解析命令行参数 - startTime = time.Now() - - osType := runtime.GOOS - if osType == "linux" { - increaseMaxOpenFiles() - } - - // 下载locations.json - locationsJsonDownload() - - // 网络环境检测,如网络不正常自动退出 - if !autoNetworkDetection() { - return - } - - // 存储国家代码到数组中 - if *countryCodes != "" { - countries = strings.Split(strings.ToUpper(*countryCodes), ",") - } - - // ipLab列表文件下载及时效性检测 - if *DownloadipLab { - checked := make(map[string]bool) - for file, url := range ipLabs { - if url == "https://zip.baipiao.eu.org/" { - if !checked[url] { - fmt.Printf("\033[90m检查 %s\033[0m\n", file) - checkIPLab(url, file) - checked[url] = true - } - } else { - fmt.Printf("\033[90m检查 %s\033[0m\n", file) - checkIPLab(url, file) - } - } - fmt.Printf("\033[32mIP库文件已处于最新状态,请修改\033[0m \033[90m-iplab=false\033[0m \033[32m重新运行程序\033[0m\n") - os.Exit(0) - } else { - lowerFile := strings.ToLower(*File) - var matchedFile string - for key := range ipLabs { - if strings.ToLower(key) == lowerFile { - matchedFile = key - break - } - } - if matchedFile != "" { - *File = matchedFile - checkIPLab(ipLabs[matchedFile], *File) - } else if *File == "ip.txt" { - _, err := os.Stat(*File) - if os.IsNotExist(err) { - fmt.Printf("%s 不存在,切换网络IP库 >>>\n", *File) - *File = "txt.zip" - ipLaburl := "https://zip.baipiao.eu.org/" - checkIPLab(ipLaburl, *File) - } - } else { - _, err := os.Stat(*File) - if os.IsNotExist(err) { - fmt.Printf("%s 文件不存在,请检查输入!\n", *File) - os.Exit(0) - } - } - } - - // 支持直接设置ip参数检测单个ip - if *DirectIP != "" { - *outFile = "result_TestIP.csv" - var DirectIPs []string - DirectIPs = strings.Split(*DirectIP, ",") - delayedDetectionIPs(DirectIPs, *enableTLS, *defaultPort) - pause() - downloadSpeedTest() - os.Exit(0) - } else { - // 设置测速结果文件名 - resetOutFileName(*File) - } - - if strings.HasSuffix(*File, ".zip") { - // 获取压缩包文件名 - ZipedFileName := strings.Split(*File, ".")[0] - caser := cases.Title(language.English) // 使用English作为默认语言标签 - ZipedFileName = caser.String(ZipedFileName) // 字母小写 - - // 生成解压文件文件名 - UnZipedFile := "ip_" + ZipedFileName + "_unZiped.txt" - - fileInfos, err := task.UnZip2txtFile(*File, UnZipedFile) - if err != nil { - fmt.Printf("解压文件时出错: %v\n", err) - return - } - - if fileInfos != nil { - // ASN 格式 - processASNZipedFiles(fileInfos) - // 下载测速并保存结果 - downloadSpeedTest() - } else { - // 非 ASN 格式,使用合并后的文件 - task.UnZip2txtFile(*File, UnZipedFile) - processIPListFile(UnZipedFile) - // 下载测速并保存结果 - downloadSpeedTest() - } - } else { - - if strings.ToLower(*File) == "ip_cfv4ipdb.txt" { - parsedFile := "ip_CFv4IPDB_Parsed.txt" - randomParseCIDR(*File, parsedFile) - *File = parsedFile - // 设置测速结果文件名 - resetOutFileName(*File) - *outFile = "result_Cfv4ipdb.csv" - } - if strings.Contains(strings.ToLower(*File), "cidr") { - pasedName := strings.Split(*File, ".")[0] - parsedFile := pasedName + "_Pased.txt" - randomParseCIDR(*File, parsedFile) - *File = parsedFile - // 设置测速结果文件名 - resetOutFileName(*File) - *outFile = "result_" + strings.Split(pasedName, "_")[1] + ".csv" - } - - // 处理非 ZIP 文件 - processIPListFile(*File) - // 下载测速并保存结果 - downloadSpeedTest() - } - - // 更新数据 - reader := bufio.NewReader(os.Stdin) - switch { - case *Domain != "" && *Token != "" && countQualified > 0: - switch strings.ToLower(*outFile) { - case "result_baipiaoge.csv", "result_proxyipdb.csv", "result_cfv4ipdb.csv", "result_scanner.csv", "result_selected.csv", "result_fofa.csv", "result_fofas.csv": - // case "txt.zip", "ip_proxyipdb.txt", "ip_cfv4ipdb.txt", "ip_scanner.txt", "ip_selected.txt", "fofa.zip", "ip_proxyipdb", "ip_fofa.txt", "fofas.zip": - dataUpdate(*outFile, *Domain, *Token) - default: - fmt.Printf("\n> 优质ip数量:\033[32m%d\033[0m ,是否要上传数据?(y/n):", countQualified) - input, _ := reader.ReadString('\n') - input = strings.TrimSpace(input) - if input == "y" { - dataUpdate(*outFile, *Domain, *Token) - } else { - fmt.Println("退出程序") - } - } - case (*Domain == "" || *Token == "") && countQualified > 0: - fmt.Printf("\n> 优质ip数量:\033[32m%d\033[0m ,是否要上传数据?(y/n):", countQualified) - input, _ := reader.ReadString('\n') - input = strings.TrimSpace(input) - if input == "y" { - if *Domain == "" { - fmt.Println("\033[90m请输入Domain(网址):\033[0m") - domain, _ := reader.ReadString('\n') - *Domain = strings.TrimSpace(domain) - } - if *Token == "" { - fmt.Println("\033[90m请输入Token:\033[0m") - token, _ := reader.ReadString('\n') - *Token = strings.TrimSpace(token) - } - if *Domain != "" && *Token != "" { - dataUpdate(*outFile, *Domain, *Token) - } else { - fmt.Println("\033[31m主机名或token缺失,本次更新取消!\033[0m") - os.Exit(0) - } - - } else { - fmt.Println("退出程序") - } - default: - os.Exit(0) - } -} - // 功能函数 // 检查location.json文件,如不存在,则从网络下载 @@ -370,6 +192,24 @@ func locationsJsonDownload() { return } fmt.Println("\033[32m成功下载并创建 location.json\033[0m") + file, err = os.Open("locations.json") + if err != nil { + fmt.Printf("无法打开文件: %v\n", err) + return + } + defer file.Close() + + body, err = io.ReadAll(file) + if err != nil { + fmt.Printf("无法读取文件: %v\n", err) + return + } + + err = json.Unmarshal(body, &locations) + if err != nil { + fmt.Printf("无法解析JSON: %v\n", err) + return + } } else { fmt.Println("\033[0;90m本地 locations.json 已存在,无需重新下载\033[0m") file, err := os.Open("locations.json") @@ -500,7 +340,7 @@ func checkProxyUrl(urlStr string) bool { } // 检查IP库是否存在并执行下载 -func checkIPLab(url string, fileName string) { +func checkIPLib(url string, fileName string) { fileInfo, err := os.Stat(fileName) if os.IsNotExist(err) { // 文件不存在,直接下载 @@ -680,12 +520,20 @@ func processIPListFile(fileName string) { func logWithProgress(countProcessed, total int) { if total != 0 { percentage := float64(countProcessed) / float64(total) * 100 - barLength := 28 // 进度条长度 + // barLength := 29 // 进度条长度 + baseLens = len("-IP 64.110.104.30 端口 443 位置:JP KIX 延迟 158 ms [ 489 ms ]") + totalCountLen := len(strconv.Itoa(totalIPs)) + alivelen := (totalCountLen - 1) + if (totalCountLen-2)*2+1 >= (totalCountLen - 1) { + alivelen = (totalCountLen - 2) * 2 + } + barLength := baseLens - len("进度") - len("100.00% / 存活 IP: /") - totalCountLen*2 - alivelen - 4 progress := int(percentage / 100 * float64(barLength)) // 构建进度条 bar := fmt.Sprintf("\033[32m%s\033[0m%s", strings.Repeat("■", progress), strings.Repeat("▣", barLength-progress)) // 并发检测进度显示 + fmt.Printf("\r进度%s\033[1;32m%6.2f%%\033[0m \033[90m%d\033[0m/\033[90m%d\033[0m ", bar, percentage, countProcessed, total) } } @@ -724,7 +572,7 @@ func delayedDetectionIPs(ips []string, enableTLS bool, port int) int { countProcessedInt := int(atomic.LoadInt32(&countProcessedInt32)) // 调用logWithProgress 函数输出进度信息 logWithProgress(countProcessedInt, total) - fmt.Printf("存活 IP: \033[1;32;5m%d\033[0m \r", countAlive) + fmt.Printf("存活 IP: \033[1;32;5m%d\033[0m\r", countAlive) }() // 如果 IP 格式为 ip:port,则分离 IP 和端口 @@ -768,11 +616,16 @@ func delayedDetectionIPs(ips []string, enableTLS bool, port int) int { totalDelay += delay } } + if recv == 0 { + return + } + Sended := PingTimes Received := recv LossRate := float64((Sended - Received) / Sended) - if LossRate > 0.3 { + if LossRate >= 0.5 { + // fmt.Printf("丢包率错误:%.1f\n", LossRate) return } tcpDuration := totalDelay / time.Duration(Received) @@ -784,11 +637,14 @@ func delayedDetectionIPs(ips []string, enableTLS bool, port int) int { // 使用 DialContext 函数 DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return (&net.Dialer{ - Timeout: httpTimeout, - KeepAlive: 0, + // Timeout: httpTimeout, + // KeepAlive: 0, }).DialContext(ctx, network, net.JoinHostPort(ip, strconv.Itoa(port))) }, }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse // 阻止重定向 + }, Timeout: httpTimeout, } @@ -806,45 +662,46 @@ func delayedDetectionIPs(ips []string, enableTLS bool, port int) int { req.Close = true resp, err := client.Do(req) if err != nil { - // fmt.Println("错误:",err) + // fmt.Println("http错误:", err) return } if resp.StatusCode != http.StatusOK { - fmt.Println(resp.StatusCode) + // fmt.Printf("ip:%s,状态码:%d \n", ip, resp.StatusCode) return } duration := time.Since(start) if duration > maxDuration { - fmt.Printf("duration:%v,max:%v \033[31m超时\033[0m]\n", duration, maxDuration) + // fmt.Printf("http延时:%v,最大允许:%v \033[31m超时\033[0m] \n", duration, maxDuration) return } body, err := io.ReadAll(resp.Body) + // var colo string // if resp.Header.Get("Server") == "cloudflare" { - // // str := resp.Header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC - // // colo := regexp.MustCompile(`[A-Z]{3}`).FindString(str) - // // fmt.Printf("Cloudflare:%s\n", colo) + // str := resp.Header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC + // colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str) // } else { // str := resp.Header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1 - // colo := regexp.MustCompile(`[A-Z]{3}`).FindString(str) - // fmt.Printf("AWS:%s\n", colo) - // fmt.Println(string(body)) + // colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str) // } // pause() if strings.Contains(string(body), "uag=Mozilla/5.0") { - if matches := regexp.MustCompile(`colo=([A-Z]+)`).FindStringSubmatch(string(body)); len(matches) > 1 { + if matches := regexp.MustCompile(`colo=([A-Z]{3})`).FindStringSubmatch(string(body)); len(matches) > 1 { dataCenter := matches[1] loc, ok := locationMap[dataCenter] - + // 排除的国家代码 + if len(exceludedCountries) != 0 && containsIgnoreCase(exceludedCountries, loc.Cca2) { + return + } // 根据 TCP 和 HTTP 延迟筛选检测结果 if float64(tcpDuration.Milliseconds()) <= float64(*tcpLimit) && float64(duration.Milliseconds()) <= float64(*httpLimit) { // 根据国家代码筛选检测结果,如果为空,则不筛选 if len(countries) == 0 || containsIgnoreCase(countries, loc.Cca2) { countAlive++ // 记录存活 IP 数量 if ok { - fmt.Printf("-IP %-15s 端口 %-5d 位置:%2s%12s 延迟 %3d ms 丢包 %.2f \n", ip, port, loc.Cca2, loc.City, tcpDuration.Milliseconds(), LossRate) + fmt.Printf("-IP %-15s 端口 %-5d 位置:%2s %3s 延迟 %3d ms [ \033[90m%-3d ms\033[0m ]\n", ip, port, loc.Cca2, dataCenter, tcpDuration.Milliseconds(), duration.Milliseconds()) resultChan <- latencyTestResult{ip, port, enableTLS, dataCenter, loc.Region, loc.Cca2, loc.City, fmt.Sprintf("%d", duration.Milliseconds()), fmt.Sprintf("%d", tcpDuration.Milliseconds())} } else { @@ -853,6 +710,8 @@ func delayedDetectionIPs(ips []string, enableTLS bool, port int) int { resultChan <- latencyTestResult{ip, port, enableTLS, dataCenter, "", "", "", fmt.Sprintf("%d", duration.Milliseconds()), fmt.Sprintf("%d", tcpDuration.Milliseconds())} } } + } else { + // fmt.Printf("-IP %-15s 端口 %-5d 位置:%2s%3s tcp延迟 %3d ms http延迟 %3d [不合格] \n", ip, port, loc.Cca2, dataCenter, loc.City, tcpDuration.Milliseconds(), duration.Milliseconds()) } } } else { @@ -917,6 +776,7 @@ func readIPs(File string) ([]string, error) { ipPort := strings.Split(ipAddr, "#")[0] ip := strings.Split(ipPort, ":")[0] portStr := strings.Split(ipPort, ":")[1] + portStr = strings.TrimSpace(portStr) port, err := strconv.Atoi(portStr) if err != nil { fmt.Println("%s端口转换错误:", ipAddr, err) @@ -989,8 +849,8 @@ func getDownloadSpeed(ip string, port int, enableTLS bool, latency string, tcpla Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { return (&net.Dialer{ - Timeout: tcpTimeout, - KeepAlive: 0, + // Timeout: httpTimeout, + KeepAlive: dlTimeout, }).DialContext(ctx, network, net.JoinHostPort(ip, strconv.Itoa(port))) }, }, @@ -1064,14 +924,15 @@ func getDownloadSpeed(ip string, port int, enableTLS bool, latency string, tcpla if *multipleNum == 1 || *speedTestThreads < 5 { // 输出结果 - if speed >= 0 { - fmt.Printf("-IP %-15s 端口 %-5s 国家 %2s 延迟 %3s ms 速度 %4.1f MB/s%s\n", ip, strconv.Itoa(port), country, tcplatency, speed, strings.Repeat(" ", 8)) + if speed >= *speedLimit/2 { + // 速度达到限速一半才在命令行输出 + fmt.Printf("-IP %-15s 端口 %-5s 国家 %2s 延迟 %3s ms 速度 %4.1f MB/s%s\n", ip, strconv.Itoa(port), country, tcplatency, speed, strings.Repeat(" ", 0)) } return speed } else { // 多协程测速会有速度损失,加以补偿 xspeed := speed * (*multipleNum) - fmt.Printf("-IP %-15s 端口 %-5s 延迟 %3s ms 速度 %4.1f MB/s, %.0f×%2.1f%s\n", ip, strconv.Itoa(port), tcplatency, xspeed, *multipleNum, speed, strings.Repeat(" ", 1)) + fmt.Printf("-IP %-15s 端口 %-5s 延迟 %3s ms 速度 %4.1f MB/s, %.0f×%2.1f%s\n", ip, strconv.Itoa(port), tcplatency, xspeed, *multipleNum, speed, strings.Repeat(" ", 0)) return xspeed } } @@ -1147,18 +1008,21 @@ func downloadSpeedTest() { <-thread wg.Done() - // select { - // case <-ctx.Done(): - // return - // default: - // 记录下载测速进度 - atomic.AddInt32(&countSt, 1) - - // 输出测速进度 - countStInt := int(atomic.LoadInt32(&countSt)) - logWithProgress(countStInt, total) - fmt.Printf(" 优选 IP:\033[1;32;5m%-2d\033[0m \r", atomic.LoadInt32(&resultCount)) - // } + select { + case <-ctx.Done(): + countStInt := int(atomic.LoadInt32(&countSt)) + logWithProgress(countStInt, total) + fmt.Printf(" 优选 IP:\033[1;32;5m%d\033[0m/\033[32m%d\033[0m\r", atomic.LoadInt32(&resultCount), *RequestNum) + return + default: + // 记录下载测速进度 + atomic.AddInt32(&countSt, 1) + + // 输出测速进度 + countStInt := int(atomic.LoadInt32(&countSt)) + logWithProgress(countStInt, total) + fmt.Printf(" 优选 IP:\033[1;32;5m%d\033[0m/\033[32m%d\033[0m\r", atomic.LoadInt32(&resultCount), *RequestNum) + } }() select { @@ -1188,14 +1052,15 @@ func downloadSpeedTest() { if _, exists := netIDs.Load(netID); !exists { // 将当前网络段标记为已存储 netIDs.Store(netID, true) - if atomic.LoadInt32(&resultCount) >= int32(*RequestNum) && !*DownloadTestAll { - cancel() - } + if atomic.LoadInt32(&resultCount) < int32(*RequestNum) && !*DownloadTestAll { // 将当前测速结果添加到results中 results = append(results, speedTestResult{latencyTestResult: res, downloadSpeed: downloadSpeed}) // 如果下载速度大于等于速度限制,增加resultCount计数 atomic.AddInt32(&resultCount, 1) + if atomic.LoadInt32(&resultCount) >= int32(*RequestNum) && !*DownloadTestAll { + cancel() + } } else if *DownloadTestAll { // 将当前测速结果添加到results中 results = append(results, speedTestResult{latencyTestResult: res, downloadSpeed: downloadSpeed}) @@ -1203,8 +1068,9 @@ func downloadSpeedTest() { atomic.AddInt32(&resultCount, 1) } } + } else { + badresults = append(badresults, speedTestResult{latencyTestResult: res, downloadSpeed: downloadSpeed}) } - badresults = append(badresults, speedTestResult{latencyTestResult: res, downloadSpeed: downloadSpeed}) } }(id, res) } @@ -1212,7 +1078,7 @@ func downloadSpeedTest() { wg.Wait() // 测速进程运行完成 // time.Sleep(10 * time.Second) - fmt.Println("\n下载速度测试完成!") + // fmt.Println("\n下载速度测试完成!") // pause() } else { for _, res := range totalResultChan { @@ -1247,24 +1113,26 @@ func getNetworkSegment(ip string) string { // writeResults 写入文件函数 func writeResults(results []speedTestResult, badresults []speedTestResult) { - // 放到外面以便调用 - countQualified = 0 // 下载速度达标ip 计数器 - countSpeed := 0 // 下载速度大于0 计数器 - if *speedTestThreads > 0 { - for _, res := range results { - if res.downloadSpeed >= float64(*speedLimit) { - countQualified++ - } - if res.downloadSpeed > 0 { - countSpeed++ - } - } - } - if countSpeed == 0 { - fmt.Println("\033[31m下载测速无可用IP\033[0m ") + countQualified = len(results) + countUnQualified := len(badresults) + + if countQualified+countUnQualified == 0 { os.Exit(0) } - // 达标的测速ip输出到一个文件 + + if countQualified > 0 { + handleQualifiedResults(results) + } else if countUnQualified > 0 { + fmt.Printf("\n\n未发现下载速度高于 \033[31m%.1f\033[0m MB/s的IP,但存在可用低速IP\n", *speedLimit) + } + + if countUnQualified > 0 { + handleUnqualifiedResults(badresults) + } +} + +// 合格ip结果写入文件 +func handleQualifiedResults(results []speedTestResult) { file, err := os.Create(*outFile) if err != nil { fmt.Printf("无法创建文件: %v\n", err) @@ -1273,7 +1141,73 @@ func writeResults(results []speedTestResult, badresults []speedTestResult) { defer file.Close() file.WriteString("\xEF\xBB\xBF") // 标记为utf-8 bom编码,防止excel打开中文乱码 - // 未达标的测速ip输出到一个文件 + writer := csv.NewWriter(file) + defer writer.Flush() + + if *speedTestThreads > 0 { + writer.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "http延迟", "tcp延迟", "下载速度(MB/s)"}) + } else { + writer.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "延迟(ms)"}) + } + + fmt.Printf("\n\n优选ip,下载速度高于 \033[32m%2.1f\033[0m MB/s,测速结果:\n", *speedLimit) + + // 创建并发协程 + var wg sync.WaitGroup + type resultWithIPTag struct { + result speedTestResult + ipTag string + } + type resultWithIPTagByIndex struct { + index int + resultWithIPTag + } + requestNum := *RequestNum + index := 0 + resultsChan := make(chan resultWithIPTagByIndex) + + for _, res := range results { + if *speedTestThreads > 0 && res.downloadSpeed >= float64(*speedLimit) && index < requestNum { + index++ + wg.Add(1) + go func(index int, res speedTestResult) { + defer func() { + wg.Done() + fmt.Print(".") + }() + OutFileName := strings.Split(*outFile, ".")[0] + suffixName := strings.Split(OutFileName, "_")[1] + dataCenterCountry := res.country + ipTag := getIPTag(res.ip, res.port, dataCenterCountry, suffixName) + // 添加索引以便不打乱顺序 + resultsChan <- resultWithIPTagByIndex{index - 1, resultWithIPTag{res, ipTag}} + }(index, res) + } else { + writer.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency, res.tcpDuration, fmt.Sprintf("%.1f", res.downloadSpeed)}) + } + } + + go func() { + wg.Wait() + close(resultsChan) + }() + + resultSlice := make([]resultWithIPTag, requestNum) + for result := range resultsChan { + resultSlice[result.index] = result.resultWithIPTag + } + for _, result := range resultSlice { + if result.result.ip != "" { + writer.Write([]string{result.result.ip, strconv.Itoa(result.result.port), strconv.FormatBool(*enableTLS), result.result.dataCenter, result.result.region, result.ipTag, result.result.city, result.result.latency, result.result.tcpDuration, fmt.Sprintf("%.1f", result.result.downloadSpeed)}) + fmt.Printf("%-15s:%-5d#%-2s-%-4.1f MB/s 平均延迟 %-3sms\n", result.result.ip, result.result.port, result.result.country, result.result.downloadSpeed, result.result.tcpDuration) + } + } + + fmt.Printf("\n\033[32m>\033[0m 优质ip写入 \033[90;4m%s\033[0m 耗时 %d 秒\n", *outFile, time.Since(startTime)/time.Second) +} + +// 未达标结果,保存以便查阅 +func handleUnqualifiedResults(badresults []speedTestResult) { outFileUnqualified := "result_Unqualified.csv" fileUnqualified, err := os.Create(outFileUnqualified) if err != nil { @@ -1283,65 +1217,23 @@ func writeResults(results []speedTestResult, badresults []speedTestResult) { defer fileUnqualified.Close() fileUnqualified.WriteString("\xEF\xBB\xBF") // 标记为utf-8 bom编码,防止excel打开中文乱码 - writer := csv.NewWriter(file) writerUnqualified := csv.NewWriter(fileUnqualified) + defer writerUnqualified.Flush() if *speedTestThreads > 0 { - if countQualified > 0 { - writer.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "http延迟", "tcp延迟", "下载速度(MB/s)"}) - } writerUnqualified.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "http延迟", "tcp延迟", "下载速度(MB/s)"}) } else { - writer.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "延迟(ms)"}) writerUnqualified.Write([]string{"IP地址", "端口", "TLS", "数据中心", "地区", "国家", "城市", "延迟(ms)"}) } - // fmt.Printf("\n") - if countQualified > 0 { - fmt.Printf("\n\n优选ip,下载速度高于 \033[32m%2.1f\033[0m MB/s,测速结果:\n", *speedLimit) - } - requestNum := *RequestNum - num := 0 - for _, res := range results { - if *speedTestThreads > 0 { - if res.downloadSpeed >= float64(*speedLimit) && countQualified > 0 && num < requestNum { - num++ - OutFileName := strings.Split(*outFile, ".")[0] // 去掉后缀名 - suffixName := strings.Split(OutFileName, "_")[1] - - // 根据设定限速值,测速结果写入不同文件 - writer.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country + "-" + suffixName, res.latencyTestResult.city, res.latencyTestResult.latency, res.tcpDuration, fmt.Sprintf("%.1f", res.downloadSpeed)}) - - // 终端输出优选结果 - fmt.Printf("%-15s:%-5d#%-2s-%2.1f MB/s 平均延迟 %-3sms\n", res.latencyTestResult.ip, res.latencyTestResult.port, res.latencyTestResult.country, res.downloadSpeed, res.tcpDuration) - - } else { - writerUnqualified.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency, res.tcpDuration, fmt.Sprintf("%.1f", res.downloadSpeed)}) - } - } else { - writer.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency}) - writerUnqualified.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency}) - } - } for _, res := range badresults { if *speedTestThreads > 0 { writerUnqualified.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency, res.tcpDuration, fmt.Sprintf("%.1f", res.downloadSpeed)}) } else { - writer.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency}) writerUnqualified.Write([]string{res.latencyTestResult.ip, strconv.Itoa(res.latencyTestResult.port), strconv.FormatBool(*enableTLS), res.latencyTestResult.dataCenter, res.latencyTestResult.region, res.latencyTestResult.country, res.latencyTestResult.city, res.latencyTestResult.latency}) } } - writer.Flush() - writerUnqualified.Flush() - // 清除输出内容 - // fmt.Print("\033[2J") - if countQualified > 0 { - fmt.Printf("\n\033[32m>\033[0m 优质ip写入 \033[90;4m%s\033[0m 耗时 %d 秒\n", *outFile, time.Since(startTime)/time.Second) - } else { - fmt.Printf("\n\n未发现下载速度高于 \033[31m%.1f\033[0m MB/s的IP,但存在可用低速IP\n", *speedLimit) - } - fmt.Printf("\033[31m>\033[0m 低速ip写入 \033[4;90m%s\033[0m 耗时 %d 秒\n", outFileUnqualified, time.Since(startTime)/time.Second) } @@ -1368,3 +1260,261 @@ func tcping(ip string, port int) (bool, time.Duration) { duration := time.Since(startTime) return true, duration } + +func getIPTag(ip string, port int, dataCenterCoCo string, tag string) string { + var apiKey string + apiKeys := *IPInfoApiKey // 填写你的apiKey + apiKeysArray := strings.Split(apiKeys, ",") + for _, key := range apiKeysArray { + if task.IsApiKeyNotExceed(key) { + apiKey = key + break + } + } + // 通过api接口获取ip信息 + info, err := task.GetIPInfo(ip, apiKey) + if err != nil { + fmt.Println("Error:", err) + return "获取错误" + } + + // 判断原生ip或广播ip + var uniIPStatus string + if info.IsUniIP() { + uniIPStatus = "Uni" + } else { + uniIPStatus = "Bro" + } + + // 获取ASN组织者名称缩写 + org := info.GetOrgNameAbbr() + + // 获取ip类型,住宅、商务、机房等 + ipType, err := info.GetIPType() + if err != nil { + fmt.Println("Error:", err) + return "获取错误" + } + + // 根据数据中心地址和ip位置是否相同,设置显示信息 + var ipLocation string + var cnProxy string + ipCoCo := info.Location.Country_code + if ipCoCo == dataCenterCoCo { + ipLocation = ipCoCo + } else { + ipLocation = ipCoCo + "-" + dataCenterCoCo + if ipCoCo == "CN" { + cnProxy = "中转" + } + } + + // 格式化输出内容 + // line := fmt.Sprintf("%s:%d#%s-%s%s-%s-%d丨%s", ip, port, ipLocation, uniIPStatus, ipType, org, info.ASN.ASN, tag) + // fmt.Println(line) + + ipTag := fmt.Sprintf("%s-%s%s%s-%s-%d丨%s", ipLocation, uniIPStatus, ipType, cnProxy, org, info.ASN.ASN, tag) + return ipTag +} + +// main 主程序 +func main() { + flag.Parse() // 解析命令行参数 + startTime = time.Now() + + osType := runtime.GOOS + if osType == "linux" { + increaseMaxOpenFiles() + } + + // 下载locations.json + locationsJsonDownload() + + // 网络环境检测,如网络不正常自动退出 + if !autoNetworkDetection() { + return + } + + // 存储国家代码到数组中 + if *countryCodes != "" { + countries = strings.Split(strings.ToUpper(*countryCodes), ",") + } + + // 存储排除代码到数组中 + if *ExcludedCounties != "" { + exceludedCountries = strings.Split(strings.ToUpper(*ExcludedCounties), ",") + } + + // ipLib列表文件下载及时效性检测 + if *DownloadipLib { + checked := make(map[string]bool) + for file, url := range ipLibs { + if url == "https://zip.baipiao.eu.org/" { + if !checked[url] { + fmt.Printf("\033[90m检查 %s\033[0m\n", file) + checkIPLib(url, file) + checked[url] = true + } + } else { + fmt.Printf("\033[90m检查 %s\033[0m\n", file) + checkIPLib(url, file) + } + } + fmt.Printf("\033[32mIP库文件已处于最新状态,请修改\033[0m \033[90m-iplib=false\033[0m \033[32m重新运行程序\033[0m\n") + os.Exit(0) + } else { + lowerFile := strings.ToLower(*File) + var matchedFile string + for key := range ipLibs { + if strings.ToLower(key) == lowerFile { + matchedFile = key + break + } + } + if matchedFile != "" { + *File = matchedFile + checkIPLib(ipLibs[matchedFile], *File) + } else if *File == "ip.txt" { + _, err := os.Stat(*File) + if os.IsNotExist(err) { + fmt.Printf("%s 不存在,切换网络IP库 >>>\n", *File) + *File = "txt.zip" + ipLiburl := "https://zip.baipiao.eu.org/" + checkIPLib(ipLiburl, *File) + } + } else { + _, err := os.Stat(*File) + if os.IsNotExist(err) { + fmt.Printf("%s 文件不存在,请检查输入!\n", *File) + os.Exit(0) + } + } + } + + // 支持直接设置ip参数检测单个ip + if *DirectIP != "" { + *outFile = "result_TestIP.csv" + var DirectIPs []string + DirectIPs = strings.Split(*DirectIP, ",") + + totalIPs = len(DirectIPs) + for _, ipPort := range DirectIPs { + if len(strings.Split(ipPort, ":")) > 1 { + port, _ := strconv.Atoi(strings.Split(ipPort, ":")[1]) + ipPort := strings.Fields(ipPort) + delayedDetectionIPs(ipPort, *enableTLS, port) + } else { + port := *defaultPort + ips := strings.Fields(ipPort) + delayedDetectionIPs(ips, *enableTLS, port) + } + } + + pause() + downloadSpeedTest() + os.Exit(0) + } else { + // 设置测速结果文件名 + resetOutFileName(*File) + } + + if strings.HasSuffix(*File, ".zip") { + // 获取压缩包文件名 + ZipedFileName := strings.Split(*File, ".")[0] + caser := cases.Title(language.English) // 使用English作为默认语言标签 + ZipedFileName = caser.String(ZipedFileName) // 字母小写 + + // 生成解压文件文件名 + UnZipedFile := "ip_" + ZipedFileName + "_unZiped.txt" + + fileInfos, err := task.UnZip2txtFile(*File, UnZipedFile) + if err != nil { + fmt.Printf("解压文件时出错: %v\n", err) + return + } + + if fileInfos != nil { + // ASN 格式 + processASNZipedFiles(fileInfos) + // 下载测速并保存结果 + downloadSpeedTest() + } else { + // 非 ASN 格式,使用合并后的文件 + task.UnZip2txtFile(*File, UnZipedFile) + processIPListFile(UnZipedFile) + // 下载测速并保存结果 + downloadSpeedTest() + } + } else { + + if strings.ToLower(*File) == "ip_cfv4ipdb.txt" { + parsedFile := "ip_CFv4IPDB_Parsed.txt" + randomParseCIDR(*File, parsedFile) + *File = parsedFile + // 设置测速结果文件名 + resetOutFileName(*File) + *outFile = "result_Cfv4ipdb.csv" + } + if strings.Contains(strings.ToLower(*File), "cidr") { + pasedName := strings.Split(*File, ".")[0] + parsedFile := pasedName + "_Pased.txt" + randomParseCIDR(*File, parsedFile) + *File = parsedFile + // 设置测速结果文件名 + resetOutFileName(*File) + *outFile = "result_" + strings.Split(pasedName, "_")[1] + ".csv" + } + + // 处理非 ZIP 文件 + processIPListFile(*File) + // 下载测速并保存结果 + downloadSpeedTest() + } + + // 更新数据 + reader := bufio.NewReader(os.Stdin) + switch { + case *Domain != "" && *Token != "" && countQualified > 0: + switch strings.ToLower(*outFile) { + case "result_baipiaoge.csv", "result_proxyipdb.csv", "result_cfv4ipdb.csv", "result_scanner.csv", "result_selected.csv", "result_fofa.csv", "result_fofas.csv","result_bihaicfip.csv","result_bihaicloudcfip.csv": + // case "txt.zip", "ip_proxyipdb.txt", "ip_cfv4ipdb.txt", "ip_scanner.txt", "ip_selected.txt", "fofa.zip", "ip_proxyipdb", "ip_fofa.txt", "fofas.zip": + dataUpdate(*outFile, *Domain, *Token) + default: + fmt.Printf("\n> 优质ip数量:\033[32m%d\033[0m ,是否要上传数据?(y/n):", countQualified) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + if input == "y" { + dataUpdate(*outFile, *Domain, *Token) + } else { + fmt.Println("退出程序") + } + } + case (*Domain == "" || *Token == "") && countQualified > 0: + fmt.Printf("\n> 优质ip数量:\033[32m%d\033[0m ,是否要上传数据?(y/n):", countQualified) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + if input == "y" { + if *Domain == "" { + fmt.Println("\033[90m请输入Domain(网址):\033[0m") + domain, _ := reader.ReadString('\n') + *Domain = strings.TrimSpace(domain) + } + if *Token == "" { + fmt.Println("\033[90m请输入Token:\033[0m") + token, _ := reader.ReadString('\n') + *Token = strings.TrimSpace(token) + } + if *Domain != "" && *Token != "" { + dataUpdate(*outFile, *Domain, *Token) + } else { + fmt.Println("\033[31m主机名或token缺失,本次更新取消!\033[0m") + os.Exit(0) + } + + } else { + fmt.Println("退出程序") + } + default: + os.Exit(0) + } +} diff --git a/task/getASN.go b/task/getASN.go new file mode 100644 index 0000000..05de62e --- /dev/null +++ b/task/getASN.go @@ -0,0 +1,195 @@ +package task + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +// IPInfo 用于存储从API返回的IP信息 +type IPInfo struct { + IP string `json:"ip"` + Company CompanyInfo `json:"company"` + ASN ASNInfo `json:"asn"` + Location LocationInfo `json:"location"` +} + +// CompanyInfo 用于存储公司信息 +type CompanyInfo struct { + Name string `json:"name"` + Type string `json:"type"` +} + +// ASNInfo 用于存储ASN信息 +type ASNInfo struct { + ASN int `json:"asn"` + Org string `json:"org"` + Country string `json:"country"` + Type string `json:"type"` +} + +// LocationInfo 用于存储位置信息 +type LocationInfo struct { + Country_code string `json:"country_code"` +} + +// getIPInfo 查询IP信息并返回其类型(住宅、商务、专线或其他) +func GetIPInfo(ip, apiKey string) (IPInfo, error) { + var url string + if apiKey != "" { + url = fmt.Sprintf("https://api.ipapi.is?q=%s&key=%s", ip, apiKey) + } else { + fmt.Println("使用免费接口,如检测量大请自行提供apiKey\r") + url = fmt.Sprintf("https://api.ipapi.is/?ip=%s", ip) + } + + // 创建一个自定义的 http.Client,并设置超时时间 + client := http.Client{ + Timeout: 50 * time.Second, // 设置超时时间为10秒 + } + + // 使用自定义的 client 发送 GET 请求 + resp, err := client.Get(url) + if err != nil { + return IPInfo{}, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return IPInfo{}, err + } + + var ipInfo IPInfo + err = json.Unmarshal(body, &ipInfo) + if err != nil { + return IPInfo{}, err + } + return ipInfo, err +} + +// 为 IPInfo 结构体创建 getIPType 方法 +func (info *IPInfo) GetIPType() (string, error) { + if info.Company.Type == "isp" { + switch info.ASN.Type { + case "isp": + return "住宅", nil + case "business": + return "家宽", nil + case "hosting": + return "托管", nil + default: + return info.ASN.Type, nil + } + } else if info.Company.Type == "business" { + switch info.ASN.Type { + case "isp": + return "商宽", nil + case "business": + return "商务", nil + case "hosting": + return "VPS", nil + default: + return info.ASN.Type, nil + } + } else if info.Company.Type == "hosting" { + switch info.ASN.Type { + case "isp": + return "中转", nil + case "business": + return "商管", nil + case "hosting": + return "机房", nil + default: + return "行业机房", nil + } + } else { + return info.ASN.Type, nil + } +} + +// 原生ip还是广播ip +func (info *IPInfo) IsUniIP() bool { + asnCountry := strings.ToUpper(info.ASN.Country) + locCountry := strings.ToUpper(info.Location.Country_code) + if asnCountry == locCountry { + return true + } + return false +} + +// Asn组织名称缩写 +func (info *IPInfo) GetOrgNameAbbr() string { + mappings := map[string]string{ + "sk broadband": "SKB", + "cmb": "CMB", + "taegu": "CMB", + "spectrum": "CFS", + "cloudflare": "CF", + "bigcommerce": "BigC", + "tcloudnet": "TCN", + "amazon": "AWS", + "linked": "Lin", + "porsche": "Porsche", + "tencent": "Tencent", + "alibaba": "ALi", + "oracle": "Oracle", + "powercomm": "LG", + "powervis": "LG", + "zdm network": "ZDM", + "cogent": "Cog", + "kirino": "Kirino", + "microsoft": "Microsoft", + "it7": "IT7", + "cluster": "Cluster", + "m247": "M247", + "multacom": "MUL", + "dimt": "DMIT", + "chunghwa": "CHT", + "pittqiao": "PIQ", + } + org := info.ASN.Org + for key, value := range mappings { + if strings.Contains(strings.ToLower(org), key) { + return value + } + } + + if len(org) > 5 { + return strings.ToUpper(org[:3]) + } + return strings.ToUpper(org) +} + +// 检测当前apiKey是否达到上限 +func IsApiKeyNotExceed(apiKey string) bool { + url := fmt.Sprintf("https://api.ipapi.is?q=%s&key=%s", "8.8.8.8", apiKey) + + // 创建一个自定义的 http.Client,并设置超时时间 + client := http.Client{ + Timeout: 50 * time.Second, // 设置超时时间为10秒 + } + + // 使用自定义的 client 发送 GET 请求 + resp, err := client.Get(url) + if err != nil { + fmt.Printf("连接错误:%v\n", err) + return false + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Print("无法读取") + return false + } + // fmt.Println(string(body)) + if strings.Contains(string(body), "exceeded") { + fmt.Println("ip-API达到限额") + return false + } + return true +} \ No newline at end of file diff --git a/task/unZip2txtFile.go b/task/unZip2txtFile.go index 360900e..fb81bec 100644 --- a/task/unZip2txtFile.go +++ b/task/unZip2txtFile.go @@ -3,7 +3,6 @@ package task import ( "archive/zip" "io" - "io/ioutil" "os" "regexp" "strconv" @@ -36,7 +35,7 @@ func UnZip2txtFile(zipPath string, outputPath string) ([]FileInfo, error) { if err != nil { return nil, err } - content, err := ioutil.ReadAll(rc) + content, err := io.ReadAll(rc) rc.Close() if err != nil { return nil, err diff --git a/tool/QueryDownload.go b/tool/QueryDownload.go new file mode 100644 index 0000000..462da08 --- /dev/null +++ b/tool/QueryDownload.go @@ -0,0 +1,145 @@ +package main + +import ( + "encoding/csv" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + //"strings" + "sync" + + "github.com/PuerkitoBio/goquery" +) + +// fetchLinks 从指定的URL获取所有以.csv结尾的下载链接 +func fetchLinks(baseURL string) ([]string, error) { + res, err := http.Get(baseURL) + if err != nil { + return nil, err + } + defer res.Body.Close() + + if res.StatusCode != 200 { + return nil, fmt.Errorf("status code error: %d %s", res.StatusCode, res.Status) + } + + doc, err := goquery.NewDocumentFromReader(res.Body) + if err != nil { + return nil, err + } + + var links []string + doc.Find(".fname1").Each(func(index int, item *goquery.Selection) { + link, _ := item.Attr("href") + //if strings.HasSuffix(link, ".csv") { + // 处理相对路径 + absoluteURL := resolveURL(baseURL, link) + links = append(links, absoluteURL) + //} + }) + + return links, nil +} + +// resolveURL 将相对路径转换为绝对路径 +func resolveURL(baseURL, relativeURL string) string { + base, err := url.Parse(baseURL) + if err != nil { + return "" + } + ref, err := url.Parse(relativeURL) + if err != nil { + return "" + } + return base.ResolveReference(ref).String() +} + +// downloadFileWg 下载指定URL的文件并保存到本地 +func downloadFileWg(baseURL, fileURL, baseDir string, wg *sync.WaitGroup) { + defer wg.Done() + + // 获取文件名和目录 + u, err := url.Parse(fileURL) + if err != nil { + fmt.Println("Error parsing URL:", err) + return + } + filePath := u.Path + fileName := path.Base(filePath) + dir := path.Join(baseDir, path.Dir(filePath)) + + // 创建目录 + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + fmt.Println("Error creating directory:", err) + return + } + + // 创建文件 + out, err := os.Create(filepath.Join(dir, fileName)) + if err != nil { + fmt.Println("Error creating file:", err) + return + } + defer out.Close() + + // 发送HTTP请求下载文件 + resp, err := http.Get(fileURL) + if err != nil { + fmt.Println("Error downloading file:", err) + return + } + defer resp.Body.Close() + + // 将响应内容写入文件 + _, err = io.Copy(out, resp.Body) + if err != nil { + fmt.Println("Error writing to file:", err) + return + } + + fmt.Println("Downloaded:", filepath.Join(dir, fileName)) +} + +func main() { + baseURL := "https://cloud.bhqt.fun/?dir=/碧海反代IP测试工具" + + // 获取程序当前工作目录 + baseDir, err := os.Getwd() + if err != nil { + fmt.Println("Error getting current directory:", err) + return + } + baseDir= baseDir+"/Test" + // 获取所有下载链接 + links, err := fetchLinks(baseURL) + if err != nil { + fmt.Println("Error fetching links:", err) + return + } + + // 创建CSV文件保存链接 + file, err := os.Create("download_links.csv") + if err != nil { + fmt.Println("Cannot create file:", err) + return + } + defer file.Close() + + writer := csv.NewWriter(file) + defer writer.Flush() + writer.Write([]string{"Link"}) + + var wg sync.WaitGroup + for _, link := range links { + writer.Write([]string{link}) // 将链接写入CSV文件 + wg.Add(1) + go downloadFileWg(baseURL, link, baseDir, &wg) // 并发下载文件 + } + + wg.Wait() // 等待所有下载完成 + fmt.Println("All files downloaded and links saved to download_links.csv") +} diff --git a/mergeCSV.go b/tool/mergeCSV.go similarity index 100% rename from mergeCSV.go rename to tool/mergeCSV.go diff --git a/tool/reParseResultTxtFile.go b/tool/reParseResultTxtFile.go new file mode 100644 index 0000000..3455a2a --- /dev/null +++ b/tool/reParseResultTxtFile.go @@ -0,0 +1,480 @@ +package main + +import ( + "bufio" + "context" + "encoding/json" + "flag" + "fmt" + "io" + "net" + "net/http" + "os" + "regexp" + "strings" + "sync" + "time" +) + +// IPInfo 用于存储从API返回的IP信息 +type IPInfo struct { + IP string `json:"ip"` + Company CompanyInfo `json:"company"` + ASN ASNInfo `json:"asn"` + Location LocationInfo `json:"location"` +} + +// CompanyInfo 用于存储公司信息 +type CompanyInfo struct { + Name string `json:"name"` + Type string `json:"type"` +} + +// ASNInfo 用于存储ASN信息 +type ASNInfo struct { + ASN int `json:"asn"` + Org string `json:"org"` + Country string `json:"country"` + Type string `json:"type"` +} + +// LocationInfo 用于存储位置信息 +type LocationInfo struct { + Country_code string `json:"country_code"` +} + +// 位置信息结构体 + +type location struct { + Iata string `json:"iata"` + Lat float64 `json:"lat"` + Lon float64 `json:"lon"` + Cca2 string `json:"cca2"` + Region string `json:"region"` + City string `json:"city"` +} + +const ( + requestURL = "https://speed.cloudflare.com/cdn-cgi/trace" // 请求trace URL + locationsJsonUrl = "https://speed.cloudflare.com/locations" // location.json下载 URL +) + +var locationMap map[string]location // IP位置数据 +// 读取机场信息 +func readLocationData() { + var locations []location + if _, err := os.Stat("locations.json"); os.IsNotExist(err) { + fmt.Println("正在从 " + locationsJsonUrl + " 下载 locations.json") + + resp, err := http.Get(locationsJsonUrl) + if err != nil { + fmt.Printf("下载失败: %v\n", err) + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Printf("无法读取响应体: %v\n", err) + return + } + + err = json.Unmarshal(body, &locations) + if err != nil { + fmt.Printf("无法解析JSON: %v\n", err) + return + } + file, err := os.Create("locations.json") + if err != nil { + fmt.Printf("无法创建文件: %v\n", err) + return + } + defer file.Close() + + _, err = file.Write(body) + if err != nil { + fmt.Printf("无法写入文件: %v\n", err) + return + } + fmt.Println("\033[32m成功下载并创建 location.json\033[0m") + } else { + file, err := os.Open("locations.json") + if err != nil { + fmt.Printf("无法打开文件: %v\n", err) + return + } + defer file.Close() + + body, err := io.ReadAll(file) + if err != nil { + fmt.Printf("无法读取文件: %v\n", err) + return + } + + err = json.Unmarshal(body, &locations) + if err != nil { + fmt.Printf("无法解析JSON: %v\n", err) + return + } + // 读取位置数据并存入变量 + locationMap = make(map[string]location) + for _, loc := range locations { + locationMap[loc.Iata] = loc + } + // fmt.Println("读取到 loacations 机场位置数据") + } +} + +// 从文件中读取IP地址并处理 +func readIPResults(File string) ([]string, error) { + file, err := os.Open(File) + if err != nil { + fmt.Println(err) + return nil, err + } + defer file.Close() + + var ipPortWithTag []string + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + ipAddr := scanner.Text() + if len(ipAddr) < 7 { + continue + } + ipPort := strings.Split(ipAddr, "#")[0] + ipTag := strings.Split(ipAddr, "丨")[1] + ipPortWithTag = append(ipPortWithTag, ipPort+"丨"+ipTag) + } + + return ipPortWithTag, scanner.Err() +} + +// 查询IP信息并返回其类型(住宅、商务、专线或其他) +func getIPInfo(ip, apiKey string) (IPInfo, error) { + var url string + if apiKey != "" { + url = fmt.Sprintf("https://api.ipapi.is?q=%s&key=%s", ip, apiKey) + } else { + fmt.Printf("\r使用免费接口,如检测量大请自行提供apiKey\r") + url = fmt.Sprintf("https://api.ipapi.is/?ip=%s", ip) + } + + // 创建一个自定义的 http.Client,并设置超时时间 + client := http.Client{ + Timeout: 50 * time.Second, // 设置超时时间为10秒 + } + + // 使用自定义的 client 发送 GET 请求 + resp, err := client.Get(url) + if err != nil { + fmt.Print("x") + return IPInfo{}, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Print("x") + return IPInfo{}, err + } + + var ipInfo IPInfo + err = json.Unmarshal(body, &ipInfo) + if err != nil { + fmt.Print("x") + return IPInfo{}, err + } + // fmt.Print(".") + return ipInfo, err +} + +// 为 IPInfo 结构体创建 getIPType 方法 +func (info *IPInfo) getIPType() (string, error) { + if info.Company.Type == "isp" { + switch info.ASN.Type { + case "isp": + return "住宅", nil + case "business": + return "家宽", nil + case "hosting": + return "托管", nil + default: + return info.ASN.Type, nil + } + } else if info.Company.Type == "business" { + switch info.ASN.Type { + case "isp": + return "商宽", nil + case "business": + return "商务", nil + case "hosting": + return "VPS", nil + default: + return info.ASN.Type, nil + } + } else if info.Company.Type == "hosting" { + switch info.ASN.Type { + case "isp": + return "中转", nil + case "business": + return "商管", nil + case "hosting": + return "机房", nil + default: + return "行业机房", nil + } + } else { + return info.ASN.Type, nil + } +} + +// 原生ip还是广播ip +func (info *IPInfo) isUniIP() bool { + asnCountry := strings.ToUpper(info.ASN.Country) + locCountry := strings.ToUpper(info.Location.Country_code) + if asnCountry == locCountry { + return true + } + return false +} + +// Asn组织名称缩写 +func (info *IPInfo) getOrgNameAbbr() string { + mappings := map[string]string{ + "sk broadband": "SKB", + "cmb": "CMB", + "taegu": "CMB", + "spectrum": "CFS", + "cloudflare": "CF", + "bigcommerce": "BigC", + "tcloudnet": "TCN", + "amazon": "AWS", + "linked": "Lin", + "porsche": "Porsche", + "tencent": "Tencent", + "alibaba": "ALi", + "oracle": "Oracle", + "powercomm": "LG", + "powervis": "LG", + "zdm network": "ZDM", + "cogent": "Cog", + "kirino": "Kirino", + "microsoft": "Microsoft", + "it7": "IT7", + "cluster": "Cluster", + "m247": "M247", + "multacom": "MUL", + "dimt": "DMIT", + "chunghwa": "CHT", + "pittqiao": "PIQ", + } + org := info.ASN.Org + for key, value := range mappings { + if strings.Contains(strings.ToLower(org), key) { + return value + } + } + + if len(org) > 5 { + return strings.ToUpper(org[:3]) + } + return strings.ToUpper(org) +} + + +func reParseResultTxtFile(ipFile string, apiKey string) { + // 定义命令行参数 + flag.Parse() + args := flag.Args() + // ip := "8.8.8.8" // 替换为要查询的IP地址 + + if len(args) > 0 { + ipFile = args[0] + } + + if ipFile == "" { + ipFile = "ip-AI.txt" + } + + ipPortWithTag, err := readIPResults(ipFile) + if err != nil { + return + } + type Result struct { + index int + line string + } + + results := make(chan Result, len(ipPortWithTag)) + + var wg sync.WaitGroup + fmt.Println("\033[90m正在获取ip信息并处理,请稍等!\033[0m") + + for i, ipPortWithTag := range ipPortWithTag { + wg.Add(1) + go func(index int, ipPortWithTag string) { + defer func() { + wg.Done() + fmt.Print(".") + }() + + ipPort := strings.Split(ipPortWithTag, "丨")[0] + tag := strings.Split(ipPortWithTag, "丨")[1] + ip := strings.Split(ipPort, ":")[0] + port := strings.Split(ipPort, ":")[1] + dataCenterCoCo := checkDataCenterCoco(ip, port) + + info, err := getIPInfo(ip, apiKey) + if err != nil { + results <- Result{index, fmt.Sprintf("获取ip信息错误 %s: %v", ip, err)} + return + } + + ipType, err := info.getIPType() + if err != nil { + results <- Result{index, fmt.Sprintf("获取ip类型错误 %s: %v", ip, err)} + return + } + + var uniIPStatus string + if info.isUniIP() { + uniIPStatus = "Uni" + } else { + uniIPStatus = "Bro" + } + + // 根据数据中心地址和ip位置是否相同,设置显示信息 + var ipLocation string + var cnProxy string + ipCoCo := info.Location.Country_code + + ipLocation = ipCoCo + if ipCoCo == dataCenterCoCo { + ipLocation = ipCoCo + } else if dataCenterCoCo != "" { + ipLocation = ipCoCo + "-" + dataCenterCoCo + if ipCoCo == "CN" { + cnProxy = "中转" + } + } + + org := info.getOrgNameAbbr() + line := fmt.Sprintf("%s:%s#%s-%s%s%s-%s-%d丨%s", ip, port, ipLocation, uniIPStatus, ipType, cnProxy, org, info.ASN.ASN, tag) + + results <- Result{index, line} + }(i, ipPortWithTag) + } + + go func() { + wg.Wait() + close(results) + }() + + // 收集结果 + resultSlice := make([]string, len(ipPortWithTag)) + for result := range results { + resultSlice[result.index] = result.line + } + + // 按顺序输出结果 + fmt.Println("\n\033[32m解析完成!\033[0m") + fmt.Println("+--------------------------------------------------------+") + for _, line := range resultSlice { + fmt.Println(line) + } + fmt.Println("+--------------------------------------------------------+") +} + + +func checkDataCenterCoco(ip string, port string) string { + client := http.Client{ + Transport: &http.Transport{ + // 使用 DialContext 函数 + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, network, net.JoinHostPort(ip, port)) + }, + }, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse // 阻止重定向 + }, + Timeout: 30 * time.Second, + } + + req, _ := http.NewRequest(http.MethodHead, requestURL, nil) + + // 添加用户代理 + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") + + req.Close = true + resp, err := client.Do(req) + if err != nil { + fmt.Print("x") + return "" + } + if resp.StatusCode != http.StatusOK { + fmt.Print("x") + return "" + } + + // 获取机场三字码,数据中心位置 + var colo string + if resp.Header.Get("Server") == "cloudflare" { + str := resp.Header.Get("CF-RAY") // 示例 cf-ray: 7bd32409eda7b020-SJC + colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str) + } else { + str := resp.Header.Get("x-amz-cf-pop") // 示例 X-Amz-Cf-Pop: SIN52-P1 + colo = regexp.MustCompile(`[A-Z]{3}`).FindString(str) + } + + loc, ok := locationMap[colo] + if ok { + // fmt.Print(".") + return loc.Cca2 + } + fmt.Print("x") + return "未获取数据" +} + +// 检测当前apiKey是否达到上限 +func isApiKeyNotExceed(apiKey string) bool { + url := fmt.Sprintf("https://api.ipapi.is?q=%s&key=%s", "8.8.8.8", apiKey) + + // 创建一个自定义的 http.Client,并设置超时时间 + client := http.Client{ + Timeout: 50 * time.Second, // 设置超时时间为10秒 + } + + // 使用自定义的 client 发送 GET 请求 + resp, err := client.Get(url) + if err != nil { + fmt.Printf("连接错误:%v\n", err) + return false + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Print("无法读取") + return false + } + // fmt.Println(string(body)) + if strings.Contains(string(body), "exceeded") { + fmt.Println("达到限额") + return false + } + return true +} + +func main() { + apiKey := "" // 替换为你的API密钥 + // // apiKey := "" + // if isApiKeyNotExceed(apiKey) { + // apiKey = "" + // } else { + // apiKey = "" + // } + readLocationData() + fmt.Print("\033[2J\033[0;0H") // 清空屏幕 + reParseResultTxtFile("", apiKey) +} diff --git a/tcpTest.go b/tool/tcpTest.go similarity index 71% rename from tcpTest.go rename to tool/tcpTest.go index 94861e2..f689edd 100644 --- a/tcpTest.go +++ b/tool/tcpTest.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/tls" + "flag" "fmt" "io" "net" @@ -27,7 +28,7 @@ const ( ) // checkIPPort 尝试连接到给定的IP和端口,并发送HTTP请求以获取响应内容 -func checkIPPort(ipPort IPPort, wg *sync.WaitGroup) { +func checkIPPort(ipPort IPPort, wg *sync.WaitGroup) string{ defer wg.Done() // 确保WaitGroup计数器在函数结束时递减 // 设置连接超时的拨号器 @@ -68,7 +69,7 @@ func checkIPPort(ipPort IPPort, wg *sync.WaitGroup) { // 发送请求并获取响应 resp, err := client.Do(req) if err != nil { - fmt.Printf("访问错误 %s:%s - %s\n", ipPort.IP, ipPort.Port, err) + fmt.Printf("访问错误 %s - %s\n", net.JoinHostPort(ipPort.IP, ipPort.Port), err) return } defer resp.Body.Close() @@ -82,9 +83,9 @@ func checkIPPort(ipPort IPPort, wg *sync.WaitGroup) { } content := buf.String() - fmt.Printf("来自 %s:%s 的响应内容:\n%s\n", ipPort.IP, ipPort.Port, content) + fmt.Printf("来自 %s 的响应内容:\n%s\n", net.JoinHostPort(ipPort.IP, ipPort.Port), content) if strings.Contains(content, "html") { - fmt.Printf("来自 %s:%s 的请求,成功\n", ipPort.IP, ipPort.Port) + fmt.Printf("来自 %s 的请求,成功\n", net.JoinHostPort(ipPort.IP, ipPort.Port)) } // 检查响应体是否包含指定的用户代理 if strings.Contains(content, "uag=Mozilla/5.0") { @@ -106,10 +107,36 @@ func checkIPPort(ipPort IPPort, wg *sync.WaitGroup) { } func main() { - // 定义要检查的IP和端口列表 - ipPorts := []IPPort{ - {"1.1.1.1", "443"}, + // 定义命令行参数 + flag.Parse() + args := flag.Args() + // 默认的IP和端口 + defaultIPPorts := []IPPort{ + {"8.8.8.8", "53"}, + } + + var ipPorts []IPPort + + if len(args) == 0 { + fmt.Println("未提供IP:Port参数,使用默认值") + ipPorts = defaultIPPorts + } else { + // 解析输入的IP和端口列表 + for _, arg := range strings.Split(args[0], ",") { + parts := strings.Split(arg, ":") + if len(parts) == 9 { + // ipv6格式:2a05:d014:ed:9600:f52b:ab01:6bb:bc9d + ipv6 := strings.Join(parts[0:8], ":") + port := parts[8] + ipPorts = append(ipPorts, IPPort{ipv6, port}) + } else if len(parts) == 2 { + ipPorts = append(ipPorts, IPPort{IP: parts[0], Port: parts[1]}) + } else { + fmt.Printf("无效的IP:Port格式: %s\n", arg) + continue + } + } } var wg sync.WaitGroup diff --git a/tcpTestWithProxy.go b/tool/tcpTestWithProxy.go similarity index 100% rename from tcpTestWithProxy.go rename to tool/tcpTestWithProxy.go diff --git a/update.go b/update.go index 0a4f164..af49e53 100644 --- a/update.go +++ b/update.go @@ -74,10 +74,10 @@ func dataUpdate(fileName string, domain string, token string) { // 设置HTTP客户端,启用Keep-Alive和重试逻辑 client := &http.Client{ Transport: &http.Transport{ - MaxIdleConns: 10, // 最大空闲连接数 - IdleConnTimeout: 30 * time.Second, // 空闲连接的超时时间 - MaxIdleConnsPerHost: 2, // 每个主机的最大空闲连接数 - DisableKeepAlives: false, // 启用 Keep-Alive + MaxIdleConns: 10, // 最大空闲连接数 + IdleConnTimeout: 30 * time.Second, // 空闲连接的超时时间 + MaxIdleConnsPerHost: 2, // 每个主机的最大空闲连接数 + DisableKeepAlives: false, // 启用 Keep-Alive // TLSHandshakeTimeout: 10 * time.Second, }, Timeout: time.Second * 30, // 设置单次请求超时时间为30秒 @@ -96,7 +96,8 @@ func dataUpdate(fileName string, domain string, token string) { fmt.Printf("更新请求失败,状态码: %d\n", resp.StatusCode) return } - fmt.Println("发起数据更新请求...........................................[\033[32mok\033[0m]") + repeatCount := baseLens - len("发起数据更新请求[ok]") + fmt.Printf("发起数据更新请求%s[\033[32mok\033[0m]\n", strings.Repeat(".", repeatCount)) // 构造读取URL readUrlStr := fmt.Sprintf("https://%s/%s?token=%s&v=%d", domain, url.PathEscape(fileName), token, time.Now().Unix()) @@ -131,12 +132,12 @@ func dataUpdate(fileName string, domain string, token string) { // 根据比较结果判断是否成功 UrlStr := fmt.Sprintf("https://%s/%s?token=%s", domain, url.PathEscape(fileName), token) - + repeatCount = baseLens - len("验证数据更新结果[ok]") if equ { - fmt.Println("验证数据更新结果...........................................[\033[32mok\033[0m]\n") - fmt.Println(string(responseContent)) - fmt.Printf("\n\033[90m优选IP\033[0m \033[90;4m%s\033[0m \033[90m已成功更新至:\033[0m\n\033[34m%s\033[0m\n", fileName, UrlStr) + fmt.Printf("验证数据更新结果%s[\033[32mok\033[0m]\n\n", strings.Repeat(".", repeatCount)) + // fmt.Println(string(responseContent)) + fmt.Printf("\n\033[90m优选IP\033[0m \033[90;4m%s\033[0m \033[90m已成功更新 %d 条数据至:\033[0m\n\033[34m%s\033[0m\n", fileName, countQualified,UrlStr) } else { - fmt.Println("验证数据更新结果...........................................[\033[31mX\033[0m]") + fmt.Printf("验证数据更新结果%s[\033[31mX\033[0m]\n", strings.Repeat(".", repeatCount)) } }