├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── lanzou ├── lanzou.go ├── lanzou_test.go └── request.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | go-lanzou* -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/iuroc/go-lanzou 2 | 3 | go 1.22.5 -------------------------------------------------------------------------------- /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(`(?:文件大小:|
大小:)(.*?)(?:
|
)`).FindStringSubmatch(html) 114 | if len(sizeMatch) != 0 { 115 | fileInfo.Size = sizeMatch[1] 116 | } 117 | dateMatch := regexp.MustCompile(`(?:上传时间:|
)(.*?)(?:|
)`).FindStringSubmatch(html) 118 | if len(dateMatch) != 0 { 119 | fileInfo.Date = dateMatch[1] 120 | } 121 | return fileInfo, nil 122 | } 123 | 124 | // 从分享链接中提取出标识字符串 125 | func getShareId(urlOrId string) (string, error) { 126 | match := regexp.MustCompile(`^https?://.*?/([a-zA-Z0-9]+)`).FindStringSubmatch(urlOrId) 127 | if len(match) != 0 { 128 | return match[1], nil 129 | } else if regexp.MustCompile(`^[a-zA-Z0-9]+$`).MatchString(urlOrId) { 130 | return urlOrId, nil 131 | } else { 132 | return "", errors.New("[getShareId] 输入的格式错误,获取 shareId 失败") 133 | } 134 | } 135 | 136 | // 从 HTML 代码中根据 key 获取下面几种格式的 value 137 | // 138 | // 'key':123 => 获得 "123" 139 | // 140 | // 'key':value => 获得 "value" 141 | // 142 | // 'key':'str' => 获得 "str" 143 | func getValueKey(html string, key string) (string, error) { 144 | match := regexp.MustCompile(`'` + key + `':'?([^',]+)`).FindStringSubmatch(html) 145 | if len(match) == 0 { 146 | return "", errors.New("[getValueKey] 正则获取 " + key + " 失败") 147 | } 148 | return match[1], nil 149 | } 150 | 151 | // 自动获取 valueKey,然后根据 valueKey 获取值 152 | func getValue(html string, key string) (string, error) { 153 | varName, err := getValueKey(html, key) 154 | if err != nil { 155 | return "", nil 156 | } 157 | match := regexp.MustCompile(`var ` + varName + ` = '(.*?)'`).FindStringSubmatch(html) 158 | if len(match) == 0 { 159 | return "", errors.New("[getValue] 正则获取 " + varName + "失败") 160 | } 161 | return match[1], nil 162 | } 163 | 164 | func fetchIframe(iframeURL string) (*DownloadInfo, error) { 165 | html, err := SendRequest(RequestConfig{ 166 | URL: iframeURL, 167 | }) 168 | 169 | if err != nil { 170 | return nil, err 171 | } 172 | signMatch := regexp.MustCompile(`'sign':'(.*?)'`).FindStringSubmatch(html) 173 | if len(signMatch) == 0 { 174 | return nil, errors.New("[fetchIframe] 获取 sign 失败") 175 | } 176 | sign := signMatch[1] 177 | signs, err := getValue(html, "signs") 178 | if err != nil { 179 | return nil, nil 180 | } 181 | websign, err := getValue(html, "websign") 182 | if err != nil { 183 | return nil, nil 184 | } 185 | websignkey, err := getValue(html, "websignkey") 186 | if err != nil { 187 | return nil, nil 188 | } 189 | params := url.Values{} 190 | params.Set("action", "downprocess") 191 | params.Set("signs", signs) 192 | params.Set("websign", websign) 193 | params.Set("websignkey", websignkey) 194 | params.Set("ves", "1") 195 | params.Set("sign", sign) 196 | postURLMatch := regexp.MustCompile(`url : '(.*?)'`).FindStringSubmatch(html) 197 | if len(postURLMatch) == 0 { 198 | return nil, errors.New("[fetchIframe] 获取 postURL 失败") 199 | } 200 | postURL := baseURL + postURLMatch[1] 201 | return ajaxm(postURL, params) 202 | } 203 | 204 | // 通过密码获取文件直链,需要先获取文件分享页的 HTML 源码 205 | func fetchWithPassword(html string, password string) (*DownloadInfo, error) { 206 | sign, err := getValue(html, "sign") 207 | if err != nil { 208 | return nil, err 209 | } 210 | params := url.Values{} 211 | params.Set("action", "downprocess") 212 | params.Set("sign", sign) 213 | params.Set("p", password) 214 | params.Set("kd", "1") 215 | postURLMatch := regexp.MustCompile(`url : '(.*?)'`).FindStringSubmatch(html) 216 | if len(postURLMatch) == 0 { 217 | return nil, errors.New("[fetchWithPassword] 获取 postURL 失败") 218 | } 219 | postURL := baseURL + postURLMatch[1] 220 | downloadInfo, err := ajaxm(postURL, params) 221 | if err != nil { 222 | return nil, errors.New("[fetchWithPassword] 密码错误") 223 | } 224 | return downloadInfo, nil 225 | } 226 | 227 | // 获取文件夹最新的一个文件的信息,包含直链,urlOrId 是文件夹的分享链接或 ID,password 是访问密码 228 | func GetLatestFile(shareURL string, password string) (*DownloadInfo, error) { 229 | fileList, err := GetFileList(shareURL, password, 0) 230 | if err != nil { 231 | return nil, err 232 | } 233 | if len(fileList) == 0 { 234 | return nil, errors.New("没有发现文件") 235 | } 236 | downloadInfo, err := GetDownloadInfo(fileList[0].ShareURL(), password, false) 237 | if err != nil { 238 | return nil, err 239 | } 240 | downloadInfo.FileInfo = fileList[0] 241 | return downloadInfo, nil 242 | } 243 | 244 | // 获取文件夹中指定页码的文件列表 245 | // 246 | // page 的值务必从 0 开始,每次只允许增长 1,不可以直接从 0 变为 2。 247 | // 248 | // 每次换页,务必暂停 1 秒以上。 249 | func GetFileList(shareURL string, password string, page int) ([]FileInfo, error) { 250 | shareId, err := getShareId(shareURL) 251 | if err != nil { 252 | return nil, err 253 | } 254 | html, err := SendRequest(RequestConfig{ 255 | URL: baseURL + "/" + shareId, 256 | Headers: map[string]string{ 257 | "User-Agent": "go-lanzou", 258 | }, 259 | }) 260 | if err != nil { 261 | return nil, err 262 | } 263 | postURLMatch := regexp.MustCompile(`url : '(.*?)'`).FindStringSubmatch(html) 264 | if len(postURLMatch) == 0 { 265 | return nil, errors.New("[fetchWithPassword] 获取 postURL 失败") 266 | } 267 | postURL := baseURL + postURLMatch[1] 268 | // lx 269 | lx, err := getValueKey(html, "lx") 270 | if err != nil { 271 | return nil, err 272 | } 273 | // uid 274 | uid, err := getValueKey(html, "uid") 275 | if err != nil { 276 | return nil, err 277 | } 278 | // up 279 | up, err := getValueKey(html, "up") 280 | if err != nil { 281 | return nil, err 282 | } 283 | // fid 284 | fid, err := getValueKey(html, "fid") 285 | if err != nil { 286 | return nil, err 287 | } 288 | // rep 289 | rep, err := getValueKey(html, "rep") 290 | if err != nil { 291 | return nil, err 292 | } 293 | // t 294 | t, err := getValue(html, "t") 295 | if err != nil { 296 | return nil, err 297 | } 298 | // k 299 | k, err := getValue(html, "k") 300 | if err != nil { 301 | return nil, err 302 | } 303 | 304 | params := url.Values{} 305 | params.Set("lx", lx) 306 | params.Set("fid", fid) 307 | params.Set("uid", uid) 308 | params.Set("pg", strconv.Itoa(page+1)) 309 | params.Set("rep", rep) 310 | params.Set("t", t) 311 | params.Set("k", k) 312 | params.Set("up", up) 313 | params.Set("pwd", password) 314 | // ls 315 | ls, err := getValueKey(html, "ls") 316 | if err == nil { 317 | params.Set("ls", ls) 318 | } 319 | return ajaxList(postURL, params) 320 | } 321 | 322 | func ajaxm(postURL string, params url.Values) (*DownloadInfo, error) { 323 | body, err := SendRequest(RequestConfig{ 324 | URL: postURL, 325 | Method: "POST", 326 | Body: strings.NewReader(params.Encode()), 327 | Headers: map[string]string{ 328 | "Content-Type": "application/x-www-form-urlencoded", 329 | "Referer": baseURL, 330 | }, 331 | }) 332 | if err != nil { 333 | return nil, err 334 | } 335 | var resData struct { 336 | Dom string `json:"dom"` 337 | Url string `json:"url"` 338 | } 339 | err2 := json.Unmarshal([]byte(body), &resData) 340 | if err2 != nil { 341 | return nil, err2 342 | } 343 | downloadURL := resData.Dom + "/file/" + resData.Url 344 | return &DownloadInfo{ 345 | URL: downloadURL, 346 | }, nil 347 | } 348 | 349 | func ajaxList(postURL string, params url.Values) ([]FileInfo, error) { 350 | body, err := SendRequest(RequestConfig{ 351 | URL: postURL, 352 | Method: "POST", 353 | Body: strings.NewReader(params.Encode()), 354 | Headers: map[string]string{ 355 | "Content-Type": "application/x-www-form-urlencoded", 356 | "Referer": baseURL, 357 | }, 358 | }) 359 | if err != nil { 360 | return nil, err 361 | } 362 | 363 | var resData struct { 364 | Zt int `json:"zt"` 365 | Info string `json:"info"` 366 | List []struct { 367 | ShareId string `json:"id"` 368 | Name string `json:"name_all"` 369 | Size string `json:"size"` 370 | Date string `json:"time"` 371 | } `json:"text"` 372 | } 373 | 374 | err2 := json.Unmarshal([]byte(body), &resData) 375 | if err2 != nil { 376 | return nil, errors.New("JSON 解析失败,可能是页码越界或访问密码错误,接口提示:" + resData.Info) 377 | } 378 | 379 | fileList := make([]FileInfo, len(resData.List)) 380 | for index, item := range resData.List { 381 | fileList[index] = FileInfo{ 382 | ShareId: item.ShareId, 383 | Name: item.Name, 384 | Size: item.Size, 385 | Date: item.Date, 386 | } 387 | } 388 | return fileList, nil 389 | } 390 | 391 | // 文件基础信息,不含直链 392 | type FileInfo struct { 393 | // 从分享链接提取的标识字符串 394 | ShareId string 395 | // 文件名称 396 | Name string 397 | // 文件大小 398 | Size string 399 | // 上传日期 400 | Date string 401 | // 访问密码 402 | Password string 403 | } 404 | 405 | // 文件分享链接,根据 baseURL 和 ShareId 构建而成 406 | func (f FileInfo) ShareURL() string { 407 | return baseURL + "/" + f.ShareId 408 | } 409 | 410 | // 文件基础信息,加上直链 411 | type DownloadInfo struct { 412 | FileInfo 413 | URL string 414 | } 415 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------