From 1051419a331e06bbcf91fe5dffce29b77730e4fc Mon Sep 17 00:00:00 2001 From: glsimon Date: Fri, 30 Jan 2026 01:21:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E139=E5=85=B1=E4=BA=AB?= =?UTF-8?q?=E7=BE=A4=E5=88=86=E4=BA=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drivers/139_grouplink/driver.go | 92 +++++++++++++++++ drivers/139_grouplink/meta.go | 42 ++++++++ drivers/139_grouplink/types.go | 116 +++++++++++++++++++++ drivers/139_grouplink/util.go | 173 ++++++++++++++++++++++++++++++++ drivers/all.go | 1 + 5 files changed, 424 insertions(+) create mode 100755 drivers/139_grouplink/driver.go create mode 100755 drivers/139_grouplink/meta.go create mode 100644 drivers/139_grouplink/types.go create mode 100644 drivers/139_grouplink/util.go diff --git a/drivers/139_grouplink/driver.go b/drivers/139_grouplink/driver.go new file mode 100755 index 00000000..ab6ca58f --- /dev/null +++ b/drivers/139_grouplink/driver.go @@ -0,0 +1,92 @@ +package _139_grouplink + +import ( + "context" + "errors" + "fmt" + "sync/atomic" + _139 "github.com/OpenListTeam/OpenList/v4/drivers/139" + "github.com/OpenListTeam/OpenList/v4/internal/driver" + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/op" + "github.com/OpenListTeam/OpenList/v4/pkg/utils" + log "github.com/sirupsen/logrus" + "time" +) + +var _ driver.Driver = (*Yun139GroupLink)(nil) + +func (d *Yun139GroupLink) Init(ctx context.Context) error { + return nil +} + +func (d *Yun139GroupLink) Drop(ctx context.Context) error { + return nil +} + +func (d *Yun139GroupLink) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { + files, err := d.list(dir.GetID()) + if err != nil { + log.Warnf("获取文件列表失败: %v", err) + return nil, err + } + + return utils.SliceConvert(files, func(src File) (model.Obj, error) { + return src, nil + }) +} + +func (d *Yun139GroupLink) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { + f, ok := file.(File) + if !ok { + return nil, errors.New("文件格式错误") + } + + if f.URL != "" { + exp := 15 * time.Minute + return &model.Link{ + URL: f.URL, + Expiration: &exp, + Concurrency: 5, + PartSize: 10 * utils.MB, + }, nil + } + + count := op.GetDriverCount("139Yun") + if count == 0 { + return nil, errors.New("未配置139Yun账号,无法获取高速下载链接") + } + + var lastErr error + for i := 0; i < count; i++ { + link, err := d.myLink(ctx, f) + if err == nil { + return link, nil + } + lastErr = err + atomic.AddInt32(&idx, 1) + } + return nil, fmt.Errorf("所有%d个139Yun账号均获取直链失败:%v", count, lastErr) +} + +func (d *Yun139GroupLink) myLink(ctx context.Context, f File) (*model.Link, error) { + driverIdx := int(atomic.LoadInt32(&idx) % int32(op.GetDriverCount("139Yun"))) + storage := op.GetFirstDriver("139Yun", driverIdx) + if storage == nil { + return nil, errors.New("找不到139云盘账号") + } + yun139 := storage.(*_139.Yun139) + log.Infof("[139Yun-%d] 为grouplink文件获取高速直链:%s(ID:%s)", yun139.ID, f.Name, f.ID) + url, err := d.getDownloadUrl(f.ID) + if err != nil { + return nil, err + } + + exp := 15 * time.Minute + return &model.Link{ + URL: url + fmt.Sprintf("#storageId=%d", yun139.ID), + Expiration: &exp, + Concurrency: yun139.Concurrency, + PartSize: yun139.ChunkSize, + }, nil +} diff --git a/drivers/139_grouplink/meta.go b/drivers/139_grouplink/meta.go new file mode 100755 index 00000000..fd6b93f5 --- /dev/null +++ b/drivers/139_grouplink/meta.go @@ -0,0 +1,42 @@ +package _139_grouplink + +import ( + "github.com/OpenListTeam/OpenList/v4/internal/driver" + "github.com/OpenListTeam/OpenList/v4/internal/model" + "github.com/OpenListTeam/OpenList/v4/internal/op" +) + +type Addition struct { + ShareId string `json:"shareId" required:"true"` + SharePwd string `json:"sharePwd" required:"true"` + RootID string `json:"rootId" default:"root"` +} + +var config = driver.Config{ + Name: "139GroupLink", + NoUpload: true, + NoOverwriteUpload: true, + DefaultRoot: "root", +} + +type Yun139GroupLink struct { + Storage model.Storage + Addition + UserDomainId string +} + +func (d *Yun139GroupLink) GetAddition() driver.Additional { return &d.Addition } +func (d *Yun139GroupLink) Config() driver.Config { return config } +func (d *Yun139GroupLink) GetStorage() *model.Storage { return &d.Storage } +func (d *Yun139GroupLink) SetStorage(s model.Storage) { d.Storage = s } +func (d *Yun139GroupLink) GetRootId() string { return d.RootID } + +func (d *Yun139GroupLink) GetDownloadUrl(fileId string) (string, error) { + return d.getDownloadUrl(fileId) +} + +func init() { + op.RegisterDriver(func() driver.Driver { + return &Yun139GroupLink{} + }) +} diff --git a/drivers/139_grouplink/types.go b/drivers/139_grouplink/types.go new file mode 100644 index 00000000..9b335765 --- /dev/null +++ b/drivers/139_grouplink/types.go @@ -0,0 +1,116 @@ +package _139_grouplink + +import ( + "time" + + "github.com/OpenListTeam/OpenList/v4/pkg/utils" +) + +type GetOutLinkInfoReq struct { + LinkId string `json:"linkId"` + Passwd string `json:"passwd"` + CaSrt int `json:"caSrt"` + CoSrt int `json:"coSrt"` + SrtDr int `json:"srtDr"` + PageNum int `json:"pageNum"` + PCaId string `json:"pCaId"` + PageSize int `json:"pageSize"` + NextPageCursor interface{} `json:"nextPageCursor"` +} + +type GetOutLinkInfoResp struct { + Success bool `json:"success"` + Code string `json:"code"` + Message string `json:"message"` + Data struct { + NodNum interface{} `json:"nodNum"` + AssetsList []Assets `json:"assetsList"` + IsCreator string `json:"isCreator"` + OutLink OutLink `json:"outLink"` + NextPageCursor interface{} `json:"nextPageCursor"` + PcaId string `json:"pCaId"` + } `json:"data"` +} + +type Assets struct { + AssetsId string `json:"assetsId"` + AssetsName string `json:"assetsName"` + Category int `json:"category"` + CoType int `json:"coType"` + CoSuffix string `json:"coSuffix"` + CoSize int64 `json:"coSize"` + UdTime string `json:"udTime"` + ThumbnailURL string `json:"thumbnailURL"` + BthumbnailURL string `json:"bthumbnailURL"` + PresentURL string `json:"presentURL"` + Path string `json:"path"` + IsDir bool `json:"-"` + Time time.Time `json:"-"` +} + +type OutLink struct { + LinkId string `json:"linkId"` + LinkCode string `json:"linkCode"` + ChannelId string `json:"channelId"` + Passwd string `json:"passwd"` + Url string `json:"url"` + LkName string `json:"lkName"` + CtTime string `json:"ctTime"` + LastUdTime string `json:"lastUdTime"` + OwnerUserId string `json:"ownerUserId"` +} + +type File struct { + Name string + Path string + Size int64 + ID string + IsDirFlag bool + Time time.Time + URL string +} + +func (f File) GetID() string { + return f.ID +} + +func (f File) GetName() string { + return f.Name +} + +func (f File) GetSize() int64 { + return f.Size +} + +func (f File) GetPath() string { + return f.Path +} + +func (f File) IsDir() bool { + return f.IsDirFlag +} + +func (f File) ModTime() time.Time { + return f.Time +} + +func (f File) CreateTime() time.Time { + return f.Time +} + +func (f File) GetHash() utils.HashInfo { + return utils.HashInfo{} +} + +func fileToObj(src Assets) File { + parsedTime, _ := time.Parse("20060102150405", src.UdTime) + return File{ + ID: src.AssetsId, + Name: src.AssetsName, + Size: src.CoSize, + Path: src.Path, + IsDirFlag: false, + Time: parsedTime, + URL: "", + } +} diff --git a/drivers/139_grouplink/util.go b/drivers/139_grouplink/util.go new file mode 100644 index 00000000..2f03304f --- /dev/null +++ b/drivers/139_grouplink/util.go @@ -0,0 +1,173 @@ +package _139_grouplink + +import ( + "encoding/json" + "errors" + "fmt" + "sync/atomic" + log "github.com/sirupsen/logrus" + _139 "github.com/OpenListTeam/OpenList/v4/drivers/139" + "github.com/OpenListTeam/OpenList/v4/drivers/base" + "github.com/OpenListTeam/OpenList/v4/internal/op" + "net/http" +) + +const apiBase = "https://share-kd-njs.yun.139.com/yun-share/general/IOutLink/" +var idx int32 = 0 + +type GetDownloadUrlReq struct { + UserDomainId string `json:"userDomainId"` + LinkId string `json:"linkId"` + AssetsId string `json:"assetsId"` +} + +type GetDownloadUrlResp struct { + Success bool `json:"success"` + Code string `json:"code"` + Message string `json:"message"` + Data struct { + DownLoadUrl string `json:"downLoadUrl"` + CdnDownLoadUrl string `json:"cdnDownLoadUrl"` + } `json:"data"` +} + +func (y *Yun139GroupLink) httpPost(pathname string, data interface{}, auth bool) ([]byte, error) { + u := apiBase + pathname + req := base.RestyClient.R() + + req.SetHeaders(map[string]string{ + "Content-Type": "application/json;charset=utf-8", + "Referer": "https://yun.139.com/", + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:147.0) Gecko/20100101 Firefox/147.0", + "Origin": "https://yun.139.com", + "x-share-channel": "0102", + "hcy-cool-flag": "1", + }) + + if auth { + driverIdx := int(atomic.LoadInt32(&idx) % int32(op.GetDriverCount("139Yun"))) + driver := op.GetFirstDriver("139Yun", driverIdx) + if driver != nil { + yun139 := driver.(*_139.Yun139) + req.SetHeader("Authorization", "Basic "+yun139.Authorization) + } else { + log.Warn("未找到139Yun驱动,无法添加Authorization鉴权头") + } + } + + jsonData, err := json.Marshal(data) + if err != nil { + return nil, err + } + req.SetBody(jsonData) + + res, err := req.Execute(http.MethodPost, u) + if err != nil { + log.Warnf("HTTP请求失败: %v, url: %s", err, u) + return nil, err + } + + return res.Body(), nil +} + +func (y *Yun139GroupLink) getDownloadUrl(fid string) (string, error) { + if y.UserDomainId == "" { + return "", errors.New("userDomainId未初始化,请先执行根目录探活") + } + req := GetDownloadUrlReq{ + UserDomainId: y.UserDomainId, + LinkId: y.ShareId, + AssetsId: fid, + } + + respBody, err := y.httpPost("getDownloadUrl", req, true) + if err != nil { + return "", fmt.Errorf("下载接口请求失败:%v", err) + } + + var resp GetDownloadUrlResp + if err := json.Unmarshal(respBody, &resp); err != nil { + return "", fmt.Errorf("下载响应解析失败:%v,body:%s", err, string(respBody)) + } + + if !resp.Success || resp.Code != "0000" { + return "", fmt.Errorf("下载接口返回错误:%s(码:%s)", resp.Message, resp.Code) + } + + if resp.Data.DownLoadUrl == "" { + return "", errors.New("下载接口未返回有效高速直链") + } + + log.Debugf("grouplink专属接口获取高速直链成功:%s", resp.Data.DownLoadUrl) + return resp.Data.DownLoadUrl, nil +} + +func (y *Yun139GroupLink) getShareInfo(pCaID string, page int) (GetOutLinkInfoResp, error) { + var resp GetOutLinkInfoResp + reqBody := GetOutLinkInfoReq{ + LinkId: y.ShareId, + Passwd: y.SharePwd, + CaSrt: 0, + CoSrt: 0, + SrtDr: 1, + PageNum: page + 1, + PCaId: pCaID, + PageSize: 100, + NextPageCursor: nil, + } + + body, err := y.httpPost("getOutLinkInfo", reqBody, false) + if err != nil { + return resp, err + } + + if err := json.Unmarshal(body, &resp); err != nil { + log.Warnf("响应解析失败: %v, body: %s", err, string(body)) + return resp, err + } + + if !resp.Success || resp.Code != "0000" { + return resp, errors.New(resp.Message) + } + + return resp, nil +} + +func (y *Yun139GroupLink) list(pCaID string) ([]File, error) { + files := make([]File, 0) + probeResp, err := y.getShareInfo("root", 0) + if err != nil { + return nil, fmt.Errorf("根目录探活获取子目录ID失败:%v", err) + } + if len(probeResp.Data.AssetsList) == 0 { + return nil, errors.New("探活响应未返回任何目录/文件项") + } + realPCaId := probeResp.Data.AssetsList[0].AssetsId + if realPCaId == "" { + return nil, errors.New("探活响应未返回有效真实pCaId") + } + log.Debugf("根目录探活成功,获取真实文件目录ID:%s", realPCaId) + + y.UserDomainId = probeResp.Data.OutLink.OwnerUserId + log.Debugf("探活成功,获取userDomainId:%s", y.UserDomainId) + + page := 0 + for { + res, err := y.getShareInfo(realPCaId, page) + if err != nil { + return nil, fmt.Errorf("真实目录分页查询失败(page=%d):%v", page, err) + } + for _, asset := range res.Data.AssetsList { + file := fileToObj(asset) + files = append(files, file) + } + + if res.Data.NextPageCursor == nil || res.Data.NextPageCursor == "" { + break + } + page++ + } + + log.Debugf("文件列表查询成功,共获取%d个文件", len(files)) + return files, nil +} diff --git a/drivers/all.go b/drivers/all.go index 3ba0a059..8c29cd57 100644 --- a/drivers/all.go +++ b/drivers/all.go @@ -88,6 +88,7 @@ import ( _ "github.com/OpenListTeam/OpenList/v4/drivers/baidu_share2" _ "github.com/OpenListTeam/OpenList/v4/drivers/quark_uc_share" _ "github.com/OpenListTeam/OpenList/v4/drivers/thunder_share" + _ "github.com/OpenListTeam/OpenList/v4/drivers/139_grouplink" ) // All do nothing,just for import