├── .gitignore ├── go.mod ├── main.go ├── lanzou ├── request.go ├── lanzou_test.go └── lanzou.go ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | go-lanzou* -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/iuroc/go-lanzou 2 | 3 | go 1.22.5 -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/iuroc/go-lanzou/lanzou" 6 | "strings" 7 | ) 8 | 9 | func main() { 10 | fmt.Printf("[ https://github.com/iuroc/go-lanzou ]\n\n") 11 | for { 12 | func() { 13 | defer func() { 14 | if err := recover(); err != nil { 15 | fmt.Printf( 16 | "❌ [main] 解析失败,原因是:%s\n\n%s\n\n", 17 | err.(error).Error(), 18 | strings.Repeat("-", 50), 19 | ) 20 | } 21 | }() 22 | fmt.Print("👉 请输入蓝奏云文件分享链接:") 23 | var shareURL string 24 | fmt.Scan(&shareURL) 25 | downloadInfo, err := lanzou.GetDownloadInfo(shareURL, "", true) 26 | if err != nil { 27 | panic(err) 28 | } 29 | fmt.Printf( 30 | "🍉 文件直链解析成功\n%s\n\n%s\n\n", 31 | downloadInfo.URL, 32 | strings.Repeat("-", 50), 33 | ) 34 | }() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lanzou/request.go: -------------------------------------------------------------------------------- 1 | package lanzou 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | ) 7 | 8 | type RequestConfig struct { 9 | Method string 10 | URL string 11 | Body io.Reader 12 | Headers map[string]string 13 | } 14 | 15 | func SendRequest(config RequestConfig) (string, error) { 16 | if config.Method == "" { 17 | config.Method = "GET" 18 | } 19 | request, err := http.NewRequest(config.Method, config.URL, config.Body) 20 | if err != nil { 21 | return "", err 22 | } 23 | for key, value := range config.Headers { 24 | request.Header.Set(key, value) 25 | } 26 | client := &http.Client{} 27 | response, err := client.Do(request) 28 | if err != nil { 29 | return "", err 30 | } 31 | defer response.Body.Close() 32 | body, err := io.ReadAll(response.Body) 33 | if err != nil { 34 | return "", err 35 | } 36 | return string(body), nil 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 欧阳鹏 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-lanzou 2 | 3 | Go 语言实现的蓝奏云直链解析程序。 4 | 5 | ## 已实现功能 6 | 7 | - 获取单个文件(可带密码)的直链 8 | - 获取文件夹(可带密码)内最新一个文件的直链 9 | - 获取文件夹(可带密码)内任意页码的文件列表 10 | 11 | ## 快速开始 12 | 13 | ```shell 14 | # 运行项目 15 | go run . 16 | 17 | # 生成可执行文件 18 | go build 19 | ``` 20 | 21 | 🍎 你也可以[下载可执行文件](https://github.com/iuroc/go-lanzou/releases)进行使用。 22 | 23 | ## 作为模块 24 | 25 | ```shell 26 | go get github.com/iuroc/go-lanzou 27 | ``` 28 | 29 | ```go 30 | package main 31 | 32 | import ( 33 | "fmt" 34 | "github.com/iuroc/go-lanzou/lanzou" 35 | ) 36 | 37 | func main() { 38 | shareURL := "https://www.lanzoui.com/imcSy2340ssb" 39 | downloadURL, err := lanzou.GetDownloadURL(shareURL) 40 | if err != nil { 41 | fmt.Println("解析失败") 42 | } else { 43 | fmt.Println("解析成功:" + downloadURL) 44 | } 45 | } 46 | ``` 47 | 48 | ## API 49 | 50 | ```go 51 | // 获取文件夹最新的一个文件的信息,包含直链 52 | // 53 | // urlOrId 是文件夹的分享链接或 ID 54 | // 55 | // password 是访问密码 56 | func lanzou.GetLatestFile(shareURL string, password string) (*lanzou.DownloadInfo, error) 57 | ``` 58 | 59 | ```go 60 | // 获取单个文件的信息,包含直链 61 | func lanzou.GetDownloadInfo(shareURL string, password string) (*lanzou.DownloadInfo, error) 62 | ``` 63 | 64 | ```go 65 | // 获取文件夹中指定页码的文件列表 66 | // 67 | // page 的值务必从 0 开始,每次只允许增长 1,不可以直接从 0 变为 2。 68 | // 69 | // 每次换页,务必暂停 1 秒以上。 70 | func lanzou.GetFileList(shareURL string, password string, page int) ([]FileInfo, error) 71 | ``` 72 | 73 | ## 蓝奏云分享合集 74 | 75 | [蓝奏云分享合集](https://github.com/iuroc/lanzou-collect/blob/master/V1/%E6%95%B0%E6%8D%AE%E6%BA%90/%E6%A0%A1%E9%AA%8C%E6%88%90%E5%8A%9F%E6%95%B0%E6%8D%AE%E6%BA%90.txt) 76 | -------------------------------------------------------------------------------- /lanzou/lanzou_test.go: -------------------------------------------------------------------------------- 1 | package lanzou 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | ) 8 | 9 | // 带密码文件夹:https://ww0.lanzouj.com/b03dwumkli,密码 2024 10 | // 不带密码文件夹:https://oyp.lanzoue.com/b083tujwh 11 | // 带密码文件:https://oyp.lanzoue.com/ilF46iudy0f,密码 1234 12 | // 不带密码文件:https://oyp.lanzoue.com/iSQzC0kfd5wb 13 | 14 | // 测试:获取文件夹(带密码)内最新一个文件的直链 15 | func TestGetLatestFile1(t *testing.T) { 16 | info, err := GetLatestFile("https://ww0.lanzouj.com/b03dwumkli", "2024") 17 | if err != nil { 18 | t.Error(err) 19 | } else { 20 | text, _ := json.Marshal(info) 21 | fmt.Println(string(text)) 22 | } 23 | } 24 | 25 | // 测试:获取文件夹(不带密码)内最新一个文件的直链 26 | func TestGetLatestFile2(t *testing.T) { 27 | info, err := GetLatestFile("https://oyp.lanzoue.com/b083tujwh", "") 28 | if err != nil { 29 | t.Error(err) 30 | } else { 31 | text, _ := json.Marshal(info) 32 | fmt.Println(string(text)) 33 | } 34 | } 35 | 36 | // 测试:获取单个文件(带密码)的直链 37 | func TestGetDownloadInfo1(t *testing.T) { 38 | info, err := GetDownloadInfo("https://oyp.lanzoue.com/ilF46iudy0f", "1234", false) 39 | if err != nil { 40 | t.Error(err) 41 | } else { 42 | text, _ := json.Marshal(info) 43 | fmt.Println(string(text)) 44 | } 45 | } 46 | 47 | // 测试:获取单个文件(不带密码)的直链 48 | func TestGetDownloadInfo2(t *testing.T) { 49 | info, err := GetDownloadInfo("https://oyp.lanzoue.com/iSQzC0kfd5wb", "", false) 50 | if err != nil { 51 | t.Error(err) 52 | } else { 53 | text, _ := json.Marshal(info) 54 | fmt.Println(string(text)) 55 | } 56 | } 57 | 58 | // 测试:获取文件夹(带密码)内任意页码的文件列表 59 | func TestGetFileList1(t *testing.T) { 60 | info, err := GetFileList("https://ww0.lanzouj.com/b03dwumkli", "2024", 0) 61 | if err != nil { 62 | t.Error(err) 63 | } else { 64 | text, _ := json.Marshal(info) 65 | fmt.Println(string(text)) 66 | } 67 | } 68 | 69 | // 测试:获取文件夹(不带密码)内任意页码的文件列表 70 | func TestGetFileList2(t *testing.T) { 71 | info, err := GetFileList("https://oyp.lanzoue.com/b083tujwh", "", 0) 72 | if err != nil { 73 | t.Error(err) 74 | } else { 75 | text, _ := json.Marshal(info) 76 | fmt.Println(string(text)) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lanzou/lanzou.go: -------------------------------------------------------------------------------- 1 | // 蓝奏云直链解析程序 2 | // 3 | // 已实现功能: 4 | // 5 | // 1. 获取单个文件(可带密码)的直链 6 | // 7 | // 2. 获取文件夹(可带密码)内最新一个文件的直链 8 | // 9 | // 3. 获取文件夹(可带密码)内任意页码的文件列表 10 | // 11 | // 示例: 12 | // 13 | // package main 14 | // 15 | // import ( 16 | // "fmt" 17 | // "github.com/iuroc/go-lanzou/lanzou" 18 | // ) 19 | // 20 | // func main() { 21 | // shareURL := "https://www.lanzoui.com/imcSy2340ssb" 22 | // downloadURL, err := lanzou.GetDownloadURL(shareURL) 23 | // if err != nil { 24 | // fmt.Println("解析失败") 25 | // } else { 26 | // fmt.Println("解析成功:" + downloadURL) 27 | // } 28 | // } 29 | package lanzou 30 | 31 | import ( 32 | "encoding/json" 33 | "errors" 34 | "fmt" 35 | "net/url" 36 | "regexp" 37 | "strconv" 38 | "strings" 39 | ) 40 | 41 | // 蓝奏云分享链接的开头部分,以域名结尾 42 | const baseURL string = "https://iuroc.lanzoue.com" 43 | 44 | // 获取单个文件的信息,包含直链。 45 | // - shareURL: 蓝奏云的分享链接或标识 46 | // - password: 访问密码 47 | // - cli: 是否启用终端交互模式,该模式下如果 password 为空,将在终端提示用户输入密码 48 | // 49 | // 示例: 50 | // 51 | // GetDownloadInfo("https://oyp.lanzoue.com/ilF46iudy0f", "1234", false) 52 | // GetDownloadInfo("https://oyp.lanzoue.com/iSQzC0kfd5wb", "", false) 53 | func GetDownloadInfo(shareURL string, password string, cli bool) (*DownloadInfo, error) { 54 | fileId, err := getShareId(shareURL) 55 | if err != nil { 56 | return nil, err 57 | } 58 | html, err := SendRequest(RequestConfig{ 59 | URL: baseURL + "/" + fileId, 60 | Headers: map[string]string{ 61 | "User-Agent": "go-lanzou", 62 | }, 63 | }) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | fileInfo, _ := getFileInfoFromHTML(html) 69 | 70 | // 判断当前分享页是否需要密码 71 | if regexp.MustCompile(`class="passwdinput"`).MatchString(html) { 72 | if password == "" && cli { 73 | fmt.Print("🔒 请输入文件访问密码:") 74 | fmt.Scan(&password) 75 | } 76 | downloadInfo, err := fetchWithPassword(html, password) 77 | if err != nil { 78 | return nil, err 79 | } 80 | downloadInfo.FileInfo.ShareId = fileId 81 | downloadInfo.FileInfo.Password = password 82 | downloadInfo.FileInfo.Name = fileInfo.Name 83 | downloadInfo.FileInfo.Size = fileInfo.Size 84 | downloadInfo.FileInfo.Date = fileInfo.Date 85 | return downloadInfo, err 86 | } 87 | 88 | iframeURLMatch := regexp.MustCompile(`src="(\/fn\?[^"]{20,})"`).FindStringSubmatch(html) 89 | if len(iframeURLMatch) == 0 { 90 | return nil, errors.New("[GetDownloadURL] 获取 iframeURL 失败") 91 | } 92 | iframeURL := baseURL + iframeURLMatch[1] 93 | downloadInfo, err := fetchIframe(iframeURL) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | downloadInfo.FileInfo.ShareId = fileId 99 | downloadInfo.FileInfo.Password = password 100 | downloadInfo.FileInfo.Name = fileInfo.Name 101 | downloadInfo.FileInfo.Size = fileInfo.Size 102 | downloadInfo.FileInfo.Date = fileInfo.Date 103 | return downloadInfo, err 104 | } 105 | 106 | // 获取页面中文件的一些信息,比如文件名、大小、时间 107 | func getFileInfoFromHTML(html string) (FileInfo, error) { 108 | fileInfo := FileInfo{} 109 | nameMatch := regexp.MustCompile(`(?:padding: 56px 0px 20px 0px;">|id="filenajax">)(.*?)`).FindStringSubmatch(html) 110 | if len(nameMatch) != 0 { 111 | fileInfo.Name = nameMatch[1] 112 | } 113 | sizeMatch := regexp.MustCompile(`(?:文件大小:|