├── .gitignore ├── api ├── utils └── utils.go ├── README.md ├── app ├── 360doc.go ├── baidu.go ├── renmin.go ├── v2ex.go ├── xinjingbao.go ├── lishipin.go ├── guojiadili.go ├── csdn.go ├── dongqiudi.go ├── hupu.go ├── acfun.go ├── wangyinews.go ├── cctv.go ├── 360search.go ├── shaoshupai.go ├── nanfangzhoumo.go ├── github.go ├── ithome.go ├── douyin.go ├── bilibili.go ├── sougou.go ├── douban.go ├── toutiao.go ├── quark.go ├── pengpai.go ├── zhihu.go ├── qqnews.go ├── souhu.go ├── history.go └── weibo.go ├── go.mod ├── all └── all.go ├── main.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iiecho1/api-for-hot-search-golang/HEAD/api -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | func ExtractMatches(text, pattern string) [][]string { 8 | regex := regexp.MustCompile(pattern) 9 | matches := regex.FindAllStringSubmatch(text, -1) 10 | return matches 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 目前包含30个app,不断添加中 2 | ## 代码很简单粗暴,以后再精简优化 3 | 4 | + 360搜索 5 | + 哔哩哔哩 6 | + AcFun 7 | + CSDN 8 | + 懂球帝 9 | + 豆瓣 10 | + 抖音 11 | + GitHub 12 | + 国家地理 13 | + 历史上的今天 14 | + 虎扑 15 | + IT之家 16 | + 梨视频 17 | + 澎湃新闻 18 | + 腾讯新闻 19 | + 少数派 20 | + 搜狗 21 | + 今日头条 22 | + V2EX 23 | + 网易新闻 24 | + 微博 25 | + 新京报 26 | + 知乎 27 | + 夸克 28 | + 搜狐 29 | + 百度 30 | + 人民网 31 | + 南方周末 32 | + 360doc 33 | + CCTV新闻 34 | 35 | ## 运行 36 | 37 | `go run main.go` 38 | 39 | 默认端口为1111,可以自己改。 40 | 41 | 浏览器打开`ip:1111` 42 | 43 | 具体路径看`main.go`中的路由表 44 | 45 | 比如访问百度的热搜就是:`ip:1111/baidu` 46 | 47 | 查看全部app的热搜为`ip:1111/all` 48 | 49 | 建议`go build`为可执行文件。 -------------------------------------------------------------------------------- /app/360doc.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Doc360() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | 17 | url := "http://www.360doc.com/" 18 | resp, err := client.Get(url) 19 | if err != nil { 20 | return nil, fmt.Errorf("http.Get error: %w", err) 21 | } 22 | defer resp.Body.Close() 23 | 24 | pageBytes, err := io.ReadAll(resp.Body) 25 | if err != nil { 26 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 27 | } 28 | 29 | pattern := `
(?:)?(.*?)
` 30 | matched := utils.ExtractMatches(string(pageBytes), pattern) 31 | 32 | var obj []map[string]interface{} 33 | for index, item := range matched { 34 | // 添加边界检查,确保有足够的匹配项 35 | if len(item) >= 3 { 36 | obj = append(obj, map[string]interface{}{ 37 | "index": index + 1, 38 | "title": item[2], 39 | "url": item[1], 40 | }) 41 | } 42 | } 43 | 44 | api := map[string]interface{}{ 45 | "code": 200, 46 | "message": "360doc", 47 | "icon": "http://www.360doc.com/favicon.ico", // 16 x 16 48 | "obj": obj, 49 | } 50 | return api, nil 51 | } 52 | -------------------------------------------------------------------------------- /app/baidu.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func Baidu() (map[string]interface{}, error) { 13 | // 创建带超时的 HTTP 客户端 14 | client := &http.Client{ 15 | Timeout: 10 * time.Second, 16 | } 17 | 18 | url := "https://top.baidu.com/board?tab=realtime" 19 | resp, err := client.Get(url) 20 | if err != nil { 21 | return nil, fmt.Errorf("http.Get error: %w", err) 22 | } 23 | defer resp.Body.Close() 24 | 25 | pageBytes, err := io.ReadAll(resp.Body) 26 | if err != nil { 27 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 28 | } 29 | 30 | pattern := `(.*?)= 2 { 38 | title := strings.TrimSpace(item[1]) 39 | obj = append(obj, map[string]interface{}{ 40 | "index": index + 1, 41 | "title": title, 42 | "url": "https://www.baidu.com/s?wd=" + title, 43 | }) 44 | } 45 | } 46 | 47 | api := map[string]interface{}{ 48 | "code": 200, 49 | "message": "百度", 50 | "icon": "https://www.baidu.com/favicon.ico", // 64 x 64 51 | "obj": obj, 52 | } 53 | return api, nil 54 | } 55 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module api 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/bytedance/sonic v1.12.7 // indirect 7 | github.com/bytedance/sonic/loader v0.2.2 // indirect 8 | github.com/cloudwego/base64x v0.1.4 // indirect 9 | github.com/cloudwego/iasm v0.2.0 // indirect 10 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect 11 | github.com/gin-contrib/sse v1.0.0 // indirect 12 | github.com/gin-gonic/gin v1.10.0 // indirect 13 | github.com/go-playground/locales v0.14.1 // indirect 14 | github.com/go-playground/universal-translator v0.18.1 // indirect 15 | github.com/go-playground/validator/v10 v10.23.0 // indirect 16 | github.com/goccy/go-json v0.10.4 // indirect 17 | github.com/json-iterator/go v1.1.12 // indirect 18 | github.com/klauspost/cpuid/v2 v2.2.9 // indirect 19 | github.com/leodido/go-urn v1.4.0 // indirect 20 | github.com/mattn/go-isatty v0.0.20 // indirect 21 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 22 | github.com/modern-go/reflect2 v1.0.2 // indirect 23 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 24 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 25 | github.com/ugorji/go/codec v1.2.12 // indirect 26 | golang.org/x/arch v0.13.0 // indirect 27 | golang.org/x/crypto v0.32.0 // indirect 28 | golang.org/x/net v0.34.0 // indirect 29 | golang.org/x/sys v0.29.0 // indirect 30 | golang.org/x/text v0.21.0 // indirect 31 | google.golang.org/protobuf v1.36.2 // indirect 32 | gopkg.in/yaml.v3 v3.0.1 // indirect 33 | ) 34 | -------------------------------------------------------------------------------- /app/renmin.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Renminwang() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | url := "http://www.people.com.cn/GB/59476/index.html" 17 | resp, err := client.Get(url) 18 | if err != nil { 19 | return nil, fmt.Errorf("http.Get error: %w", err) 20 | } 21 | defer resp.Body.Close() 22 | // 检查状态码 23 | if resp.StatusCode != http.StatusOK { 24 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 25 | } 26 | pageBytes, err := io.ReadAll(resp.Body) 27 | if err != nil { 28 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 29 | } 30 | 31 | pattern := `
  • (.*?)
  • ` 32 | matched := utils.ExtractMatches(string(pageBytes), pattern) 33 | // 检查是否匹配到数据 34 | if len(matched) == 0 { 35 | return map[string]interface{}{ 36 | "code": 500, 37 | "message": "未匹配到数据,可能页面结构已变更", 38 | "icon": "http://www.people.com.cn/favicon.ico", 39 | "obj": []map[string]interface{}{}, 40 | }, nil 41 | } 42 | var obj []map[string]interface{} 43 | for index, item := range matched { 44 | result := make(map[string]interface{}) 45 | result["index"] = index + 1 46 | result["title"] = item[2] 47 | result["url"] = item[1] 48 | obj = append(obj, result) 49 | } 50 | api := map[string]interface{}{ 51 | "code": 200, 52 | "message": "人民网", 53 | "icon": "http://www.people.com.cn/favicon.ico", // 16 x 16 54 | "obj": obj, 55 | } 56 | return api, nil 57 | } 58 | -------------------------------------------------------------------------------- /app/v2ex.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func V2ex() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | url := "https://www.v2ex.com" 17 | resp, err := client.Get(url) 18 | if err != nil { 19 | return nil, fmt.Errorf("http.Get error: %w", err) 20 | } 21 | defer resp.Body.Close() 22 | // 检查状态码 23 | if resp.StatusCode != http.StatusOK { 24 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 25 | } 26 | pageBytes, err := io.ReadAll(resp.Body) 27 | if err != nil { 28 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 29 | } 30 | pattern := `\s*(.*?)<\/a>\s*<\/span>` 31 | matched := utils.ExtractMatches(string(pageBytes), pattern) 32 | // 检查是否匹配到数据 33 | if len(matched) == 0 { 34 | return map[string]interface{}{ 35 | "code": 500, 36 | "message": "未匹配到数据,可能页面结构已变更", 37 | "icon": "https://www.v2ex.com/static/img/icon_rayps_64.png", 38 | "obj": []map[string]interface{}{}, 39 | }, nil 40 | } 41 | 42 | var obj []map[string]interface{} 43 | for index, item := range matched { 44 | result := make(map[string]interface{}) 45 | result["index"] = index + 1 46 | result["title"] = item[2] 47 | result["url"] = url + item[1] 48 | obj = append(obj, result) 49 | } 50 | api := map[string]interface{}{ 51 | "code": 200, 52 | "message": "V2EX", 53 | "icon": "https://www.v2ex.com/static/img/icon_rayps_64.png", // 64 x 64 54 | "obj": obj, 55 | } 56 | return api, nil 57 | } 58 | -------------------------------------------------------------------------------- /app/xinjingbao.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Xinjingbao() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | 17 | url := "https://www.bjnews.com.cn/" 18 | resp, err := client.Get(url) 19 | if err != nil { 20 | return nil, fmt.Errorf("http.Get error: %w", err) 21 | } 22 | defer resp.Body.Close() 23 | // 检查状态码 24 | if resp.StatusCode != http.StatusOK { 25 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 26 | } 27 | pageBytes, err := io.ReadAll(resp.Body) 28 | if err != nil { 29 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 30 | } 31 | pattern := `

    \s*]*>\s*]*>\d*\s*(.*?)\s*

    [\s\S]*?(.*?)
    ` 32 | matched := utils.ExtractMatches(string(pageBytes), pattern) 33 | // 检查是否匹配到数据 34 | if len(matched) == 0 { 35 | return map[string]interface{}{ 36 | "code": 500, 37 | "message": "未匹配到数据,可能页面结构已变更", 38 | "icon": "https://www.bjnews.com.cn/favicon.ico", 39 | "obj": []map[string]interface{}{}, 40 | }, nil 41 | } 42 | 43 | var obj []map[string]interface{} 44 | for index, item := range matched { 45 | obj = append(obj, map[string]interface{}{ 46 | "index": index + 1, 47 | "title": item[2], 48 | "url": item[1], 49 | "hotValue": item[3], 50 | }) 51 | } 52 | api := map[string]interface{}{ 53 | "code": 200, 54 | "message": "新京报", 55 | "icon": "https://www.bjnews.com.cn/favicon.ico", // 20 x 20 56 | "obj": obj, 57 | } 58 | return api, nil 59 | } 60 | -------------------------------------------------------------------------------- /app/lishipin.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Lishipin() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | 17 | url := "https://www.pearvideo.com/popular" 18 | resp, err := client.Get(url) 19 | if err != nil { 20 | return nil, fmt.Errorf("http.Get error: %w", err) 21 | } 22 | defer resp.Body.Close() 23 | // 检查状态码 24 | if resp.StatusCode != http.StatusOK { 25 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 26 | } 27 | 28 | pageBytes, err := io.ReadAll(resp.Body) 29 | if err != nil { 30 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 31 | } 32 | 33 | pattern := `\s*(.*?)\s*(.*?)

    ` 34 | matched := utils.ExtractMatches(string(pageBytes), pattern) 35 | // 检查是否匹配到数据 36 | if len(matched) == 0 { 37 | return map[string]interface{}{ 38 | "code": 500, 39 | "message": "未匹配到数据,可能页面结构已变更", 40 | "icon": "https://page.pearvideo.com/webres/img/logo.png", 41 | "obj": []map[string]interface{}{}, 42 | }, nil 43 | } 44 | var obj []map[string]interface{} 45 | for index, item := range matched { 46 | obj = append(obj, map[string]interface{}{ 47 | "index": index + 1, 48 | "title": item[2], 49 | "url": "https://www.pearvideo.com/" + fmt.Sprint(item[1]), 50 | "desc": item[3], 51 | }) 52 | } 53 | api := map[string]interface{}{ 54 | "code": 200, 55 | "message": "梨视频", 56 | "icon": "https://page.pearvideo.com/webres/img/logo.png", // 76 x 98 57 | "obj": obj, 58 | } 59 | return api, nil 60 | } 61 | -------------------------------------------------------------------------------- /all/all.go: -------------------------------------------------------------------------------- 1 | package all 2 | 3 | import ( 4 | "api/app" 5 | "fmt" 6 | "sync" 7 | ) 8 | 9 | func All() map[string]interface{} { 10 | // 定义一个函数列表,方便循环调用 11 | funcs := map[string]func() (map[string]interface{}, error){ 12 | "360搜索": app.Search360, 13 | "哔哩哔哩": app.Bilibili, 14 | "AcFun": app.Acfun, 15 | "CSDN": app.CSDN, 16 | "懂球帝": app.Dongqiudi, 17 | "豆瓣": app.Douban, 18 | "抖音": app.Douyin, 19 | "GitHub": app.Github, 20 | "国家地理": app.Guojiadili, 21 | "历史上的今天": app.History, 22 | "虎扑": app.Hupu, 23 | "IT之家": app.Ithome, 24 | "梨视频": app.Lishipin, 25 | "澎湃新闻": app.Pengpai, 26 | "腾讯新闻": app.Qqnews, 27 | "少数派": app.Shaoshupai, 28 | "搜狗": app.Sougou, 29 | "今日头条": app.Toutiao, 30 | "V2EX": app.V2ex, 31 | "网易新闻": app.WangyiNews, 32 | "微博": app.WeiboHot, 33 | "新京报": app.Xinjingbao, 34 | "知乎": app.Zhihu, 35 | "夸克": app.Quark, 36 | "搜狐": app.Souhu, 37 | "百度": app.Baidu, 38 | "人民网": app.Renminwang, 39 | "南方周末": app.Nanfangzhoumo, 40 | "360doc": app.Doc360, 41 | "CCTV新闻": app.CCTV, 42 | } 43 | 44 | allResult := make(map[string]interface{}) 45 | var wg sync.WaitGroup 46 | var mu sync.Mutex 47 | 48 | for key, fn := range funcs { 49 | wg.Add(1) 50 | go func(k string, f func() (map[string]interface{}, error)) { 51 | defer wg.Done() 52 | result, err := f() 53 | if err != nil { 54 | fmt.Printf("%s 请求失败: %v\n", k, err) 55 | return 56 | } 57 | 58 | if result["code"] == 200 { 59 | mu.Lock() 60 | allResult[k] = result["obj"] 61 | mu.Unlock() 62 | } 63 | }(key, fn) 64 | } 65 | 66 | wg.Wait() 67 | fmt.Println("成功获取的热搜数量:", len(allResult)) 68 | 69 | return map[string]interface{}{ 70 | "code": 200, 71 | "obj": allResult, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/guojiadili.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Guojiadili() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | 17 | url := "https://www.dili360.com/" 18 | resp, err := client.Get(url) 19 | if err != nil { 20 | return nil, fmt.Errorf("http.Get error: %w", err) 21 | } 22 | defer resp.Body.Close() 23 | 24 | pageBytes, err := io.ReadAll(resp.Body) 25 | if err != nil { 26 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 27 | } 28 | 29 | pattern := `
  • \s*\d*\s*

    (.*?)` 30 | matched := utils.ExtractMatches(string(pageBytes), pattern) 31 | 32 | // 检查是否匹配到数据 33 | if len(matched) == 0 { 34 | return map[string]interface{}{ 35 | "code": 500, 36 | "message": "未匹配到数据,可能页面结构已变更", 37 | "icon": "https://www.dili360.com/favicon.ico", 38 | "obj": []map[string]interface{}{}, 39 | }, nil 40 | } 41 | 42 | var obj []map[string]interface{} 43 | for index, item := range matched { 44 | // 添加边界检查 45 | if len(item) >= 3 { 46 | urlPath := item[1] 47 | // 确保 URL 路径正确拼接 48 | fullURL := "http://www.dili360.com" + urlPath 49 | // 如果已经是完整 URL,不需要拼接 50 | if len(urlPath) > 4 && urlPath[:4] == "http" { 51 | fullURL = urlPath 52 | } 53 | 54 | obj = append(obj, map[string]interface{}{ 55 | "index": index + 1, 56 | "title": item[2], 57 | "url": fullURL, 58 | }) 59 | } 60 | } 61 | 62 | api := map[string]interface{}{ 63 | "code": 200, 64 | "message": "国家地理", 65 | "icon": "http://www.dili360.com/favicon.ico", // 32 x 32 66 | "obj": obj, 67 | } 68 | return api, nil 69 | } 70 | -------------------------------------------------------------------------------- /app/csdn.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "time" 10 | ) 11 | 12 | type csdbResponse struct { 13 | Data []csdnData `json:"data"` 14 | } 15 | type csdnData struct { 16 | Title string `json:"articleTitle"` 17 | URL string `json:"articleDetailUrl"` 18 | HotValue string `json:"pcHotRankScore"` 19 | } 20 | 21 | func CSDN() (map[string]interface{}, error) { 22 | // 创建自定义 Transport,跳过 TLS 验证(仅用于测试) 23 | tr := &http.Transport{ 24 | TLSClientConfig: &tls.Config{ 25 | InsecureSkipVerify: true, // 跳过证书验证 26 | }, 27 | } 28 | 29 | client := &http.Client{ 30 | Transport: tr, 31 | Timeout: 10 * time.Second, 32 | } 33 | 34 | url := "https://blog.csdn.net/phoenix/web/blog/hotRank?&pageSize=100" 35 | resp, err := client.Get(url) 36 | if err != nil { 37 | return nil, err 38 | } 39 | defer resp.Body.Close() 40 | // 检查状态码 41 | if resp.StatusCode != http.StatusOK { 42 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 43 | } 44 | pageBytes, err := io.ReadAll(resp.Body) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | var resultMap csdbResponse 50 | err = json.Unmarshal(pageBytes, &resultMap) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | data := resultMap.Data 56 | var obj []map[string]interface{} 57 | for index, item := range data { 58 | obj = append(obj, map[string]interface{}{ 59 | "index": index + 1, 60 | "title": item.Title, 61 | "url": item.URL, 62 | "hotValue": item.HotValue, 63 | }) 64 | } 65 | 66 | api := map[string]interface{}{ 67 | "code": 200, 68 | "message": "CSDN", 69 | "icon": "https://csdnimg.cn/public/favicon.ico", 70 | "obj": obj, 71 | } 72 | return api, nil 73 | } 74 | -------------------------------------------------------------------------------- /app/dongqiudi.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type dqdresponse struct { 12 | Data dqdList `json:"data"` 13 | } 14 | type dqdList struct { 15 | NewList []dqddata `json:"new_list"` 16 | } 17 | type dqddata struct { 18 | Title string `json:"title"` 19 | URL string `json:"share"` 20 | } 21 | 22 | func Dongqiudi() (map[string]interface{}, error) { 23 | // 创建带超时的 HTTP 客户端 24 | client := &http.Client{ 25 | Timeout: 10 * time.Second, 26 | } 27 | 28 | url := "https://dongqiudi.com/api/v3/archive/pc/index/getIndex" 29 | resp, err := client.Get(url) 30 | if err != nil { 31 | return nil, fmt.Errorf("http.Get error: %w", err) 32 | } 33 | defer resp.Body.Close() 34 | 35 | pageBytes, err := io.ReadAll(resp.Body) 36 | if err != nil { 37 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 38 | } 39 | 40 | var resultMap dqdresponse 41 | err = json.Unmarshal(pageBytes, &resultMap) 42 | if err != nil { 43 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 44 | } 45 | 46 | // 检查数据是否为空 47 | if len(resultMap.Data.NewList) == 0 { 48 | return map[string]interface{}{ 49 | "code": 500, 50 | "message": "API返回数据为空", 51 | "icon": "https://www.dongqiudi.com/images/dqd-logo.png", 52 | "obj": []map[string]interface{}{}, 53 | }, nil 54 | } 55 | 56 | var obj []map[string]interface{} 57 | for index, item := range resultMap.Data.NewList { 58 | obj = append(obj, map[string]interface{}{ 59 | "index": index + 1, 60 | "title": item.Title, 61 | "url": item.URL, 62 | }) 63 | } 64 | 65 | api := map[string]interface{}{ 66 | "code": 200, 67 | "message": "懂球帝", 68 | "icon": "https://www.dongqiudi.com/images/dqd-logo.png", // 800 x 206 69 | "obj": obj, 70 | } 71 | return api, nil 72 | } 73 | -------------------------------------------------------------------------------- /app/hupu.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Hupu() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | 17 | url := "https://www.hupu.com/" 18 | resp, err := client.Get(url) 19 | if err != nil { 20 | return nil, fmt.Errorf("http.Get error: %w", err) 21 | } 22 | defer resp.Body.Close() 23 | 24 | // 检查状态码 25 | if resp.StatusCode != http.StatusOK { 26 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 27 | } 28 | 29 | pageBytes, err := io.ReadAll(resp.Body) 30 | if err != nil { 31 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 32 | } 33 | 34 | pattern := `]+>\s*]+>\s*]+>\d+\s*]+>(.*?)` 35 | matches := utils.ExtractMatches(string(pageBytes), pattern) 36 | 37 | // 检查是否匹配到数据 38 | if len(matches) == 0 { 39 | return map[string]interface{}{ 40 | "code": 500, 41 | "message": "未匹配到数据,可能页面结构已变更", 42 | "icon": "https://www.hupu.com/favicon.ico", 43 | "obj": []map[string]interface{}{}, 44 | }, nil 45 | } 46 | 47 | var obj []map[string]interface{} 48 | for index, item := range matches { 49 | // 添加边界检查 50 | if len(item) >= 3 { 51 | url := item[1] 52 | title := item[2] 53 | 54 | // 确保 URL 是完整的 55 | if len(url) > 0 && url[0] == '/' { 56 | url = "https://www.hupu.com" + url 57 | } 58 | 59 | obj = append(obj, map[string]interface{}{ 60 | "index": index + 1, 61 | "title": title, 62 | "url": url, 63 | }) 64 | } 65 | } 66 | 67 | api := map[string]interface{}{ 68 | "code": 200, 69 | "message": "虎扑", 70 | "icon": "https://www.hupu.com/favicon.ico", // 32 x 32 71 | "obj": obj, 72 | } 73 | return api, nil 74 | } 75 | -------------------------------------------------------------------------------- /app/acfun.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type acfunResponse struct { 12 | RankList []acfunData `json:"rankList"` 13 | } 14 | type acfunData struct { 15 | Title string `json:"contentTitle"` 16 | URL string `json:"shareUrl"` 17 | } 18 | 19 | func Acfun() (map[string]interface{}, error) { 20 | // 创建带超时的 HTTP 客户端 21 | client := &http.Client{ 22 | Timeout: 10 * time.Second, 23 | } 24 | 25 | url := "https://www.acfun.cn/rest/pc-direct/rank/channel?channelId=&subChannelId=&rankLimit=30&rankPeriod=DAY" 26 | // 创建一个自定义请求 27 | req, err := http.NewRequest("GET", url, nil) 28 | if err != nil { 29 | return nil, fmt.Errorf("http.NewRequest error: %w", err) 30 | } 31 | 32 | // 设置 Headers 33 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 34 | 35 | resp, err := client.Do(req) 36 | if err != nil { 37 | return nil, fmt.Errorf("http.Client.Do error: %w", err) 38 | } 39 | defer resp.Body.Close() 40 | 41 | pageBytes, err := io.ReadAll(resp.Body) 42 | if err != nil { 43 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 44 | } 45 | 46 | var resultMap acfunResponse 47 | err = json.Unmarshal(pageBytes, &resultMap) 48 | if err != nil { 49 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 50 | } 51 | 52 | var obj []map[string]interface{} 53 | for index, item := range resultMap.RankList { 54 | obj = append(obj, map[string]interface{}{ 55 | "index": index + 1, 56 | "title": item.Title, 57 | "url": item.URL, 58 | }) 59 | } 60 | 61 | api := map[string]interface{}{ 62 | "code": 200, 63 | "message": "AcFun", 64 | "icon": "https://cdn.aixifan.com/ico/favicon.ico", // 32 x 32 65 | "obj": obj, 66 | } 67 | 68 | return api, nil 69 | } 70 | -------------------------------------------------------------------------------- /app/wangyinews.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | func WangyiNews() (map[string]interface{}, error) { 13 | // 创建带超时的 HTTP 客户端 14 | client := &http.Client{ 15 | Timeout: 10 * time.Second, 16 | } 17 | url := "https://news.163.com/" 18 | resp, err := client.Get(url) 19 | if err != nil { 20 | return nil, fmt.Errorf("http.Get error: %w", err) 21 | } 22 | defer resp.Body.Close() 23 | 24 | // 检查状态码 25 | if resp.StatusCode != http.StatusOK { 26 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 27 | } 28 | 29 | pageBytes, _ := io.ReadAll(resp.Body) 30 | if err != nil { 31 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 32 | } 33 | 34 | pattern := `\d*\s*]*>(.*?)\s*(\d*)` 35 | matched := utils.ExtractMatches(string(pageBytes), pattern) 36 | // 检查是否匹配到数据 37 | if len(matched) == 0 { 38 | return map[string]interface{}{ 39 | "code": 500, 40 | "message": "未匹配到数据,可能页面结构已变更", 41 | "icon": "https://news.163.com/favicon.ico", 42 | "obj": []map[string]interface{}{}, 43 | }, nil 44 | } 45 | 46 | var obj []map[string]interface{} 47 | for index, item := range matched { 48 | hot, err := strconv.ParseFloat(item[3], 64) 49 | if err != nil { 50 | // 处理转换错误,可以给默认值或者跳过该项 51 | hot = 0 52 | // 或者使用日志记录错误但不中断程序 53 | // log.Printf("parse hot value error for item %s: %v", title, err) 54 | } 55 | obj = append(obj, map[string]interface{}{ 56 | "index": index + 1, 57 | "title": item[2], 58 | "url": item[1], 59 | "hotValue": fmt.Sprintf("%.1f万", hot/10000), 60 | }) 61 | } 62 | api := map[string]interface{}{ 63 | "code": 200, 64 | "message": "网易新闻", 65 | "icon": "https://news.163.com/favicon.ico", // 16 x 16 66 | "obj": obj, 67 | } 68 | return api, nil 69 | } 70 | -------------------------------------------------------------------------------- /app/cctv.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type cctvResponse struct { 12 | Data cctvData `json:"data"` 13 | } 14 | type cctvData struct { 15 | List []cctvList `json:"list"` 16 | } 17 | 18 | type cctvList struct { 19 | Title string `json:"title"` 20 | URL string `json:"url"` 21 | } 22 | 23 | func CCTV() (map[string]interface{}, error) { 24 | // 创建带超时的 HTTP 客户端 25 | client := &http.Client{ 26 | Timeout: 10 * time.Second, 27 | } 28 | 29 | url := "https://news.cctv.com/2019/07/gaiban/cmsdatainterface/page/world_1.jsonp" 30 | resp, err := client.Get(url) 31 | if err != nil { 32 | return nil, fmt.Errorf("http.Get error: %w", err) 33 | } 34 | defer resp.Body.Close() 35 | 36 | pageBytes, err := io.ReadAll(resp.Body) 37 | if err != nil { 38 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 39 | } 40 | 41 | // 检查响应长度是否足够 42 | if len(pageBytes) <= 6 { 43 | return nil, fmt.Errorf("API返回数据长度不足") 44 | } 45 | 46 | var resultMap cctvResponse 47 | // 删除 JSONP 回调函数包裹,解析实际 JSON 数据 48 | err = json.Unmarshal(pageBytes[6:len(pageBytes)-1], &resultMap) 49 | if err != nil { 50 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 51 | } 52 | 53 | // 检查数据是否为空 54 | if len(resultMap.Data.List) == 0 { 55 | return map[string]interface{}{ 56 | "code": 500, 57 | "message": "API返回数据为空", 58 | "icon": "https://news.cctv.com/favicon.ico", 59 | "obj": []map[string]interface{}{}, 60 | }, nil 61 | } 62 | 63 | var obj []map[string]interface{} 64 | for index, item := range resultMap.Data.List { 65 | obj = append(obj, map[string]interface{}{ 66 | "index": index + 1, 67 | "title": item.Title, 68 | "url": item.URL, 69 | }) 70 | } 71 | 72 | api := map[string]interface{}{ 73 | "code": 200, 74 | "message": "CCTV新闻", 75 | "icon": "https://news.cctv.com/favicon.ico", // 16 x 16 76 | "obj": obj, 77 | } 78 | return api, nil 79 | } 80 | -------------------------------------------------------------------------------- /app/360search.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | type search360Item struct { 13 | LongTitle string `json:"long_title"` 14 | Title string `json:"title"` 15 | Score string `json:"score"` 16 | Rank string `json:"rank"` 17 | } 18 | 19 | func Search360() (map[string]interface{}, error) { 20 | url := "https://ranks.hao.360.com/mbsug-api/hotnewsquery?type=news&realhot_limit=50" 21 | client := &http.Client{ 22 | Timeout: 10 * time.Second, 23 | } 24 | resp, err := client.Get(url) 25 | if err != nil { 26 | return nil, fmt.Errorf("http.Get error: %w", err) 27 | } 28 | defer resp.Body.Close() 29 | 30 | // 2.读取页面内容 31 | pageBytes, err := io.ReadAll(resp.Body) 32 | if err != nil { 33 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 34 | } 35 | 36 | var resultSlice []search360Item 37 | err = json.Unmarshal(pageBytes, &resultSlice) 38 | if err != nil { 39 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 40 | } 41 | 42 | var obj []map[string]interface{} 43 | for _, item := range resultSlice { 44 | title := item.Title 45 | if item.LongTitle != "" { 46 | title = item.LongTitle 47 | } 48 | 49 | hot, err := strconv.ParseFloat(item.Score, 64) 50 | if err != nil { 51 | // 处理转换错误,可以给默认值或者跳过该项 52 | hot = 0 53 | // 或者使用日志记录错误但不中断程序 54 | // log.Printf("parse hot value error for item %s: %v", title, err) 55 | } 56 | 57 | obj = append(obj, map[string]interface{}{ 58 | "index": item.Rank, 59 | "title": title, 60 | "hotValue": fmt.Sprintf("%.1f万", hot/10000), 61 | "url": "https://www.so.com/s?q=" + title, 62 | }) 63 | } 64 | 65 | api := map[string]interface{}{ 66 | "code": 200, 67 | "message": "360搜索", 68 | "icon": "https://ss.360tres.com/static/121a1737750aa53d.ico", 69 | "obj": obj, 70 | } 71 | return api, nil 72 | } 73 | -------------------------------------------------------------------------------- /app/shaoshupai.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type sspResponse struct { 12 | Data []sspData `json:"data"` 13 | } 14 | type sspData struct { 15 | Title string `json:"title"` 16 | ID int `json:"id"` 17 | } 18 | 19 | func Shaoshupai() (map[string]interface{}, error) { 20 | // 创建带超时的 HTTP 客户端 21 | client := &http.Client{ 22 | Timeout: 10 * time.Second, 23 | } 24 | url := "https://sspai.com/api/v1/article/tag/page/get?limit=100000&tag=%E7%83%AD%E9%97%A8%E6%96%87%E7%AB%A0" 25 | resp, err := client.Get(url) 26 | if err != nil { 27 | return nil, fmt.Errorf("http.Get error: %w", err) 28 | } 29 | defer resp.Body.Close() 30 | // 检查状态码 31 | if resp.StatusCode != http.StatusOK { 32 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 33 | } 34 | pageBytes, err := io.ReadAll(resp.Body) 35 | if err != nil { 36 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 37 | } 38 | 39 | var resultMap sspResponse 40 | err = json.Unmarshal(pageBytes, &resultMap) 41 | if err != nil { 42 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 43 | } 44 | // 检查数据是否为空 45 | if len(resultMap.Data) == 0 { 46 | return map[string]interface{}{ 47 | "code": 500, 48 | "message": "API返回数据为空", 49 | "icon": "https://cdn-static.sspai.com/favicon/sspai.ico", 50 | "obj": []map[string]interface{}{}, 51 | }, nil 52 | } 53 | 54 | data := resultMap.Data 55 | var obj []map[string]interface{} 56 | for index, item := range data { 57 | obj = append(obj, map[string]interface{}{ 58 | "index": index + 1, 59 | "title": item.Title, 60 | "url": "https://sspai.com/post/" + fmt.Sprint(item.ID), 61 | }) 62 | } 63 | api := map[string]interface{}{ 64 | "code": 200, 65 | "message": "少数派", 66 | "icon": "https://cdn-static.sspai.com/favicon/sspai.ico", // 64 x 64 67 | "obj": obj, 68 | } 69 | return api, nil 70 | } 71 | -------------------------------------------------------------------------------- /app/nanfangzhoumo.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | type nfResponse struct { 13 | NfzmData nfData `json:"data"` 14 | } 15 | type nfData struct { 16 | HotContents []contents `json:"hot_contents"` 17 | } 18 | type contents struct { 19 | Title string `json:"subject"` 20 | ID float64 `json:"id"` 21 | } 22 | 23 | func Nanfangzhoumo() (map[string]interface{}, error) { 24 | // 创建带超时的 HTTP 客户端 25 | client := &http.Client{ 26 | Timeout: 10 * time.Second, 27 | } 28 | 29 | url := "https://www.infzm.com/hot_contents?format=json" 30 | resp, err := client.Get(url) 31 | if err != nil { 32 | return nil, fmt.Errorf("http.Get error: %w", err) 33 | } 34 | defer resp.Body.Close() 35 | // 检查状态码 36 | if resp.StatusCode != http.StatusOK { 37 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 38 | } 39 | 40 | pageBytes, err := io.ReadAll(resp.Body) 41 | if err != nil { 42 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 43 | } 44 | var resultMap nfResponse 45 | err = json.Unmarshal(pageBytes, &resultMap) 46 | if err != nil { 47 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 48 | } 49 | wordList := resultMap.NfzmData.HotContents 50 | // 检查数据是否为空 51 | if len(wordList) == 0 { 52 | return map[string]interface{}{ 53 | "code": 500, 54 | "message": "API返回数据为空", 55 | "icon": "https://www.infzm.com/favicon.ico", 56 | "obj": []map[string]interface{}{}, 57 | }, nil 58 | } 59 | var obj []map[string]interface{} 60 | for index, item := range wordList { 61 | obj = append(obj, map[string]interface{}{ 62 | "index": index + 1, 63 | "title": item.Title, 64 | "url": "https://www.infzm.com/contents/" + strconv.FormatFloat(item.ID, 'f', -1, 64), 65 | }) 66 | } 67 | api := map[string]interface{}{ 68 | "code": 200, 69 | "message": "南方周末", 70 | "icon": "https://www.infzm.com/favicon.ico", // 32 x 32 71 | "obj": obj, 72 | } 73 | return api, nil 74 | } 75 | -------------------------------------------------------------------------------- /app/github.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | func Github() (map[string]interface{}, error) { 13 | // 创建带超时的 HTTP 客户端 14 | client := &http.Client{ 15 | Timeout: 10 * time.Second, 16 | } 17 | 18 | url := "https://github.com/trending" 19 | resp, err := client.Get(url) 20 | if err != nil { 21 | return nil, fmt.Errorf("http.Get error: %w", err) 22 | } 23 | defer resp.Body.Close() 24 | 25 | pageBytes, err := io.ReadAll(resp.Body) 26 | if err != nil { 27 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 28 | } 29 | 30 | pattern := `\s*([^<]+)\s*<\/span>\s*([^<]+)<\/a>\s*<\/h2>\s*\s*([^<]+)\s*<\/p>` 31 | // 注意:这里假设 utils.ExtractMatches 没有改变,如果它也返回 error,需要修改 32 | matched := utils.ExtractMatches(string(pageBytes), pattern) 33 | 34 | // 检查是否匹配到数据 35 | if len(matched) == 0 { 36 | return map[string]interface{}{ 37 | "code": 500, 38 | "message": "未匹配到 trending 数据,可能页面结构已变更", 39 | "icon": "https://github.githubassets.com/favicons/favicon.png", 40 | "obj": []map[string]interface{}{}, 41 | }, nil 42 | } 43 | 44 | var obj []map[string]interface{} 45 | for index, item := range matched { 46 | // 添加边界检查 47 | if len(item) >= 4 { 48 | // 清理用户名和仓库名 49 | user := strings.TrimSpace(item[1]) 50 | repo := strings.TrimSpace(item[2]) 51 | // 合并并移除所有空格 52 | trimed := strings.ReplaceAll(user+repo, " ", "") 53 | 54 | desc := "" 55 | if len(item) >= 4 { 56 | desc = strings.TrimSpace(item[3]) 57 | } 58 | 59 | obj = append(obj, map[string]interface{}{ 60 | "index": index + 1, 61 | "title": trimed, 62 | "desc": desc, 63 | "url": "https://github.com/" + trimed, 64 | }) 65 | } 66 | } 67 | 68 | api := map[string]interface{}{ 69 | "code": 200, 70 | "message": "GitHub", 71 | "icon": "https://github.githubassets.com/favicons/favicon.png", // 32 x 32 72 | "obj": obj, 73 | } 74 | return api, nil 75 | } 76 | -------------------------------------------------------------------------------- /app/ithome.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Ithome() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | 17 | url := "https://m.ithome.com/rankm/" 18 | resp, err := client.Get(url) 19 | if err != nil { 20 | return nil, fmt.Errorf("http.Get error: %w", err) 21 | } 22 | defer resp.Body.Close() 23 | 24 | // 检查状态码 25 | if resp.StatusCode != http.StatusOK { 26 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 27 | } 28 | 29 | pageBytes, err := io.ReadAll(resp.Body) 30 | if err != nil { 31 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 32 | } 33 | 34 | pattern := `]*>[\s\S]*?

    ([^<]+)

    ` 35 | matches := utils.ExtractMatches(string(pageBytes), pattern) 36 | 37 | // 检查是否匹配到数据 38 | if len(matches) == 0 { 39 | return map[string]interface{}{ 40 | "code": 500, 41 | "message": "未匹配到数据,可能页面结构已变更", 42 | "icon": "https://www.ithome.com/favicon.ico", 43 | "obj": []map[string]interface{}{}, 44 | }, nil 45 | } 46 | 47 | // 确定要取多少条数据(最多12条) 48 | count := len(matches) 49 | if count > 12 { 50 | count = 12 51 | } 52 | 53 | var obj []map[string]interface{} 54 | for index := 0; index < count; index++ { 55 | item := matches[index] 56 | // 添加边界检查 57 | if len(item) >= 3 { 58 | obj = append(obj, map[string]interface{}{ 59 | "index": index + 1, 60 | "title": item[2], 61 | "url": item[1], 62 | }) 63 | } 64 | } 65 | 66 | // 确保有有效数据 67 | if len(obj) == 0 { 68 | return map[string]interface{}{ 69 | "code": 500, 70 | "message": "处理后的数据为空", 71 | "icon": "https://www.ithome.com/favicon.ico", 72 | "obj": []map[string]interface{}{}, 73 | }, nil 74 | } 75 | 76 | api := map[string]interface{}{ 77 | "code": 200, 78 | "message": "IT之家", 79 | "icon": "https://www.ithome.com/favicon.ico", // 32 x 32 80 | "obj": obj, 81 | } 82 | return api, nil 83 | } 84 | -------------------------------------------------------------------------------- /app/douyin.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/url" 9 | "time" 10 | ) 11 | 12 | type Douyinresponse struct { 13 | WordList []Douyindata `json:"word_list"` 14 | } 15 | 16 | type Douyindata struct { 17 | Title string `json:"word"` 18 | HotVaule float64 `json:"hot_value"` 19 | } 20 | 21 | func Douyin() (map[string]interface{}, error) { 22 | // 创建带超时的 HTTP 客户端 23 | client := &http.Client{ 24 | Timeout: 10 * time.Second, 25 | } 26 | 27 | urlStr := "https://www.iesdouyin.com/web/api/v2/hotsearch/billboard/word/" 28 | resp, err := client.Get(urlStr) 29 | if err != nil { 30 | return nil, fmt.Errorf("http.Get error: %w", err) 31 | } 32 | defer resp.Body.Close() 33 | 34 | pageBytes, err := io.ReadAll(resp.Body) 35 | if err != nil { 36 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 37 | } 38 | 39 | var resultMap Douyinresponse 40 | err = json.Unmarshal(pageBytes, &resultMap) 41 | if err != nil { 42 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 43 | } 44 | 45 | // 检查数据是否为空 46 | if len(resultMap.WordList) == 0 { 47 | return map[string]interface{}{ 48 | "code": 500, 49 | "message": "API返回数据为空", 50 | "icon": "https://lf1-cdn-tos.bytegoofy.com/goofy/ies/douyin_web/public/favicon.ico", 51 | "obj": []map[string]interface{}{}, 52 | }, nil 53 | } 54 | 55 | var obj []map[string]interface{} 56 | for index, item := range resultMap.WordList { 57 | // URL 编码标题,确保特殊字符正确处理 58 | encodedTitle := url.QueryEscape(item.Title) 59 | 60 | hotValue := "" 61 | if item.HotVaule > 0 { 62 | hotValue = fmt.Sprintf("%.2f万", item.HotVaule/10000) 63 | } 64 | 65 | obj = append(obj, map[string]interface{}{ 66 | "index": index + 1, 67 | "title": item.Title, 68 | "url": "https://www.douyin.com/search/" + encodedTitle, 69 | "hotValue": hotValue, 70 | }) 71 | } 72 | 73 | api := map[string]interface{}{ 74 | "code": 200, 75 | "message": "抖音", 76 | "icon": "https://lf1-cdn-tos.bytegoofy.com/goofy/ies/douyin_web/public/favicon.ico", // 32 x 32 77 | "obj": obj, 78 | } 79 | return api, nil 80 | } 81 | -------------------------------------------------------------------------------- /app/bilibili.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type bilibiliResponse struct { 12 | Data bilibiliList `json:"data"` 13 | } 14 | 15 | type bilibiliList struct { 16 | List []bilibiliData `json:"list"` 17 | } 18 | type bilibiliData struct { 19 | Title string `json:"title"` 20 | Bvid string `json:"bvid"` 21 | } 22 | 23 | func Bilibili() (map[string]interface{}, error) { 24 | // 创建带超时的 HTTP 客户端 25 | client := &http.Client{ 26 | Timeout: 10 * time.Second, 27 | } 28 | 29 | url := "https://api.bilibili.com/x/web-interface/ranking/v2?rid=0&type=all" 30 | 31 | req, err := http.NewRequest("GET", url, nil) 32 | if err != nil { 33 | return nil, fmt.Errorf("http.NewRequest error: %w", err) 34 | } 35 | 36 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 37 | 38 | resp, err := client.Do(req) 39 | if err != nil { 40 | return nil, fmt.Errorf("http.Client.Do error: %w", err) 41 | } 42 | defer resp.Body.Close() 43 | 44 | pageBytes, err := io.ReadAll(resp.Body) 45 | if err != nil { 46 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 47 | } 48 | 49 | var resultMap bilibiliResponse 50 | err = json.Unmarshal(pageBytes, &resultMap) 51 | if err != nil { 52 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 53 | } 54 | 55 | // 检查数据是否为空 56 | if len(resultMap.Data.List) == 0 { 57 | return map[string]interface{}{ 58 | "code": 500, 59 | "message": "API返回数据为空或格式不正确,实际返回数据:" + fmt.Sprintf("%+v", resultMap.Data), 60 | }, nil // 这里返回 nil error,因为这是业务逻辑错误,不是程序错误 61 | } 62 | 63 | var obj []map[string]interface{} 64 | for index, item := range resultMap.Data.List { 65 | obj = append(obj, map[string]interface{}{ 66 | "index": index + 1, 67 | "title": item.Title, 68 | "url": "https://www.bilibili.com/video/" + item.Bvid, 69 | }) 70 | } 71 | 72 | api := map[string]interface{}{ 73 | "code": 200, 74 | "message": "哔哩哔哩", 75 | "icon": "https://www.bilibili.com/favicon.ico", // 32 x 32 76 | "obj": obj, 77 | } 78 | return api, nil 79 | } 80 | -------------------------------------------------------------------------------- /app/sougou.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | func Sougou() (map[string]interface{}, error) { 12 | // 创建带超时的 HTTP 客户端 13 | client := &http.Client{ 14 | Timeout: 10 * time.Second, 15 | } 16 | url := "https://www.sogou.com/web?query=%E6%90%9C%E7%8B%97%E7%83%AD%E6%90%9C" 17 | req, err := http.NewRequest("GET", url, nil) 18 | if err != nil { 19 | return nil, fmt.Errorf("http.NewRequest error: %w", err) 20 | } 21 | // 设置请求头 22 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 23 | req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") 24 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") 25 | 26 | resp, err := client.Do(req) 27 | if err != nil { 28 | return nil, fmt.Errorf("http.Client.Do error: %w", err) 29 | } 30 | defer resp.Body.Close() 31 | // 检查状态码 32 | if resp.StatusCode != http.StatusOK { 33 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 34 | } 35 | 36 | pageBytes, err := io.ReadAll(resp.Body) 37 | if err != nil { 38 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 39 | } 40 | 41 | pattern := `]*>[\s\S]*?

    \s*]*>(.*?)\s*

    [\s\S]*?\s*(.*?)` 42 | matched := utils.ExtractMatches(string(pageBytes), pattern) 43 | // 检查是否匹配到数据 44 | if len(matched) == 0 { 45 | return map[string]interface{}{ 46 | "code": 500, 47 | "message": "未匹配到数据,可能页面结构已变更", 48 | "icon": "https://www.sogou.com/favicon.ico", 49 | "obj": []map[string]interface{}{}, 50 | }, nil 51 | } 52 | var obj []map[string]interface{} 53 | for index, item := range matched { 54 | obj = append(obj, map[string]interface{}{ 55 | "index": index + 1, 56 | "title": item[2], 57 | "url": item[1], 58 | "hotValue": item[3], 59 | }) 60 | } 61 | api := map[string]interface{}{ 62 | "code": 200, 63 | "message": "搜狗", 64 | "icon": "https://www.sogou.com/favicon.ico", // 32 x 32 65 | "obj": obj, 66 | } 67 | return api, nil 68 | } 69 | -------------------------------------------------------------------------------- /app/douban.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type doubanItem struct { 12 | Score float64 `json:"score"` 13 | Name string `json:"name"` 14 | URI string `json:"uri"` 15 | } 16 | 17 | func Douban() (map[string]interface{}, error) { 18 | // 创建带超时的 HTTP 客户端 19 | client := &http.Client{ 20 | Timeout: 10 * time.Second, 21 | } 22 | 23 | url := "https://m.douban.com/rexxar/api/v2/chart/hot_search_board?count=10&start=0" 24 | 25 | req, err := http.NewRequest("GET", url, nil) 26 | if err != nil { 27 | return nil, fmt.Errorf("http.NewRequest error: %w", err) 28 | } 29 | 30 | // 设置 Headers(模拟浏览器) 31 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 32 | req.Header.Set("Referer", "https://www.douban.com/gallery/") 33 | 34 | // 发送请求 35 | resp, err := client.Do(req) 36 | if err != nil { 37 | return nil, fmt.Errorf("http.Client.Do error: %w", err) 38 | } 39 | defer resp.Body.Close() 40 | 41 | // 读取响应 42 | pageBytes, err := io.ReadAll(resp.Body) 43 | if err != nil { 44 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 45 | } 46 | 47 | var items []doubanItem 48 | err = json.Unmarshal(pageBytes, &items) 49 | if err != nil { 50 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 51 | } 52 | 53 | // 检查数据是否为空 54 | if len(items) == 0 { 55 | return map[string]interface{}{ 56 | "code": 500, 57 | "message": "API返回数据为空", 58 | "icon": "https://www.douban.com/favicon.ico", 59 | "obj": []map[string]interface{}{}, 60 | }, nil 61 | } 62 | 63 | var obj []map[string]interface{} 64 | for index, item := range items { 65 | hotValue := "" 66 | if item.Score > 0 { 67 | hotValue = fmt.Sprintf("%.2f万", item.Score/10000) 68 | } 69 | 70 | obj = append(obj, map[string]interface{}{ 71 | "index": index + 1, 72 | "title": item.Name, 73 | "url": item.URI, 74 | "hotValue": hotValue, 75 | }) 76 | } 77 | 78 | api := map[string]interface{}{ 79 | "code": 200, 80 | "message": "豆瓣", 81 | "icon": "https://www.douban.com/favicon.ico", // 32 x 32 82 | "obj": obj, 83 | } 84 | return api, nil 85 | } 86 | -------------------------------------------------------------------------------- /app/toutiao.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | type ttResponse struct { 13 | Data []ttData `json:"data"` 14 | } 15 | type ttData struct { 16 | Title string `json:"Title"` 17 | URL string `json:"Url"` 18 | HotValue string `json:"HotValue"` 19 | } 20 | 21 | func Toutiao() (map[string]interface{}, error) { 22 | // 创建带超时的 HTTP 客户端 23 | client := &http.Client{ 24 | Timeout: 10 * time.Second, 25 | } 26 | url := "https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc" 27 | resp, err := client.Get(url) 28 | if err != nil { 29 | return nil, fmt.Errorf("http.Get error: %w", err) 30 | } 31 | defer resp.Body.Close() 32 | // 检查状态码 33 | if resp.StatusCode != http.StatusOK { 34 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 35 | } 36 | pageBytes, err := io.ReadAll(resp.Body) 37 | if err != nil { 38 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 39 | } 40 | 41 | var resultMap ttResponse 42 | err = json.Unmarshal(pageBytes, &resultMap) 43 | if err != nil { 44 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 45 | } 46 | // 检查数据是否为空 47 | if len(resultMap.Data) == 0 { 48 | return map[string]interface{}{ 49 | "code": 500, 50 | "message": "API返回数据为空", 51 | "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/pipieh7nupabozups/toutiao_web_pc/tt-icon.png", 52 | "obj": []map[string]interface{}{}, 53 | }, nil 54 | } 55 | 56 | data := resultMap.Data 57 | var obj []map[string]interface{} 58 | for index, item := range data { 59 | parsedHot, err := strconv.ParseFloat(item.HotValue, 64) 60 | hot := 0.0 61 | if err != nil { 62 | // 解析失败时使用默认值 63 | hot = 0 64 | } else { 65 | hot = parsedHot 66 | } 67 | obj = append(obj, map[string]interface{}{ 68 | "index": index + 1, 69 | "title": item.Title, 70 | "url": item.URL, 71 | "hotValue": fmt.Sprintf("%.1f万", hot/10000), 72 | }) 73 | } 74 | api := map[string]interface{}{ 75 | "code": 200, 76 | "message": "今日头条", 77 | "icon": "https://lf3-static.bytednsdoc.com/obj/eden-cn/pipieh7nupabozups/toutiao_web_pc/tt-icon.png", // 144 x 144 78 | "obj": obj, 79 | } 80 | return api, nil 81 | } 82 | -------------------------------------------------------------------------------- /app/quark.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | "time" 10 | ) 11 | 12 | type quarkResponse struct { 13 | Data quarkData `json:"data"` 14 | } 15 | type quarkData struct { 16 | HotNews hotNews `json:"hotNews"` 17 | } 18 | type hotNews struct { 19 | Item []quarkItem `json:"item"` 20 | } 21 | type quarkItem struct { 22 | URL string `json:"url"` 23 | Title string `json:"title"` 24 | HotValue string `json:"hot"` 25 | } 26 | 27 | func Quark() (map[string]interface{}, error) { 28 | // 创建带超时的 HTTP 客户端 29 | client := &http.Client{ 30 | Timeout: 10 * time.Second, 31 | } 32 | 33 | url := "https://biz.quark.cn/api/trending/ranking/getNewsRanking?modules=hotNews&uc_param_str=dnfrpfbivessbtbmnilauputogpintnwmtsvcppcprsnnnchmicckpgixsnx" 34 | resp, err := client.Get(url) 35 | if err != nil { 36 | return nil, fmt.Errorf("http.Get error: %w", err) 37 | } 38 | defer resp.Body.Close() 39 | // 检查状态码 40 | if resp.StatusCode != http.StatusOK { 41 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 42 | } 43 | pageBytes, err := io.ReadAll(resp.Body) 44 | if err != nil { 45 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 46 | } 47 | var resultMap quarkResponse 48 | err = json.Unmarshal(pageBytes, &resultMap) 49 | if err != nil { 50 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 51 | } 52 | // 检查数据是否为空 53 | if len(resultMap.Data.HotNews.Item) == 0 { 54 | return map[string]interface{}{ 55 | "code": 500, 56 | "message": "API返回数据为空", 57 | "icon": "https://gw.alicdn.com/imgextra/i3/O1CN018r2tKf28YP7ev0fPF_!!6000000007944-2-tps-48-48.png", 58 | "obj": []map[string]interface{}{}, 59 | }, nil 60 | } 61 | data := resultMap.Data.HotNews.Item 62 | obj := make([]map[string]interface{}, 0, len(data)) 63 | 64 | for i, item := range data { 65 | hot, err := strconv.ParseFloat(item.HotValue, 64) 66 | if err != nil { 67 | hot = 0 68 | } 69 | 70 | obj = append(obj, map[string]interface{}{ 71 | "index": i + 1, 72 | "title": item.Title, 73 | "url": item.URL, 74 | "hotValue": fmt.Sprintf("%.1f万", hot/10000), 75 | }) 76 | } 77 | 78 | api := map[string]interface{}{ 79 | "code": 200, 80 | "message": "夸克", 81 | "obj": obj, 82 | "icon": "https://gw.alicdn.com/imgextra/i3/O1CN018r2tKf28YP7ev0fPF_!!6000000007944-2-tps-48-48.png", 83 | } 84 | return api, nil 85 | } 86 | -------------------------------------------------------------------------------- /app/pengpai.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type ppResponse struct { 12 | Data ppData `json:"data"` 13 | } 14 | type ppData struct { 15 | HotNews []news `json:"hotNews"` 16 | } 17 | type news struct { 18 | Title string `json:"name"` 19 | ContId string `json:"contId"` 20 | } 21 | 22 | func Pengpai() (map[string]interface{}, error) { 23 | // 创建带超时的 HTTP 客户端 24 | client := &http.Client{ 25 | Timeout: 10 * time.Second, 26 | } 27 | 28 | url := "https://cache.thepaper.cn/contentapi/wwwIndex/rightSidebar" 29 | resp, err := client.Get(url) 30 | if err != nil { 31 | return nil, fmt.Errorf("http.Get error: %w", err) 32 | } 33 | defer resp.Body.Close() 34 | // 检查状态码 35 | if resp.StatusCode != http.StatusOK { 36 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 37 | } 38 | 39 | pageBytes, err := io.ReadAll(resp.Body) 40 | if err != nil { 41 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 42 | } 43 | var resultMap ppResponse 44 | err = json.Unmarshal(pageBytes, &resultMap) 45 | if err != nil { 46 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 47 | } 48 | // 检查数据是否为空 49 | if len(resultMap.Data.HotNews) == 0 { 50 | return map[string]interface{}{ 51 | "code": 500, 52 | "message": "API返回数据为空", 53 | "icon": "https://www.thepaper.cn/favicon.ico", 54 | "obj": []map[string]interface{}{}, 55 | }, nil 56 | } 57 | 58 | data := resultMap.Data.HotNews 59 | var obj []map[string]interface{} 60 | for index, item := range data { 61 | // 确保 ContId 不为空 62 | if item.ContId == "" { 63 | continue // 跳过没有 ContId 的新闻 64 | } 65 | 66 | obj = append(obj, map[string]interface{}{ 67 | "index": index + 1, 68 | "title": item.Title, 69 | "url": "https://www.thepaper.cn/newsDetail_forward_" + item.ContId, 70 | }) 71 | } 72 | 73 | // 如果所有数据都因为没有 ContId 被跳过 74 | if len(obj) == 0 { 75 | return map[string]interface{}{ 76 | "code": 500, 77 | "message": "API返回数据格式异常,缺少必要字段", 78 | "icon": "https://www.thepaper.cn/favicon.ico", 79 | "obj": []map[string]interface{}{}, 80 | }, nil 81 | } 82 | api := map[string]interface{}{ 83 | "code": 200, 84 | "message": "澎湃新闻", 85 | "icon": "https://www.thepaper.cn/favicon.ico", // 32 x 32 86 | "obj": obj, 87 | } 88 | return api, nil 89 | } 90 | -------------------------------------------------------------------------------- /app/zhihu.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type zhResponse struct { 12 | Response response `json:"recommend_queries"` 13 | } 14 | type response struct { 15 | Data []zhData `json:"queries"` 16 | } 17 | type zhData struct { 18 | Title string `json:"query"` 19 | } 20 | 21 | func Zhihu() (map[string]interface{}, error) { 22 | // 创建带超时的 HTTP 客户端 23 | client := &http.Client{ 24 | Timeout: 10 * time.Second, 25 | } 26 | 27 | urlStr := "https://www.zhihu.com/api/v4/search/recommend_query/v2" 28 | req, err := http.NewRequest("GET", urlStr, nil) 29 | if err != nil { 30 | return nil, fmt.Errorf("http.NewRequest error: %w", err) 31 | } 32 | 33 | // 设置请求头,模拟正常浏览器访问 34 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 35 | req.Header.Set("Accept", "application/json, text/plain, */*") 36 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") 37 | req.Header.Set("Referer", "https://www.zhihu.com/") 38 | 39 | resp, err := client.Do(req) 40 | if err != nil { 41 | return nil, fmt.Errorf("http.Client.Do error: %w", err) 42 | } 43 | defer resp.Body.Close() 44 | 45 | // 检查状态码 46 | if resp.StatusCode != http.StatusOK { 47 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 48 | } 49 | 50 | pageBytes, err := io.ReadAll(resp.Body) 51 | if err != nil { 52 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 53 | } 54 | 55 | var resultMap zhResponse 56 | err = json.Unmarshal(pageBytes, &resultMap) 57 | if err != nil { 58 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 59 | } 60 | // 检查数据是否为空 61 | if len(resultMap.Response.Data) == 0 { 62 | return map[string]interface{}{ 63 | "code": 500, 64 | "message": "API返回数据为空", 65 | "icon": "https://static.zhihu.com/static/favicon.ico", 66 | "obj": []map[string]interface{}{}, 67 | }, nil 68 | } 69 | 70 | data := resultMap.Response.Data 71 | var obj []map[string]interface{} 72 | for index, item := range data { 73 | obj = append(obj, map[string]interface{}{ 74 | "index": index + 1, 75 | "title": item.Title, 76 | "url": "https://www.zhihu.com/search?q=" + item.Title, 77 | }) 78 | } 79 | api := map[string]interface{}{ 80 | "code": 200, 81 | "message": "知乎", 82 | "icon": "https://static.zhihu.com/static/favicon.ico", // 32 x 32 83 | "obj": obj, 84 | } 85 | return api, nil 86 | } 87 | -------------------------------------------------------------------------------- /app/qqnews.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "time" 9 | ) 10 | 11 | type qqResponse struct { 12 | IdList []idListItem `json:"idlist"` 13 | } 14 | 15 | type idListItem struct { 16 | IdsHash string `json:"ids_hash"` 17 | NewsList []newsItem `json:"newslist"` 18 | } 19 | 20 | type newsItem struct { 21 | Title string `json:"title"` 22 | Url string `json:"url"` 23 | Time string `json:"time"` 24 | HotEvent hotEvent `json:"hotEvent"` 25 | } 26 | 27 | type hotEvent struct { 28 | HotScore float64 `json:"hotScore"` 29 | } 30 | 31 | func Qqnews() (map[string]interface{}, error) { 32 | // 创建带超时的 HTTP 客户端 33 | client := &http.Client{ 34 | Timeout: 10 * time.Second, 35 | } 36 | 37 | url := "https://r.inews.qq.com/gw/event/hot_ranking_list?page_size=51" 38 | resp, err := client.Get(url) 39 | if err != nil { 40 | return nil, fmt.Errorf("http.Get error: %w", err) 41 | } 42 | defer resp.Body.Close() 43 | // 检查状态码 44 | if resp.StatusCode != http.StatusOK { 45 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 46 | } 47 | 48 | pageBytes, err := io.ReadAll(resp.Body) 49 | if err != nil { 50 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 51 | } 52 | 53 | // 使用结构体解析响应 54 | var result qqResponse 55 | err = json.Unmarshal(pageBytes, &result) 56 | if err != nil { 57 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 58 | } 59 | 60 | // 检查数据是否为空或格式不正确 61 | if len(result.IdList) == 0 || len(result.IdList[0].NewsList) == 0 { 62 | return map[string]interface{}{ 63 | "code": 500, 64 | "message": "API返回数据为空", 65 | "icon": "https://mat1.gtimg.com/qqcdn/qqindex2021/favicon.ico", 66 | "obj": []map[string]interface{}{}, 67 | }, nil 68 | } 69 | 70 | // 获取新闻列表数据 71 | newsListData := result.IdList[0].NewsList 72 | 73 | var obj []map[string]interface{} 74 | for index, item := range newsListData { 75 | if index == 0 { 76 | continue 77 | } 78 | hot := item.HotEvent.HotScore / 10000 79 | hotValue := fmt.Sprintf("%.1f万", hot) 80 | 81 | obj = append(obj, map[string]interface{}{ 82 | "index": index, 83 | "title": item.Title, 84 | "url": item.Url, 85 | "time": item.Time, 86 | "hotValue": hotValue, 87 | }) 88 | } 89 | api := map[string]interface{}{ 90 | "code": 200, 91 | "message": "腾讯新闻", 92 | "icon": "https://mat1.gtimg.com/qqcdn/qqindex2021/favicon.ico", // 96 x 96 93 | "obj": obj, 94 | } 95 | return api, nil 96 | } 97 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "api/all" 5 | "api/app" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func main() { 11 | r := gin.Default() 12 | 13 | // 创建路由映射,使用包装函数 14 | routes := map[string]func(c *gin.Context){ 15 | "/bilibili": createHandler(app.Bilibili), 16 | "/360search": createHandler(app.Search360), 17 | "/acfun": createHandler(app.Acfun), 18 | "/csdn": createHandler(app.CSDN), 19 | "/dongqiudi": createHandler(app.Dongqiudi), 20 | "/douban": createHandler(app.Douban), 21 | "/douyin": createHandler(app.Douyin), 22 | "/github": createHandler(app.Github), 23 | "/guojiadili": createHandler(app.Guojiadili), 24 | "/history": createHandler(app.History), 25 | "/hupu": createHandler(app.Hupu), 26 | "/ithome": createHandler(app.Ithome), 27 | "/lishipin": createHandler(app.Lishipin), 28 | "/pengpai": createHandler(app.Pengpai), 29 | "/qqnews": createHandler(app.Qqnews), 30 | "/shaoshupai": createHandler(app.Shaoshupai), 31 | "/sougou": createHandler(app.Sougou), 32 | "/toutiao": createHandler(app.Toutiao), 33 | "/v2ex": createHandler(app.V2ex), 34 | "/wangyinews": createHandler(app.WangyiNews), 35 | "/weibo": createHandler(app.WeiboHot), 36 | "/xinjingbao": createHandler(app.Xinjingbao), 37 | "/zhihu": createHandler(app.Zhihu), 38 | "/kuake": createHandler(app.Quark), 39 | "/souhu": createHandler(app.Souhu), 40 | "/baidu": createHandler(app.Baidu), 41 | "/renmin": createHandler(app.Renminwang), 42 | "/nanfang": createHandler(app.Nanfangzhoumo), 43 | "/360doc": createHandler(app.Doc360), 44 | "/cctv": createHandler(app.CCTV), 45 | "/all": allHandler, // 单独处理 all,因为它签名不同 46 | } 47 | 48 | // 注册路由 49 | for path, handler := range routes { 50 | r.GET(path, handler) 51 | } 52 | 53 | r.Run(":1111") 54 | } 55 | 56 | // 创建通用处理器函数,处理返回 error 的函数 57 | func createHandler(fn func() (map[string]interface{}, error)) gin.HandlerFunc { 58 | return func(c *gin.Context) { 59 | result, err := fn() 60 | if err != nil { 61 | // 发生错误时返回错误响应 62 | c.JSON(500, map[string]interface{}{ 63 | "code": 500, 64 | "message": "服务器内部错误: " + err.Error(), 65 | "obj": []map[string]interface{}{}, 66 | }) 67 | return 68 | } 69 | 70 | // 检查 API 返回的 code 71 | if code, ok := result["code"].(int); ok && code != 200 { 72 | // API 返回业务错误 73 | c.JSON(code, result) 74 | return 75 | } 76 | 77 | // 成功返回 78 | c.JSON(200, result) 79 | } 80 | } 81 | 82 | // allHandler 专门处理 all.All() 函数 83 | func allHandler(c *gin.Context) { 84 | result := all.All() 85 | 86 | // 检查 API 返回的 code 87 | if code, ok := result["code"].(int); ok && code != 200 { 88 | // API 返回业务错误 89 | c.JSON(code, result) 90 | return 91 | } 92 | 93 | // 成功返回 94 | c.JSON(200, result) 95 | } 96 | -------------------------------------------------------------------------------- /app/souhu.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strconv" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type souhuResponse struct { 14 | Data []newsArticles `json:"newsArticles"` 15 | } 16 | type newsArticles struct { 17 | Title string `json:"title"` 18 | URL string `json:"h5Link"` 19 | Hot string `json:"score"` 20 | } 21 | 22 | func fetchSouhuPage(page int) ([]newsArticles, error) { 23 | // 创建带超时的 HTTP 客户端 24 | client := &http.Client{ 25 | Timeout: 10 * time.Second, 26 | } 27 | 28 | url := fmt.Sprintf("https://3g.k.sohu.com/api/channel/hotchart/hotnews.go?p1=NjY2NjY2&page=%d", page) 29 | resp, err := client.Get(url) 30 | if err != nil { 31 | return nil, fmt.Errorf("fetch page %d: http.Get error: %w", page, err) 32 | } 33 | defer resp.Body.Close() 34 | 35 | // 检查状态码 36 | if resp.StatusCode != http.StatusOK { 37 | return nil, fmt.Errorf("fetch page %d: HTTP请求失败,状态码: %d", page, resp.StatusCode) 38 | } 39 | 40 | pageBytes, err := io.ReadAll(resp.Body) 41 | if err != nil { 42 | return nil, fmt.Errorf("fetch page %d: io.ReadAll error: %w", page, err) 43 | } 44 | 45 | var resultMap souhuResponse 46 | err = json.Unmarshal(pageBytes, &resultMap) 47 | if err != nil { 48 | return nil, fmt.Errorf("fetch page %d: json.Unmarshal error: %w", page, err) 49 | } 50 | 51 | return resultMap.Data, nil 52 | } 53 | 54 | func Souhu() (map[string]interface{}, error) { 55 | var wordList []newsArticles 56 | var wg sync.WaitGroup 57 | var mutex sync.Mutex 58 | var fetchErrors []error 59 | 60 | // 并发获取两个页面的数据 61 | for i := 1; i <= 2; i++ { 62 | wg.Add(1) 63 | go func(page int) { 64 | defer wg.Done() 65 | data, err := fetchSouhuPage(page) 66 | if err != nil { 67 | mutex.Lock() 68 | fetchErrors = append(fetchErrors, err) 69 | mutex.Unlock() 70 | return 71 | } 72 | mutex.Lock() 73 | wordList = append(wordList, data...) 74 | mutex.Unlock() 75 | }(i) 76 | } 77 | 78 | wg.Wait() 79 | 80 | // 如果有错误发生,检查是否至少获取到一些数据 81 | if len(fetchErrors) > 0 { 82 | // 如果完全没有获取到数据,返回错误 83 | if len(wordList) == 0 { 84 | return nil, fmt.Errorf("获取数据失败: %v", fetchErrors) 85 | } 86 | // 如果获取到部分数据,继续处理但记录错误 87 | fmt.Printf("部分页面获取失败,但继续处理已获取的数据: %v\n", fetchErrors) 88 | } 89 | // 检查数据是否为空 90 | if len(wordList) == 0 { 91 | return map[string]interface{}{ 92 | "code": 500, 93 | "message": "API返回数据为空", 94 | "icon": "https://3g.k.sohu.com/favicon.ico", 95 | "obj": []map[string]interface{}{}, 96 | }, nil 97 | } 98 | var obj []map[string]interface{} 99 | for index, item := range wordList { 100 | hotValue, err := strconv.ParseFloat(item.Hot, 64) 101 | if err != nil { 102 | hotValue = 0 // 如果解析失败,设置为0 103 | } 104 | obj = append(obj, map[string]interface{}{ 105 | "index": index + 1, 106 | "title": item.Title, 107 | "url": item.URL, 108 | "hotValue": fmt.Sprintf("%.2f万", hotValue), 109 | }) 110 | } 111 | 112 | api := map[string]interface{}{ 113 | "code": 200, 114 | "message": "搜狐新闻", 115 | "icon": "https://3g.k.sohu.com/favicon.ico", // 48 x 48 116 | "obj": obj, 117 | } 118 | return api, nil 119 | } 120 | -------------------------------------------------------------------------------- /app/history.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "strings" 9 | "time" 10 | 11 | "golang.org/x/net/html" 12 | ) 13 | 14 | func stripHTML(htmlString string) string { 15 | // 使用 html.Parse 解析 HTML 字符串 16 | doc, err := html.Parse(strings.NewReader(htmlString)) 17 | if err != nil { 18 | // 解析失败时返回原始字符串 19 | return htmlString 20 | } 21 | 22 | // 使用一个 buffer 保存结果 23 | var result strings.Builder 24 | 25 | // 定义一个递归函数来遍历 HTML 树 26 | var visit func(n *html.Node) 27 | visit = func(n *html.Node) { 28 | // 如果当前节点是文本节点,将文本内容追加到结果中 29 | if n.Type == html.TextNode { 30 | result.WriteString(n.Data) 31 | } 32 | // 递归处理子节点 33 | for c := n.FirstChild; c != nil; c = c.NextSibling { 34 | visit(c) 35 | } 36 | } 37 | 38 | // 调用递归函数开始遍历 HTML 树 39 | visit(doc) 40 | 41 | // 返回结果的字符串形式 42 | return result.String() 43 | } 44 | 45 | func History() (map[string]interface{}, error) { 46 | // 创建带超时的 HTTP 客户端 47 | client := &http.Client{ 48 | Timeout: 10 * time.Second, 49 | } 50 | 51 | currentTime := time.Now() 52 | month := fmt.Sprintf("%02d", currentTime.Month()) 53 | day := fmt.Sprintf("%02d", currentTime.Day()) 54 | url := "https://baike.baidu.com/cms/home/eventsOnHistory/" + month + ".json" 55 | 56 | resp, err := client.Get(url) 57 | if err != nil { 58 | return nil, fmt.Errorf("http.Get error: %w", err) 59 | } 60 | defer resp.Body.Close() 61 | 62 | pageBytes, err := io.ReadAll(resp.Body) 63 | if err != nil { 64 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 65 | } 66 | 67 | var resultMap map[string]interface{} 68 | err = json.Unmarshal(pageBytes, &resultMap) 69 | if err != nil { 70 | return nil, fmt.Errorf("json.Unmarshal error: %w", err) 71 | } 72 | 73 | // 检查数据结构 74 | monthData, ok := resultMap[month].(map[string]interface{}) 75 | if !ok { 76 | return nil, fmt.Errorf("API返回数据格式异常: 月份数据不存在") 77 | } 78 | 79 | date := month + day 80 | dateListInterface, ok := monthData[date] 81 | if !ok { 82 | return nil, fmt.Errorf("今天(%s月%s日)没有历史事件数据", month, day) 83 | } 84 | 85 | dateList, ok := dateListInterface.([]interface{}) 86 | if !ok { 87 | return nil, fmt.Errorf("API返回数据格式异常: 日期列表格式不正确") 88 | } 89 | 90 | // 检查数据是否为空 91 | if len(dateList) == 0 { 92 | return map[string]interface{}{ 93 | "code": 500, 94 | "message": "今天(" + month + "月" + day + "日)没有历史事件数据", 95 | "icon": "https://baike.baidu.com/favicon.ico", 96 | "obj": []map[string]interface{}{}, 97 | }, nil 98 | } 99 | 100 | var obj []map[string]interface{} 101 | for index, item := range dateList { 102 | itemMap, ok := item.(map[string]interface{}) 103 | if !ok { 104 | continue // 跳过格式不正确的项 105 | } 106 | 107 | // 提取标题 108 | titleInterface, ok := itemMap["title"] 109 | if !ok { 110 | continue 111 | } 112 | title, ok := titleInterface.(string) 113 | if !ok { 114 | continue 115 | } 116 | 117 | // 提取链接 118 | urlInterface, ok := itemMap["link"] 119 | urlStr := "" 120 | if ok { 121 | if link, ok := urlInterface.(string); ok { 122 | urlStr = link 123 | } 124 | } 125 | 126 | obj = append(obj, map[string]interface{}{ 127 | "index": index + 1, 128 | "title": stripHTML(title), 129 | "url": urlStr, 130 | }) 131 | } 132 | 133 | api := map[string]interface{}{ 134 | "code": 200, 135 | "message": "历史上的今天", 136 | "icon": "https://baike.baidu.com/favicon.ico", // 64 x 64 137 | "obj": obj, 138 | } 139 | return api, nil 140 | } 141 | -------------------------------------------------------------------------------- /app/weibo.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "api/utils" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "regexp" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | func WeiboHot() (map[string]interface{}, error) { 14 | // 创建带超时的 HTTP 客户端 15 | client := &http.Client{ 16 | Timeout: 10 * time.Second, 17 | } 18 | url := "https://s.weibo.com/top/summary" 19 | req, err := http.NewRequest("GET", url, nil) 20 | if err != nil { 21 | return nil, fmt.Errorf("http.NewRequest error: %w", err) 22 | } 23 | // 设置请求头 24 | req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36") 25 | req.Header.Set("Cookie", "SUB=_2AkMasdasdqadTy2Pna4Rl77p7cJZAXC") 26 | req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") 27 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") 28 | req.Header.Set("Referer", "https://s.weibo.com/") 29 | 30 | resp, err := client.Do(req) 31 | if err != nil { 32 | return nil, fmt.Errorf("http.Client.Do error: %w", err) 33 | } 34 | defer resp.Body.Close() 35 | // 检查状态码 36 | if resp.StatusCode != http.StatusOK { 37 | return nil, fmt.Errorf("HTTP请求失败,状态码: %d", resp.StatusCode) 38 | } 39 | pageBytes, err := io.ReadAll(resp.Body) 40 | if err != nil { 41 | return nil, fmt.Errorf("io.ReadAll error: %w", err) 42 | } 43 | 44 | pageContent := string(pageBytes) 45 | 46 | // 方法1:使用正则表达式提取热搜数据 47 | var obj []map[string]interface{} 48 | 49 | // 正则表达式匹配热搜条目 50 | pattern := `]*target="_blank">([^<]+)\s*([^<]*)?` 51 | matched := utils.ExtractMatches(pageContent, pattern) 52 | // 预编译正则表达式,避免重复编译 53 | nonDigitRegexp := regexp.MustCompile(`[^\d]`) 54 | for index, item := range matched { 55 | if len(item) >= 3 { 56 | title := strings.TrimSpace(item[2]) 57 | url := "https://s.weibo.com" + item[1] 58 | hotValue := "" 59 | if len(item) >= 4 { 60 | hotValue = strings.TrimSpace(item[3]) 61 | } 62 | 63 | // 清理热度值中的非数字字符 64 | if hotValue != "" { 65 | hotValue = strings.TrimSpace(nonDigitRegexp.ReplaceAllString(hotValue, "")) 66 | } 67 | 68 | obj = append(obj, map[string]interface{}{ 69 | "index": index + 1, 70 | "title": title, 71 | "url": url, 72 | "hotValue": hotValue, 73 | }) 74 | } 75 | } 76 | 77 | // 如果正则匹配失败,尝试备用方法 78 | if len(obj) == 0 { 79 | obj = extractWeiboHotSearchFallback(pageContent) 80 | } 81 | // 检查是否获取到数据 82 | if len(obj) == 0 { 83 | return map[string]interface{}{ 84 | "code": 500, 85 | "message": "无法提取热搜数据,页面结构可能已变更", 86 | "icon": "https://weibo.com/favicon.ico", 87 | "obj": []map[string]interface{}{}, 88 | }, nil 89 | } 90 | api := map[string]interface{}{ 91 | "code": 200, 92 | "message": "微博热搜", 93 | "icon": "https://weibo.com/favicon.ico", 94 | "obj": obj, 95 | } 96 | return api, nil 97 | } 98 | 99 | // 备用提取方法 100 | func extractWeiboHotSearchFallback(content string) []map[string]interface{} { 101 | var obj []map[string]interface{} 102 | 103 | // 尝试匹配更简单的模式 104 | patterns := []string{ 105 | `]*>([^<]+)`, 106 | `class="td-02".*?]*>([^<]+)`, 107 | } 108 | 109 | for _, pattern := range patterns { 110 | matched := utils.ExtractMatches(content, pattern) 111 | for index, item := range matched { 112 | if len(item) >= 3 { 113 | title := strings.TrimSpace(item[2]) 114 | url := "https://s.weibo.com" + item[1] 115 | 116 | obj = append(obj, map[string]interface{}{ 117 | "index": index + 1, 118 | "title": title, 119 | "url": url, 120 | "hotValue": "", // 备用方法可能无法获取热度值 121 | }) 122 | } 123 | } 124 | if len(obj) > 0 { 125 | break 126 | } 127 | } 128 | 129 | return obj 130 | } 131 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q= 2 | github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I= 3 | github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= 4 | github.com/bytedance/sonic/loader v0.2.2 h1:jxAJuN9fOot/cyz5Q6dUuMJF5OqQ6+5GfA8FjjQ0R4o= 5 | github.com/bytedance/sonic/loader v0.2.2/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 6 | github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= 7 | github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= 8 | github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= 9 | github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= 13 | github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= 14 | github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= 15 | github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= 16 | github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= 17 | github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 18 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 19 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 20 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 21 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 22 | github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= 23 | github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= 24 | github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= 25 | github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 26 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 27 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 28 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 29 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 30 | github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= 31 | github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= 32 | github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 33 | github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= 34 | github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 35 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 36 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 37 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 39 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 40 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 41 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 42 | github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= 43 | github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= 44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 45 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 46 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 47 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 48 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 49 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 50 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 51 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 52 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 53 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 54 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 55 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 56 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 57 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 58 | github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= 59 | github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 60 | golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA= 61 | golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= 62 | golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= 63 | golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 64 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 65 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 66 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 67 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 68 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 69 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 70 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 71 | google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= 72 | google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 73 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 75 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 76 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 77 | nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= 78 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 79 | --------------------------------------------------------------------------------