├── .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 |
--------------------------------------------------------------------------------