You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

406 lines
8.8 KiB
Go

1 year ago
package spider
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"fmt"
"github.com/ip2location/ip2location-go/v9"
"github.com/metacubex/mihomo/adapter"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/config"
C "github.com/metacubex/mihomo/constant"
"github.com/snakem982/proxypool/tools"
"gopkg.in/yaml.v3"
1 year ago
"log"
1 year ago
"net"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
)
const (
CollectClash = "clash"
CollectV2ray = "v2ray"
CollectSharelink = "share"
CollectFuzzy = "fuzzy"
)
//go:embed IP2LOCATION-LITE-DB1.BIN
var fsIp2 []byte
var db *ip2location.DB
var emojiMap = make(map[string]string)
//go:embed flags.json
var fsEmoji []byte
type dBReader struct {
reader *bytes.Reader
}
func (d dBReader) Read(p []byte) (n int, err error) {
return d.reader.Read(p)
}
func (d dBReader) Close() error {
return nil
}
func (d dBReader) ReadAt(p []byte, off int64) (n int, err error) {
return d.reader.ReadAt(p, off)
}
func init() {
db, _ = ip2location.OpenDBWithReader(dBReader{reader: bytes.NewReader(fsIp2)})
type countryEmoji struct {
Code string `json:"code"`
Emoji string `json:"emoji"`
}
var countryEmojiList = make([]countryEmoji, 0)
_ = json.Unmarshal(fsEmoji, &countryEmojiList)
for _, i := range countryEmojiList {
emojiMap[i.Code] = i.Emoji
}
}
func Crawl() bool {
// 加载默认配置中的节点
defaultBuf, defaultErr := os.ReadFile(filepath.Join(C.Path.HomeDir(), "uploads/0.yaml"))
proxies := make([]map[string]any, 0)
if defaultErr == nil && len(defaultBuf) > 0 {
rawCfg, err := config.UnmarshalRawConfig(defaultBuf)
if err == nil && len(rawCfg.Proxy) > 0 {
proxies = rawCfg.Proxy
1 year ago
log.Printf("load default config proxies success %d\n", len(rawCfg.Proxy))
1 year ago
}
}
// 获取getters
getters := make([]Getter, 0)
//values := cache.GetList(constant.PrefixGetter)
//if len(values) > 0 {
// for _, value := range values {
// getter := Getter{}
// _ = json.Unmarshal(value, &getter)
// getters = append(getters, getter)
// }
//}
// 进行抓取
if len(getters) > 0 {
wg := &sync.WaitGroup{}
var pc = make(chan []map[string]any)
for _, g := range getters {
collect, err := NewCollect(g.Type, g)
if err != nil {
continue
}
wg.Add(1)
go collect.Get2ChanWG(pc, wg)
}
go func() {
wg.Wait()
close(pc)
}()
for p := range pc {
if p != nil {
proxies = append(proxies, p...)
}
}
}
// 去重
maps := Unique(proxies, true)
if len(maps) == 0 {
return false
}
// 转换
nodes := map2proxies(maps)
if len(nodes) == 0 {
return false
}
// url测速
keys := urlTest(nodes)
if len(keys) == 0 {
return false
}
// 国家代码查询
proxies = GetCountryName(keys, maps)
// 排序添加emoji
SortAddEmoji(proxies)
if len(proxies) > 255 {
proxies = proxies[0:256]
}
// 存盘
data := make(map[string]any)
data["proxies"] = proxies
all, _ := yaml.Marshal(data)
filePath := C.Path.HomeDir() + "/uploads/0.yaml"
_ = os.Remove(filePath)
_ = os.WriteFile(filePath, all, 0777)
return true
}
func Unique(mappings []map[string]any, needTls bool) (maps map[string]map[string]any) {
maps = make(map[string]map[string]any)
for _, mapping := range mappings {
proxyType, existType := mapping["type"].(string)
if !existType {
continue
}
var (
proxyId string
err error
)
server := mapping["server"]
port := mapping["port"]
password := mapping["password"]
uuid := mapping["uuid"]
switch proxyType {
case "ss":
proxyId = fmt.Sprintf("%s|%v|%v|%v", "ss", server, port, password)
case "ssr":
proxyId = fmt.Sprintf("%s|%v|%v|%v", "ssr", server, port, password)
case "vmess":
if needTls {
tls, existTls := mapping["tls"].(bool)
if !existTls || !tls {
continue
}
}
proxyId = fmt.Sprintf("%s|%v|%v|%v", "vmess", server, port, uuid)
case "vless":
if needTls {
tls, existTls := mapping["tls"].(bool)
if !existTls || !tls {
continue
}
}
flow, existFlow := mapping["flow"].(string)
if existFlow && flow != "" && flow != "xtls-rprx-vision" {
continue
}
proxyId = fmt.Sprintf("%s|%v|%v|%v", "vless", server, port, uuid)
case "trojan":
if needTls {
_, existSni := mapping["sni"].(string)
if !existSni {
continue
}
}
proxyId = fmt.Sprintf("%s|%v|%v|%v", "trojan", server, port, password)
case "hysteria":
authStr, exist := mapping["auth_str"]
if !exist {
authStr = mapping["auth-str"]
}
proxyId = fmt.Sprintf("%s|%v|%v|%v", "hysteria", server, port, authStr)
case "hysteria2":
proxyId = fmt.Sprintf("%s|%v|%v|%v", "hysteria2", server, port, password)
case "wireguard":
authStr := mapping["private-key"]
proxyId = fmt.Sprintf("%s|%v|%v|%v", "wireguard", server, port, authStr)
case "tuic":
proxyId = fmt.Sprintf("%s|%v|%v|%v|%v", "tuic", server, port, uuid, password)
default:
err = fmt.Errorf("unsupport proxy type: %s", proxyType)
}
if err != nil {
continue
}
temp := mapping
temp["name"] = proxyId
maps[proxyId] = temp
}
return
}
func map2proxies(maps map[string]map[string]any) (proxies []C.Proxy) {
pool := NewTimeoutPoolWithDefaults()
pool.WaitCount(len(maps))
mutex := sync.Mutex{}
proxies = make([]C.Proxy, 0)
for _, m := range maps {
proxy := m
pool.SubmitWithTimeout(func(done chan struct{}) {
defer func() {
if e := recover(); e != nil {
1 year ago
log.Printf("===map2proxies===%s\n", e)
1 year ago
}
done <- struct{}{}
}()
proxyT, err := adapter.ParseProxy(proxy)
if err == nil {
mutex.Lock()
proxies = append(proxies, proxyT)
mutex.Unlock()
}
}, 2*time.Second)
}
pool.StartAndWait()
return
}
func urlTest(proxies []C.Proxy) []string {
pool := NewTimeoutPoolWithDefaults()
keys := make([]string, 0)
m := sync.Mutex{}
expectedStatus, _ := utils.NewUnsignedRanges[uint16]("200/204/301/302")
url := "https://www.gstatic.com/generate_204"
pool.WaitCount(len(proxies))
for _, p := range proxies {
proxy := p
pool.SubmitWithTimeout(func(done chan struct{}) {
defer func() {
if e := recover(); e != nil {
1 year ago
log.Printf("===urlTest===%s\n", e)
1 year ago
}
done <- struct{}{}
}()
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*4500)
defer cancel()
_, err := proxy.URLTest(ctx, url, expectedStatus)
if err == nil {
m.Lock()
keys = append(keys, proxy.Name())
m.Unlock()
}
}, 5*time.Second)
}
pool.StartAndWait()
return keys
}
func GetCountryName(keys []string, maps map[string]map[string]any) []map[string]any {
r := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
d := net.Dialer{
Timeout: 100 * time.Millisecond,
}
return d.DialContext(ctx, network, "1.1.1.1")
},
}
proxies := make([]map[string]any, 0)
ipLock := sync.Mutex{}
wg := sync.WaitGroup{}
wg.Add(len(keys))
for _, key := range keys {
m := maps[key]
m["name"] = "ZZ"
go func() {
defer wg.Done()
ipOrDomain := m["server"].(string)
if tools.CheckStringAlphabet(ipOrDomain) {
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
defer cancel()
iPv4, err := r.LookupIP(ctx, "ip4", ipOrDomain)
if err == nil && iPv4 != nil {
m["name"] = getCountryCode(iPv4[0].String())
}
} else {
m["name"] = getCountryCode(ipOrDomain)
}
ipLock.Lock()
proxies = append(proxies, m)
ipLock.Unlock()
}()
}
wg.Wait()
return proxies
}
func getCountryCode(domain string) string {
countryCode := "ZZ"
record, err := db.Get_country_short(domain)
if err != nil || len(record.Country_short) != 2 {
return countryCode
}
return record.Country_short
}
func getIndex(at string) int {
switch at {
case "hysteria2":
return 1
case "hysteria":
return 2
case "tuic":
return 3
case "ss":
return 5
case "vless":
return 6
default:
return 10
}
}
func SortAddEmoji(proxies []map[string]any) {
sort.Slice(proxies, func(i, j int) bool {
iProtocol := proxies[i]["type"].(string)
jProtocol := proxies[j]["type"].(string)
if getIndex(iProtocol) != getIndex(jProtocol) {
return getIndex(iProtocol) < getIndex(jProtocol)
}
if proxies[i]["name"].(string) != proxies[j]["name"].(string) {
return proxies[i]["name"].(string) < proxies[j]["name"].(string)
}
return tools.Reverse(proxies[i]["server"].(string)) < tools.Reverse(proxies[j]["server"].(string))
})
for i, _ := range proxies {
name := proxies[i]["name"].(string)
name = fmt.Sprintf("%s %s_%+02v", emojiMap[name], name, i+1)
proxies[i]["name"] = strings.TrimSpace(name)
}
}
func SortAddIndex(proxies []map[string]any) []map[string]any {
// 去重
maps := Unique(proxies, false)
keys := make([]string, 0)
for k := range maps {
keys = append(keys, k)
}
// 国家代码查询
proxies = GetCountryName(keys, maps)
// 排序添加emoji
SortAddEmoji(proxies)
return proxies
}