package chaoxing import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "mime/multipart" "net/http" "net/url" "strings" "time" "github.com/alist-org/alist/v3/internal/driver" "github.com/alist-org/alist/v3/internal/errs" "github.com/alist-org/alist/v3/internal/model" "github.com/alist-org/alist/v3/internal/op" "github.com/alist-org/alist/v3/pkg/cron" "github.com/alist-org/alist/v3/pkg/utils" "github.com/go-resty/resty/v2" "google.golang.org/appengine/log" ) type ChaoXing struct { model.Storage Addition cron *cron.Cron config driver.Config conf Conf } func (d *ChaoXing) Config() driver.Config { return d.config } func (d *ChaoXing) GetAddition() driver.Additional { return &d.Addition } func (d *ChaoXing) refreshCookie() error { cookie, err := d.Login() if err != nil { d.Status = err.Error() op.MustSaveDriverStorage(d) return nil } d.Addition.Cookie = cookie op.MustSaveDriverStorage(d) return nil } func (d *ChaoXing) Init(ctx context.Context) error { err := d.refreshCookie() if err != nil { log.Errorf(ctx, err.Error()) } d.cron = cron.NewCron(time.Hour * 12) d.cron.Do(func() { err = d.refreshCookie() if err != nil { log.Errorf(ctx, err.Error()) } }) return nil } func (d *ChaoXing) Drop(ctx context.Context) error { d.cron.Stop() return nil } func (d *ChaoXing) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) { files, err := d.GetFiles(dir.GetID()) if err != nil { return nil, err } return utils.SliceConvert(files, func(src File) (model.Obj, error) { return fileToObj(src), nil }) } func (d *ChaoXing) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) { var resp DownResp ua := d.conf.ua fileId := strings.Split(file.GetID(), "$")[1] _, err := d.requestDownload("/screen/note_note/files/status/"+fileId, http.MethodPost, func(req *resty.Request) { req.SetHeader("User-Agent", ua) }, &resp) if err != nil { return nil, err } u := resp.Download return &model.Link{ URL: u, Header: http.Header{ "Cookie": []string{d.Cookie}, "Referer": []string{d.conf.referer}, "User-Agent": []string{ua}, }, Concurrency: 2, PartSize: 10 * utils.MB, }, nil } func (d *ChaoXing) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error { query := map[string]string{ "bbsid": d.Addition.Bbsid, "name": dirName, "pid": parentDir.GetID(), } var resp ListFileResp _, err := d.request("/pc/resource/addResourceFolder", http.MethodGet, func(req *resty.Request) { req.SetQueryParams(query) }, &resp) if err != nil { return err } if resp.Result != 1 { msg := fmt.Sprintf("error:%s", resp.Msg) return errors.New(msg) } return nil } func (d *ChaoXing) Move(ctx context.Context, srcObj, dstDir model.Obj) error { query := map[string]string{ "bbsid": d.Addition.Bbsid, "folderIds": srcObj.GetID(), "targetId": dstDir.GetID(), } if !srcObj.IsDir() { query = map[string]string{ "bbsid": d.Addition.Bbsid, "recIds": strings.Split(srcObj.GetID(), "$")[0], "targetId": dstDir.GetID(), } } var resp ListFileResp _, err := d.request("/pc/resource/moveResource", http.MethodGet, func(req *resty.Request) { req.SetQueryParams(query) }, &resp) if err != nil { return err } if !resp.Status { msg := fmt.Sprintf("error:%s", resp.Msg) return errors.New(msg) } return nil } func (d *ChaoXing) Rename(ctx context.Context, srcObj model.Obj, newName string) error { query := map[string]string{ "bbsid": d.Addition.Bbsid, "folderId": srcObj.GetID(), "name": newName, } path := "/pc/resource/updateResourceFolderName" if !srcObj.IsDir() { // path = "/pc/resource/updateResourceFileName" // query = map[string]string{ // "bbsid": d.Addition.Bbsid, // "recIds": strings.Split(srcObj.GetID(), "$")[0], // "name": newName, // } return errors.New("此网盘不支持修改文件名") } var resp ListFileResp _, err := d.request(path, http.MethodGet, func(req *resty.Request) { req.SetQueryParams(query) }, &resp) if err != nil { return err } if resp.Result != 1 { msg := fmt.Sprintf("error:%s", resp.Msg) return errors.New(msg) } return nil } func (d *ChaoXing) Copy(ctx context.Context, srcObj, dstDir model.Obj) error { // TODO copy obj, optional return errs.NotImplement } func (d *ChaoXing) Remove(ctx context.Context, obj model.Obj) error { query := map[string]string{ "bbsid": d.Addition.Bbsid, "folderIds": obj.GetID(), } path := "/pc/resource/deleteResourceFolder" var resp ListFileResp if !obj.IsDir() { path = "/pc/resource/deleteResourceFile" query = map[string]string{ "bbsid": d.Addition.Bbsid, "recIds": strings.Split(obj.GetID(), "$")[0], } } _, err := d.request(path, http.MethodGet, func(req *resty.Request) { req.SetQueryParams(query) }, &resp) if err != nil { return err } if resp.Result != 1 { msg := fmt.Sprintf("error:%s", resp.Msg) return errors.New(msg) } return nil } func (d *ChaoXing) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error { var resp UploadDataRsp _, err := d.request("https://noteyd.chaoxing.com/pc/files/getUploadConfig", http.MethodGet, func(req *resty.Request) { }, &resp) if err != nil { return err } if resp.Result != 1 { return errors.New("get upload data error") } body := &bytes.Buffer{} writer := multipart.NewWriter(body) filePart, err := writer.CreateFormFile("file", stream.GetName()) if err != nil { return err } _, err = utils.CopyWithBuffer(filePart, stream) if err != nil { return err } err = writer.WriteField("_token", resp.Msg.Token) if err != nil { return err } err = writer.WriteField("puid", fmt.Sprintf("%d", resp.Msg.Puid)) if err != nil { fmt.Println("Error writing param2 to request body:", err) return err } err = writer.Close() if err != nil { return err } req, err := http.NewRequest("POST", "https://pan-yz.chaoxing.com/upload", body) if err != nil { return err } req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Content-Length", fmt.Sprintf("%d", body.Len())) resps, err := http.DefaultClient.Do(req) if err != nil { return err } defer resps.Body.Close() bodys, err := io.ReadAll(resps.Body) if err != nil { return err } var fileRsp UploadFileDataRsp err = json.Unmarshal(bodys, &fileRsp) if err != nil { return err } if fileRsp.Msg != "success" { return errors.New(fileRsp.Msg) } uploadDoneParam := UploadDoneParam{Key: fileRsp.ObjectID, Cataid: "100000019", Param: fileRsp.Data} params, err := json.Marshal(uploadDoneParam) if err != nil { return err } query := map[string]string{ "bbsid": d.Addition.Bbsid, "pid": dstDir.GetID(), "type": "yunpan", "params": url.QueryEscape("[" + string(params) + "]"), } var respd ListFileResp _, err = d.request("/pc/resource/addResource", http.MethodGet, func(req *resty.Request) { req.SetQueryParams(query) }, &respd) if err != nil { return err } if respd.Result != 1 { msg := fmt.Sprintf("error:%v", resp.Msg) return errors.New(msg) } return nil } var _ driver.Driver = (*ChaoXing)(nil)