refactor: init v3
This commit is contained in:
parent
eb15bce24b
commit
b76060570e
50
.github/workflows/build.yml
vendored
50
.github/workflows/build.yml
vendored
@ -1,50 +0,0 @@
|
|||||||
name: build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ '**' ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ '**' ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
platform: [ubuntu-latest]
|
|
||||||
go-version: [1.18]
|
|
||||||
name: Build
|
|
||||||
runs-on: ${{ matrix.platform }}
|
|
||||||
steps:
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go-version }}
|
|
||||||
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: '16'
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
path: alist
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
docker pull techknowlogick/xgo:latest
|
|
||||||
go install src.techknowlogick.com/xgo@latest
|
|
||||||
sudo apt install upx
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
mv alist/build.sh .
|
|
||||||
bash build.sh web
|
|
||||||
mv dist/* alist/public
|
|
||||||
bash build.sh build
|
|
||||||
|
|
||||||
- name: Upload artifact
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: artifact
|
|
||||||
path: alist/build
|
|
44
.github/workflows/build_docker.yml
vendored
44
.github/workflows/build_docker.yml
vendored
@ -1,44 +0,0 @@
|
|||||||
name: build_docker
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ v2 ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_docker:
|
|
||||||
name: Docker
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v3
|
|
||||||
with:
|
|
||||||
images: xhofe/alist
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: '16'
|
|
||||||
- name: Build web
|
|
||||||
run: |
|
|
||||||
bash build.sh web
|
|
||||||
mv dist/* public
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: xhofe
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
|
72
.github/workflows/release.yml
vendored
72
.github/workflows/release.yml
vendored
@ -1,72 +0,0 @@
|
|||||||
name: release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
changelog:
|
|
||||||
name: Create Release
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Changelog
|
|
||||||
uses: Bullrich/generate-release-changelog@master
|
|
||||||
id: Changelog
|
|
||||||
env:
|
|
||||||
REPO: ${{ github.repository }}
|
|
||||||
- name: Create Release
|
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@latest
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: Release ${{ github.ref }}
|
|
||||||
body: |
|
|
||||||
${{ steps.Changelog.outputs.changelog }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
release:
|
|
||||||
needs: changelog
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
platform: [ubuntu-latest]
|
|
||||||
go-version: [1.18]
|
|
||||||
name: Release
|
|
||||||
runs-on: ${{ matrix.platform }}
|
|
||||||
steps:
|
|
||||||
- name: Setup Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: ${{ matrix.go-version }}
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
ref: v2
|
|
||||||
path: alist
|
|
||||||
persist-credentials: false
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install upx
|
|
||||||
run: |
|
|
||||||
docker pull techknowlogick/xgo:latest
|
|
||||||
go install src.techknowlogick.com/xgo@latest
|
|
||||||
sudo apt install upx
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
mv alist/build.sh .
|
|
||||||
bash build.sh cdn
|
|
||||||
mv dist/* alist/public
|
|
||||||
bash build.sh release
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v1
|
|
||||||
with:
|
|
||||||
files: alist/build/compress/*
|
|
45
.github/workflows/release_docker.yml
vendored
45
.github/workflows/release_docker.yml
vendored
@ -1,45 +0,0 @@
|
|||||||
name: release_docker
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker_release:
|
|
||||||
name: Docker
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v3
|
|
||||||
with:
|
|
||||||
images: xhofe/alist
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: '16'
|
|
||||||
- name: Build web
|
|
||||||
run: |
|
|
||||||
bash build.sh cdn
|
|
||||||
mv dist/* public
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
- name: Login to DockerHub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: xhofe
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Build and push
|
|
||||||
id: docker_build
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
|
@ -17,12 +17,12 @@ Clone `alist` and `alist-web` anywhere:
|
|||||||
$ git clone https://github.com/Xhofe/alist.git
|
$ git clone https://github.com/Xhofe/alist.git
|
||||||
$ git clone https://github.com/Xhofe/alist-web.git
|
$ git clone https://github.com/Xhofe/alist-web.git
|
||||||
```
|
```
|
||||||
You should switch to the dev branch for development.
|
You should switch to the `main` branch for development.
|
||||||
|
|
||||||
## Preview your change
|
## Preview your change
|
||||||
### backend
|
### backend
|
||||||
```shell
|
```shell
|
||||||
$ go run alist.go
|
$ go run cmd/alist.go
|
||||||
```
|
```
|
||||||
### frontend
|
### frontend
|
||||||
```shell
|
```shell
|
||||||
@ -102,4 +102,4 @@ The rest of the commit message is then used for this.
|
|||||||
## Submit a pull request
|
## Submit a pull request
|
||||||
|
|
||||||
Push your branch to your `alist` fork and open a pull request against the
|
Push your branch to your `alist` fork and open a pull request against the
|
||||||
`dev` branch.
|
`main` branch.
|
||||||
|
14
Dockerfile
14
Dockerfile
@ -1,14 +0,0 @@
|
|||||||
FROM alpine:edge as builder
|
|
||||||
LABEL stage=go-builder
|
|
||||||
WORKDIR /app/
|
|
||||||
COPY ./ ./
|
|
||||||
RUN apk add --no-cache bash git go gcc musl-dev; \
|
|
||||||
bash build.sh docker
|
|
||||||
|
|
||||||
FROM alpine:edge
|
|
||||||
LABEL MAINTAINER="i@nn.ci"
|
|
||||||
VOLUME /opt/alist/data/
|
|
||||||
WORKDIR /opt/alist/
|
|
||||||
COPY --from=builder /app/bin/alist ./
|
|
||||||
EXPOSE 5244
|
|
||||||
CMD [ "./alist", "-docker" ]
|
|
58
README.md
58
README.md
@ -1,6 +1,6 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://jsd.nn.ci/gh/alist-org/logo@main/logo.svg"/></a>
|
||||||
<p><em>🗂️Another file list program that supports multiple storage, powered by Gin and React.</em></p>
|
<p><em>🗂️A file list program that supports multiple storage, powered by Gin and React.</em></p>
|
||||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
||||||
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
||||||
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
|
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
|
||||||
@ -13,64 +13,12 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
English | [中文](./README_cn.md) | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
[Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- [x] Multiple storage
|
|
||||||
- [x] Local storage
|
|
||||||
- [x] [Aliyundrive](https://www.aliyundrive.com/)
|
|
||||||
- [x] OneDrive / Sharepoint ([global](https://www.office.com/), [cn](https://portal.partner.microsoftonline.cn),de,us)
|
|
||||||
- [x] [189cloud](https://cloud.189.cn) (Personal, Family)
|
|
||||||
- [x] [GoogleDrive](https://drive.google.com/)
|
|
||||||
- [x] [123pan](https://www.123pan.com/)
|
|
||||||
- [x] [Lanzou](https://pc.woozooo.com/)
|
|
||||||
- [x] [Alist](https://github.com/Xhofe/alist)
|
|
||||||
- [x] FTP
|
|
||||||
- [x] [PikPak](https://www.mypikpak.com/)
|
|
||||||
- [x] [ShandianPan](https://shandianpan.com/)
|
|
||||||
- [x] [S3](https://aws.amazon.com/s3/)
|
|
||||||
- [x] WebDav(Support OneDrive/SharePoint without API)
|
|
||||||
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))
|
|
||||||
- [x] [Mediatrack](https://www.mediatrack.cn/)
|
|
||||||
- [x] [139yun](https://yun.139.com/) (Personal, Family)
|
|
||||||
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
|
||||||
- [x] [Baidu Disk](http://pan.baidu.com/)
|
|
||||||
- [x] [Quark](https://pan.quark.cn)
|
|
||||||
- [x] [XunleiCloud](https://pan.xunlei.com/)
|
|
||||||
- [x] SFTP
|
|
||||||
- [x] [Baidu.Photo](https://photo.baidu.com/)
|
|
||||||
- [x] Easy to deploy and out-of-the-box
|
|
||||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
|
||||||
- [x] Image preview in gallery mode
|
|
||||||
- [x] Video and audio preview (mp4, mp3, ...)
|
|
||||||
- [x] Office documents preview (docx, pptx, xlsx, ...)
|
|
||||||
- [x] `README.md` preview rendering
|
|
||||||
- [x] File permalink copy and direct file download
|
|
||||||
- [x] Dark mode
|
|
||||||
- [x] I18n
|
|
||||||
- [x] Protected routes (password protection and authentication)
|
|
||||||
- [x] WebDav (see https://alist-doc.nn.ci/en/docs/webdav for details)
|
|
||||||
- [x] [Docker Deploy](https://hub.docker.com/r/xhofe/alist)
|
|
||||||
- [x] Cloudflare workers proxy
|
|
||||||
- [x] File/Folder package download
|
|
||||||
- [x] Support video list playback and subtitles(ass,srt,vtt)
|
|
||||||
- [x] Web upload(Can allow visitors to upload), delete, mkdir, rename, move and copy
|
|
||||||
|
|
||||||
## Discussion
|
## Discussion
|
||||||
|
|
||||||
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports and feature request only.**
|
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports and feature request only.**
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
Available at: <https://alist.nn.ci>.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Document
|
|
||||||
|
|
||||||
<https://alist-doc.nn.ci/en/>
|
|
||||||
|
|
||||||
## Special sponsors
|
## Special sponsors
|
||||||
- [Find Resources - Aliyundrive Resource Search Engine](https://zhaoziyuan.la/)
|
- [Find Resources - Aliyundrive Resource Search Engine](https://zhaoziyuan.la/)
|
||||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||||
|
91
README_cn.md
91
README_cn.md
@ -1,91 +0,0 @@
|
|||||||
<div align="center">
|
|
||||||
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
|
||||||
<p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p>
|
|
||||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
|
|
||||||
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
|
|
||||||
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
|
|
||||||
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square&color=%239F7AEA" alt="Downloads"></a>
|
|
||||||
<a href="https://github.com/Xhofe/alist/blob/v2/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
|
|
||||||
<a href="https://pay.xhofe.top">
|
|
||||||
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[English](./README.md) | 中文 | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
|
||||||
|
|
||||||
## 支持
|
|
||||||
|
|
||||||
- [x] 多种存储
|
|
||||||
- [x] 本地存储
|
|
||||||
- [x] [阿里云盘](https://www.aliyundrive.com/)
|
|
||||||
- [x] OneDrive / Sharepoint([国际版](https://www.office.com/), [世纪互联](https://portal.partner.microsoftonline.cn),de,us)
|
|
||||||
- [x] [天翼云盘](https://cloud.189.cn) (个人云, 家庭云)
|
|
||||||
- [x] [GoogleDrive](https://drive.google.com/)
|
|
||||||
- [x] [123云盘](https://www.123pan.com/)
|
|
||||||
- [x] [蓝奏云](https://pc.woozooo.com/)
|
|
||||||
- [x] [Alist](https://github.com/Xhofe/alist)
|
|
||||||
- [x] FTP
|
|
||||||
- [x] [PikPak](https://www.mypikpak.com/)
|
|
||||||
- [x] [闪电盘](https://shandianpan.com/)
|
|
||||||
- [x] [S3](https://aws.amazon.com/cn/s3/)
|
|
||||||
- [x] WebDav(支持无API的OneDrive/SharePoint)
|
|
||||||
- [x] Teambition([中国](https://www.teambition.com/ ),[国际](https://us.teambition.com/ ))
|
|
||||||
- [x] [分秒帧](https://www.mediatrack.cn/)
|
|
||||||
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
|
|
||||||
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
|
||||||
- [x] [百度网盘](http://pan.baidu.com/)
|
|
||||||
- [x] [夸克网盘](https://pan.quark.cn)
|
|
||||||
- [x] [迅雷云盘](https://pan.xunlei.com/)
|
|
||||||
- [x] SFTP
|
|
||||||
- [x] [一刻相册](https://photo.baidu.com/)
|
|
||||||
- [x] 部署方便,开箱即用
|
|
||||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
|
||||||
- [x] 画廊模式下的图像预览
|
|
||||||
- [x] 视频和音频预览(mp4、mp3 等)
|
|
||||||
- [x] Office 文档预览(docx、pptx、xlsx、...)
|
|
||||||
- [x] `README.md` 预览渲染
|
|
||||||
- [x] 文件永久链接复制和直接文件下载
|
|
||||||
- [x] 黑暗模式
|
|
||||||
- [x] 国际化
|
|
||||||
- [x] 受保护的路由(密码保护和身份验证)
|
|
||||||
- [x] WebDav(具体见https://alist-doc.nn.ci/docs/webdav )
|
|
||||||
- [x] [Docker 部署](https://hub.docker.com/r/xhofe/alist)
|
|
||||||
- [x] Cloudflare workers 中转
|
|
||||||
- [x] 文件/文件夹打包下载
|
|
||||||
- [x] 支持视频列表播放和字幕(ass,srt,vtt)
|
|
||||||
- [x] 网页上传(可以允许访客上传),删除,新建文件夹,重命名,移动,复制
|
|
||||||
|
|
||||||
## 讨论
|
|
||||||
|
|
||||||
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) ,**issue仅针对错误报告和功能请求。**
|
|
||||||
|
|
||||||
## 演示
|
|
||||||
|
|
||||||
<https://alist.nn.ci>。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 文档
|
|
||||||
|
|
||||||
<https://alist-doc.nn.ci/>
|
|
||||||
|
|
||||||
## 特别赞助
|
|
||||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
|
|
||||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
|
||||||
|
|
||||||
## 许可
|
|
||||||
|
|
||||||
`AList` 是在 AGPL-3.0 许可下许可的开源软件。
|
|
||||||
|
|
||||||
## 免责声明
|
|
||||||
- 本程序为免费开源项目,旨在分享网盘文件,方便下载以及学习golang,使用时请遵守相关法律法规,请勿滥用;
|
|
||||||
- 本程序通过调用官方sdk/接口实现,无破坏官方接口行为;
|
|
||||||
- 本程序仅做302重定向/流量转发,不拦截、存储、篡改任何用户数据;
|
|
||||||
- 在使用本程序之前,你应了解并承担相应的风险,包括但不限于账号被ban,下载限速等,与本程序无关;
|
|
||||||
- 如有侵权,请通过[邮件](mailto:i@nn.ci)与我联系,会及时处理。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
> [@博客](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@Telegram群](https://t.me/alist_chat) · [@QQ群](https://jq.qq.com/?_wv=1027&k=YJJj2Gwb)
|
|
59
alist.go
59
alist.go
@ -1,59 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/bootstrap"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
_ "github.com/Xhofe/alist/drivers"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/server"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Init() bool {
|
|
||||||
bootstrap.InitConf()
|
|
||||||
bootstrap.InitCron()
|
|
||||||
bootstrap.InitModel()
|
|
||||||
if conf.Password {
|
|
||||||
pass, err := model.GetSettingByKey("password")
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf(err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
fmt.Printf("your password: %s\n", pass.Value)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
server.InitIndex()
|
|
||||||
bootstrap.InitSettings()
|
|
||||||
bootstrap.InitAccounts()
|
|
||||||
bootstrap.InitCache()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if conf.Version {
|
|
||||||
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\nWebVersion: %s\n",
|
|
||||||
conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag, conf.WebTag)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !Init() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !conf.Debug {
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
}
|
|
||||||
r := gin.Default()
|
|
||||||
server.InitApiRouter(r)
|
|
||||||
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
|
|
||||||
log.Infof("start server @ %s", base)
|
|
||||||
var err error
|
|
||||||
if conf.Conf.Scheme.Https {
|
|
||||||
err = r.RunTLS(base, conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
|
||||||
} else {
|
|
||||||
err = r.Run(base)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to start: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/drivers/operate"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitAccounts() {
|
|
||||||
log.Infof("init accounts...")
|
|
||||||
var accounts []model.Account
|
|
||||||
if err := conf.DB.Find(&accounts).Error; err != nil {
|
|
||||||
log.Fatalf("failed sync init accounts")
|
|
||||||
}
|
|
||||||
for i, account := range accounts {
|
|
||||||
model.RegisterAccount(account)
|
|
||||||
driver, ok := base.GetDriver(account.Type)
|
|
||||||
if !ok {
|
|
||||||
log.Errorf("no [%s] driver", account.Type)
|
|
||||||
} else {
|
|
||||||
log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
|
|
||||||
//err := driver.Save(&accounts[i], nil)
|
|
||||||
err := operate.Save(driver, &accounts[i], nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
|
|
||||||
} else {
|
|
||||||
log.Infof("success init account: %s, type: %s", account.Name, account.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/eko/gocache/v2/cache"
|
|
||||||
"github.com/eko/gocache/v2/store"
|
|
||||||
goCache "github.com/patrickmn/go-cache"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitCache init cache
|
|
||||||
func InitCache() {
|
|
||||||
log.Infof("init cache...")
|
|
||||||
c := conf.Conf.Cache
|
|
||||||
if c.Expiration == 0 {
|
|
||||||
c.Expiration, c.CleanupInterval = 60, 120
|
|
||||||
}
|
|
||||||
goCacheClient := goCache.New(time.Duration(c.Expiration)*time.Minute, time.Duration(c.CleanupInterval)*time.Minute)
|
|
||||||
goCacheStore := store.NewGoCache(goCacheClient, nil)
|
|
||||||
conf.Cache = cache.New(goCacheStore)
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/caarlos0/env/v6"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitConf init config
|
|
||||||
func InitConf() {
|
|
||||||
log.Infof("reading config file: %s", conf.ConfigFile)
|
|
||||||
if !utils.Exists(conf.ConfigFile) {
|
|
||||||
log.Infof("config file not exists, creating default config file")
|
|
||||||
_, err := utils.CreatNestedFile(conf.ConfigFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to create config file")
|
|
||||||
}
|
|
||||||
conf.Conf = conf.DefaultConfig()
|
|
||||||
if !utils.WriteToJson(conf.ConfigFile, conf.Conf) {
|
|
||||||
log.Fatalf("failed to create default config file")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
config, err := ioutil.ReadFile(conf.ConfigFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("reading config file error:%s", err.Error())
|
|
||||||
}
|
|
||||||
conf.Conf = conf.DefaultConfig()
|
|
||||||
err = utils.Json.Unmarshal(config, conf.Conf)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("load config error: %s", err.Error())
|
|
||||||
}
|
|
||||||
log.Debugf("config:%+v", conf.Conf)
|
|
||||||
// update config.json struct
|
|
||||||
confBody, err := utils.Json.MarshalIndent(conf.Conf, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("marshal config error:%s", err.Error())
|
|
||||||
}
|
|
||||||
err = ioutil.WriteFile(conf.ConfigFile, confBody, 0777)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("update config struct error: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !conf.Conf.Force {
|
|
||||||
confFromEnv()
|
|
||||||
}
|
|
||||||
err := os.RemoveAll(filepath.Join(conf.Conf.TempDir))
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("failed delete temp file:", err)
|
|
||||||
}
|
|
||||||
err = os.MkdirAll(conf.Conf.TempDir, 0700)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("create temp dir error: %s", err.Error())
|
|
||||||
}
|
|
||||||
log.Debugf("config: %+v", conf.Conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func confFromEnv() {
|
|
||||||
prefix := "ALIST_"
|
|
||||||
if conf.Docker {
|
|
||||||
prefix = ""
|
|
||||||
}
|
|
||||||
if err := env.Parse(conf.Conf, env.Options{
|
|
||||||
Prefix: prefix,
|
|
||||||
}); err != nil {
|
|
||||||
log.Fatalf("load config from env error: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitCron init cron
|
|
||||||
func InitCron() {
|
|
||||||
log.Infof("init cron...")
|
|
||||||
conf.Cron = cron.New()
|
|
||||||
conf.Cron.Start()
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitLog init log
|
|
||||||
func InitLog() {
|
|
||||||
if conf.Debug {
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
log.SetReportCaller(true)
|
|
||||||
}
|
|
||||||
if conf.Password || conf.Version {
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
}
|
|
||||||
log.SetFormatter(&log.TextFormatter{
|
|
||||||
//DisableColors: true,
|
|
||||||
ForceColors: true,
|
|
||||||
EnvironmentOverrideColors: true,
|
|
||||||
TimestampFormat: "2006-01-02 15:04:05",
|
|
||||||
FullTimestamp: true,
|
|
||||||
})
|
|
||||||
log.Infof("init log...")
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
|
|
||||||
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
|
|
||||||
flag.BoolVar(&conf.Version, "version", false, "print version info")
|
|
||||||
flag.BoolVar(&conf.Password, "password", false, "print current password")
|
|
||||||
flag.BoolVar(&conf.Docker, "docker", false, "is using docker")
|
|
||||||
flag.Parse()
|
|
||||||
InitLog()
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/driver/mysql"
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/driver/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
"gorm.io/gorm/schema"
|
|
||||||
log2 "log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitModel() {
|
|
||||||
log.Infof("init model...")
|
|
||||||
var err error
|
|
||||||
databaseConfig := conf.Conf.Database
|
|
||||||
newLogger := logger.New(
|
|
||||||
log2.New(os.Stdout, "\r\n", log2.LstdFlags),
|
|
||||||
logger.Config{
|
|
||||||
SlowThreshold: time.Second,
|
|
||||||
LogLevel: logger.Silent,
|
|
||||||
IgnoreRecordNotFoundError: true,
|
|
||||||
Colorful: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
gormConfig := &gorm.Config{
|
|
||||||
NamingStrategy: schema.NamingStrategy{
|
|
||||||
TablePrefix: databaseConfig.TablePrefix,
|
|
||||||
},
|
|
||||||
Logger: newLogger,
|
|
||||||
}
|
|
||||||
switch databaseConfig.Type {
|
|
||||||
case "sqlite3":
|
|
||||||
{
|
|
||||||
if !(strings.HasSuffix(databaseConfig.DBFile, ".db") && len(databaseConfig.DBFile) > 3) {
|
|
||||||
log.Fatalf("db name error.")
|
|
||||||
}
|
|
||||||
db, err := gorm.Open(sqlite.Open(databaseConfig.DBFile), gormConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to connect database:%s", err.Error())
|
|
||||||
}
|
|
||||||
conf.DB = db
|
|
||||||
}
|
|
||||||
case "mysql":
|
|
||||||
{
|
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
|
|
||||||
databaseConfig.User, databaseConfig.Password, databaseConfig.Host, databaseConfig.Port, databaseConfig.Name, databaseConfig.SslMode)
|
|
||||||
db, err := gorm.Open(mysql.Open(dsn), gormConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to connect database:%s", err.Error())
|
|
||||||
}
|
|
||||||
conf.DB = db
|
|
||||||
}
|
|
||||||
case "postgres":
|
|
||||||
{
|
|
||||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Shanghai",
|
|
||||||
databaseConfig.Host, databaseConfig.User, databaseConfig.Password, databaseConfig.Name, databaseConfig.Port, databaseConfig.SslMode)
|
|
||||||
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to connect database:%s", err.Error())
|
|
||||||
}
|
|
||||||
conf.DB = db
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
log.Fatalf("not supported database type: %s", databaseConfig.Type)
|
|
||||||
}
|
|
||||||
log.Infof("auto migrate model...")
|
|
||||||
if databaseConfig.Type == "mysql" {
|
|
||||||
err = conf.DB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").
|
|
||||||
AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{}, &model.SearchFile{})
|
|
||||||
} else {
|
|
||||||
err = conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{}, &model.SearchFile{})
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to auto migrate: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,323 +0,0 @@
|
|||||||
package bootstrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitSettings() {
|
|
||||||
log.Infof("init settings...")
|
|
||||||
|
|
||||||
err := model.SaveSetting(model.Version)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed write setting: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
settings := []model.SettingItem{
|
|
||||||
{
|
|
||||||
Key: "title",
|
|
||||||
Value: "Alist",
|
|
||||||
Description: "title",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "password",
|
|
||||||
Value: utils.RandomStr(8),
|
|
||||||
Description: "password",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "logo",
|
|
||||||
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/can_circle.svg",
|
|
||||||
Description: "logo",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "favicon",
|
|
||||||
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg",
|
|
||||||
Description: "favicon",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "icon color",
|
|
||||||
Value: "#1890ff",
|
|
||||||
Description: "icon's color",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "announcement",
|
|
||||||
Value: "This is a test announcement.",
|
|
||||||
Description: "announcement message (support markdown)",
|
|
||||||
Type: "text",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "text types",
|
|
||||||
Value: strings.Join(conf.TextTypes, ","),
|
|
||||||
Type: "string",
|
|
||||||
Description: "text type extensions",
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "audio types",
|
|
||||||
Value: strings.Join(conf.AudioTypes, ","),
|
|
||||||
Type: "string",
|
|
||||||
Description: "audio type extensions",
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "video types",
|
|
||||||
Value: strings.Join(conf.VideoTypes, ","),
|
|
||||||
Type: "string",
|
|
||||||
Description: "video type extensions",
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "d_proxy types",
|
|
||||||
Value: strings.Join(conf.DProxyTypes, ","),
|
|
||||||
Type: "string",
|
|
||||||
Description: "/d but proxy",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "hide files",
|
|
||||||
Value: "/\\/README.md/i",
|
|
||||||
Type: "text",
|
|
||||||
Description: "hide files, support RegExp, one per line",
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "music cover",
|
|
||||||
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/circle_center.svg",
|
|
||||||
Description: "music cover image",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "site beian",
|
|
||||||
Description: "chinese beian info",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "global readme url",
|
|
||||||
Description: "Default display when directory has no readme",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "pdf viewer url",
|
|
||||||
Type: "string",
|
|
||||||
Value: "https://alist-org.github.io/pdf.js/web/viewer.html?file=$url",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "autoplay video",
|
|
||||||
Value: "false",
|
|
||||||
Type: "bool",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "autoplay audio",
|
|
||||||
Value: "false",
|
|
||||||
Type: "bool",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "check parent folder",
|
|
||||||
Value: "false",
|
|
||||||
Type: "bool",
|
|
||||||
Description: "check parent folder password",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "customize head",
|
|
||||||
Value: "",
|
|
||||||
Type: "text",
|
|
||||||
Description: "Customize head, placed at the beginning of the head",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "customize body",
|
|
||||||
Value: "",
|
|
||||||
Type: "text",
|
|
||||||
Description: "Customize script, placed at the end of the body",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "home emoji",
|
|
||||||
Value: "🏠",
|
|
||||||
Type: "string",
|
|
||||||
Description: "emoji in front of home in nav",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "animation",
|
|
||||||
Value: "true",
|
|
||||||
Type: "bool",
|
|
||||||
Description: "when there are a lot of files, the animation will freeze when opening",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "check down link",
|
|
||||||
Value: "false",
|
|
||||||
Type: "bool",
|
|
||||||
Description: "check down link password, your link will be 'https://alist.com/d/filename?pw=xxx'",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "WebDAV username",
|
|
||||||
Value: "admin",
|
|
||||||
Description: "WebDAV username",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "WebDAV password",
|
|
||||||
Value: utils.RandomStr(8),
|
|
||||||
Description: "WebDAV password",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "artplayer whitelist",
|
|
||||||
Value: "*",
|
|
||||||
Description: "refer to https://artplayer.org/document/options#whitelist",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "artplayer autoSize",
|
|
||||||
Value: "true",
|
|
||||||
Description: "refer to https://artplayer.org/document/options#autosize",
|
|
||||||
Type: "bool",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "Visitor WebDAV username",
|
|
||||||
Value: "guest",
|
|
||||||
Description: "Visitor WebDAV username",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "Visitor WebDAV password",
|
|
||||||
Value: "guest",
|
|
||||||
Description: "Visitor WebDAV password",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "load type",
|
|
||||||
Value: "all",
|
|
||||||
Type: "select",
|
|
||||||
Values: "all,load more,auto load more,pagination",
|
|
||||||
Description: "Not recommended to choose to auto load more, it has bugs now",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "default page size",
|
|
||||||
Value: "30",
|
|
||||||
Type: "number",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.FRONT,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "ocr api",
|
|
||||||
Value: "https://api.nn.ci/ocr/file/json",
|
|
||||||
Description: "Used to identify verification codes",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "enable search",
|
|
||||||
Value: "false",
|
|
||||||
Type: "bool",
|
|
||||||
Access: model.PUBLIC,
|
|
||||||
Group: model.BACK,
|
|
||||||
Description: "Experimental function, not recommended as it's still under development",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "Aria2 RPC url",
|
|
||||||
Value: "http://localhost:6800/jsonrpc",
|
|
||||||
Description: "Aria2 RPC url, e.g. 'http://aria2.example.com:6800/jsonrpc'",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "Aria2 RPC secret",
|
|
||||||
Value: "",
|
|
||||||
Description: "Aria2 RPC secret, e.g. '123456'",
|
|
||||||
Type: "string",
|
|
||||||
Access: model.PRIVATE,
|
|
||||||
Group: model.BACK,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, _ := range settings {
|
|
||||||
v := settings[i]
|
|
||||||
v.Version = conf.GitTag
|
|
||||||
o, err := model.GetSettingByKey(v.Key)
|
|
||||||
if err != nil {
|
|
||||||
if err == gorm.ErrRecordNotFound {
|
|
||||||
err = model.SaveSetting(v)
|
|
||||||
if v.Key == "password" {
|
|
||||||
log.Infof("Initial password: %s", conf.C.Sprintf(v.Value))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed write setting: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Fatalf("can't get setting: %s", err.Error())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
//o.Version = conf.GitTag
|
|
||||||
//err = model.SaveSetting(*o)
|
|
||||||
v.Value = o.Value
|
|
||||||
err = model.SaveSetting(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed write setting: %s", err.Error())
|
|
||||||
}
|
|
||||||
if v.Key == "password" {
|
|
||||||
log.Infof("Your password: %s", conf.C.Sprintf(v.Value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
model.LoadSettings()
|
|
||||||
}
|
|
157
build.sh
157
build.sh
@ -1,157 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# 构建前端,在当前目录产生一个dist文件夹
|
|
||||||
BUILD_WEB() {
|
|
||||||
git clone https://github.com/alist-org/alist-web.git
|
|
||||||
cd alist-web
|
|
||||||
yarn
|
|
||||||
yarn build
|
|
||||||
sed -i -e "s/\/CDN_URL\//\//g" dist/index.html
|
|
||||||
sed -i -e "s/assets/\/assets/g" dist/index.html
|
|
||||||
rm -f dist/index.html-e
|
|
||||||
mv dist ..
|
|
||||||
cd .. || exit
|
|
||||||
rm -rf alist-web
|
|
||||||
}
|
|
||||||
|
|
||||||
CDN_WEB() {
|
|
||||||
curl -L https://github.com/alist-org/alist-web/releases/latest/download/dist.tar.gz -o dist.tar.gz
|
|
||||||
tar -zxvf dist.tar.gz
|
|
||||||
rm -f dist.tar.gz
|
|
||||||
}
|
|
||||||
|
|
||||||
# 在DOCKER中构建
|
|
||||||
BUILD_DOCKER() {
|
|
||||||
appName="alist"
|
|
||||||
builtAt="$(date +'%F %T %z')"
|
|
||||||
goVersion=$(go version | sed 's/go version //')
|
|
||||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
|
||||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
|
||||||
gitTag=$(git describe --long --tags --dirty --always)
|
|
||||||
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
|
||||||
ldflags="\
|
|
||||||
-w -s \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
|
|
||||||
"
|
|
||||||
go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter alist.go
|
|
||||||
}
|
|
||||||
|
|
||||||
BUILD() {
|
|
||||||
cd alist
|
|
||||||
appName="alist"
|
|
||||||
builtAt="$(date +'%F %T %z')"
|
|
||||||
goVersion=$(go version | sed 's/go version //')
|
|
||||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
|
||||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
|
||||||
gitTag=$(git describe --long --tags --dirty --always)
|
|
||||||
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
|
||||||
echo "build version: $gitTag"
|
|
||||||
|
|
||||||
ldflags="\
|
|
||||||
-w -s \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
|
|
||||||
"
|
|
||||||
rm -rf .git/
|
|
||||||
if [ "$1" == "release" ]; then
|
|
||||||
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
|
||||||
else
|
|
||||||
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
|
||||||
fi
|
|
||||||
mkdir -p "build"
|
|
||||||
mv alist-* build
|
|
||||||
if [ "$1" != "release" ]; then
|
|
||||||
cd build
|
|
||||||
upx -9 ./alist-linux*
|
|
||||||
upx -9 ./alist-windows*
|
|
||||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
|
||||||
cat md5.txt
|
|
||||||
cd .. || exit
|
|
||||||
fi
|
|
||||||
cd .. || exit
|
|
||||||
}
|
|
||||||
|
|
||||||
BUILD_MUSL() {
|
|
||||||
BASE="https://musl.cc/"
|
|
||||||
FILES=(x86_64-linux-musl-cross aarch64-linux-musl-cross arm-linux-musleabihf-cross mips-linux-musl-cross mips64-linux-musl-cross mips64el-linux-musl-cross mipsel-linux-musl-cross powerpc64le-linux-musl-cross s390x-linux-musl-cross)
|
|
||||||
for i in "${FILES[@]}"; do
|
|
||||||
url="${BASE}${i}.tgz"
|
|
||||||
curl -L -o "${i}.tgz" "${url}"
|
|
||||||
sudo tar xf "${i}.tgz" --strip-components 1 -C /usr/local
|
|
||||||
done
|
|
||||||
cd alist
|
|
||||||
appName="alist"
|
|
||||||
builtAt="$(date +'%F %T %z')"
|
|
||||||
goVersion=$(go version | sed 's/go version //')
|
|
||||||
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
|
|
||||||
gitCommit=$(git log --pretty=format:"%h" -1)
|
|
||||||
gitTag=$(git describe --long --tags --dirty --always)
|
|
||||||
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
|
||||||
ldflags="\
|
|
||||||
-w -s --extldflags '-static -fpic' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
|
|
||||||
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
|
|
||||||
"
|
|
||||||
OS_ARCHES=(linux-musl-amd64 linux-musl-arm64 linux-musl-arm linux-musl-mips linux-musl-mips64 linux-musl-mips64le linux-musl-mipsle linux-musl-ppc64le linux-musl-s390x)
|
|
||||||
CGO_ARGS=(x86_64-linux-musl-gcc aarch64-linux-musl-gcc arm-linux-musleabihf-gcc mips-linux-musl-gcc mips64-linux-musl-gcc mips64el-linux-musl-gcc mipsel-linux-musl-gcc powerpc64le-linux-musl-gcc s390x-linux-musl-gcc)
|
|
||||||
for i in "${!OS_ARCHES[@]}"; do
|
|
||||||
os_arch=${OS_ARCHES[$i]}
|
|
||||||
cgo_cc=${CGO_ARGS[$i]}
|
|
||||||
echo building for ${os_arch}
|
|
||||||
export GOOS=${os_arch%%-*}
|
|
||||||
export GOARCH=${os_arch##*-}
|
|
||||||
export CC=${cgo_cc}
|
|
||||||
export CGO_ENABLED=1
|
|
||||||
go build -o ./build/$appName-$os_arch -ldflags="$ldflags" -tags=jsoniter alist.go
|
|
||||||
done
|
|
||||||
cd .. || exit
|
|
||||||
}
|
|
||||||
|
|
||||||
RELEASE() {
|
|
||||||
cd alist/build
|
|
||||||
upx -9 ./alist-linux-amd64
|
|
||||||
upx -9 ./alist-windows*
|
|
||||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
|
||||||
cat md5.txt
|
|
||||||
mkdir compress
|
|
||||||
mv md5.txt compress
|
|
||||||
for i in $(find . -type f -name "$appName-linux-*"); do
|
|
||||||
tar -czvf compress/"$i".tar.gz "$i"
|
|
||||||
done
|
|
||||||
for i in $(find . -type f -name "$appName-darwin-*"); do
|
|
||||||
tar -czvf compress/"$i".tar.gz "$i"
|
|
||||||
done
|
|
||||||
for i in $(find . -type f -name "$appName-windows-*"); do
|
|
||||||
zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i"
|
|
||||||
done
|
|
||||||
cd ../.. || exit
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "$1" = "web" ]; then
|
|
||||||
BUILD_WEB
|
|
||||||
elif [ "$1" = "cdn" ]; then
|
|
||||||
CDN_WEB
|
|
||||||
elif [ "$1" = "docker" ]; then
|
|
||||||
BUILD_DOCKER
|
|
||||||
elif [ "$1" = "build" ]; then
|
|
||||||
BUILD build
|
|
||||||
elif [ "$1" = "release" ]; then
|
|
||||||
BUILD_MUSL
|
|
||||||
BUILD release
|
|
||||||
RELEASE
|
|
||||||
else
|
|
||||||
echo -e "${RED_COLOR} Parameter error ${RES}"
|
|
||||||
fi
|
|
7
cmd/alist.go
Normal file
7
cmd/alist.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Hello, world.")
|
||||||
|
}
|
@ -1,54 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
type Database struct {
|
|
||||||
Type string `json:"type" env:"DB_TYPE"`
|
|
||||||
Host string `json:"host" env:"DB_HOST"`
|
|
||||||
Port int `json:"port" env:"DB_PORT"`
|
|
||||||
User string `json:"user" env:"DB_USER"`
|
|
||||||
Password string `json:"password" env:"DB_PASS"`
|
|
||||||
Name string `json:"name" env:"DB_NAME"`
|
|
||||||
DBFile string `json:"db_file" env:"DB_FILE"`
|
|
||||||
TablePrefix string `json:"table_prefix" env:"DB_TABLE_PREFIX"`
|
|
||||||
SslMode string `json:"ssl_mode" env:"DB_SLL_MODE"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Scheme struct {
|
|
||||||
Https bool `json:"https" env:"HTTPS"`
|
|
||||||
CertFile string `json:"cert_file" env:"CERT_FILE"`
|
|
||||||
KeyFile string `json:"key_file" env:"KEY_FILE"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CacheConfig struct {
|
|
||||||
Expiration int64 `json:"expiration" env:"CACHE_EXPIRATION"`
|
|
||||||
CleanupInterval int64 `json:"cleanup_interval" env:"CLEANUP_INTERVAL"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Force bool `json:"force"`
|
|
||||||
Address string `json:"address" env:"ADDR"`
|
|
||||||
Port int `json:"port" env:"PORT"`
|
|
||||||
Assets string `json:"assets" env:"ASSETS"`
|
|
||||||
Database Database `json:"database"`
|
|
||||||
Scheme Scheme `json:"scheme"`
|
|
||||||
Cache CacheConfig `json:"cache"`
|
|
||||||
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
|
||||||
return &Config{
|
|
||||||
Address: "0.0.0.0",
|
|
||||||
Port: 5244,
|
|
||||||
Assets: "https://npm.elemecdn.com/alist-web@$version/dist",
|
|
||||||
TempDir: "data/temp",
|
|
||||||
Database: Database{
|
|
||||||
Type: "sqlite3",
|
|
||||||
Port: 0,
|
|
||||||
TablePrefix: "x_",
|
|
||||||
DBFile: "data/data.db",
|
|
||||||
},
|
|
||||||
Cache: CacheConfig{
|
|
||||||
Expiration: 60,
|
|
||||||
CleanupInterval: 120,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
const (
|
|
||||||
UNKNOWN = iota
|
|
||||||
FOLDER
|
|
||||||
OFFICE
|
|
||||||
VIDEO
|
|
||||||
AUDIO
|
|
||||||
TEXT
|
|
||||||
IMAGE
|
|
||||||
)
|
|
104
conf/var.go
104
conf/var.go
@ -1,104 +0,0 @@
|
|||||||
package conf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/eko/gocache/v2/cache"
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
BuiltAt string
|
|
||||||
GoVersion string
|
|
||||||
GitAuthor string
|
|
||||||
GitCommit string
|
|
||||||
GitTag string = "dev"
|
|
||||||
WebTag string
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ConfigFile string // config file
|
|
||||||
Conf *Config
|
|
||||||
Debug bool
|
|
||||||
Version bool
|
|
||||||
Password bool
|
|
||||||
Docker bool
|
|
||||||
|
|
||||||
DB *gorm.DB
|
|
||||||
Cache *cache.Cache
|
|
||||||
Ctx = context.TODO()
|
|
||||||
Cron *cron.Cron
|
|
||||||
|
|
||||||
C = color.New(color.FgHiBlue, color.Bold, color.BgHiWhite, color.Underline)
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
TextTypes = []string{"txt", "htm", "html", "xml", "java", "properties", "sql",
|
|
||||||
"js", "md", "json", "conf", "ini", "vue", "php", "py", "bat", "gitignore", "yml",
|
|
||||||
"go", "sh", "c", "cpp", "h", "hpp", "tsx", "vtt", "srt", "ass"}
|
|
||||||
DProxyTypes = []string{"m3u8"}
|
|
||||||
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
|
|
||||||
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv", "m4v"}
|
|
||||||
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav", "opus"}
|
|
||||||
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
|
|
||||||
)
|
|
||||||
|
|
||||||
var settingsMap = make(map[string]string)
|
|
||||||
|
|
||||||
func Set(key string, value string) {
|
|
||||||
settingsMap[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetStr(key string) string {
|
|
||||||
value, ok := settingsMap[key]
|
|
||||||
if !ok {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBool(key string) bool {
|
|
||||||
value, ok := settingsMap[key]
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return value == "true"
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetInt(key string, defaultV int) int {
|
|
||||||
value, ok := settingsMap[key]
|
|
||||||
if !ok {
|
|
||||||
return defaultV
|
|
||||||
}
|
|
||||||
v, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
return defaultV
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
LoadSettings = []string{
|
|
||||||
"check parent folder", "check down link", "WebDAV username", "WebDAV password",
|
|
||||||
"Visitor WebDAV username", "Visitor WebDAV password",
|
|
||||||
"default page size", "load type",
|
|
||||||
"ocr api", "favicon",
|
|
||||||
"enable search",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
RawIndexHtml string
|
|
||||||
ManageHtml string
|
|
||||||
IndexHtml string
|
|
||||||
Token string
|
|
||||||
|
|
||||||
//CheckParent bool
|
|
||||||
//CheckDown bool
|
|
||||||
//DavUsername string
|
|
||||||
//DavPassword string
|
|
||||||
//VisitorDavUsername string
|
|
||||||
//VisitorDavPassword string
|
|
||||||
)
|
|
@ -1,178 +0,0 @@
|
|||||||
package _23
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (driver Pan123) Login(account *model.Account) error {
|
|
||||||
url := "https://www.123pan.com/api/user/sign_in"
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
var resp Pan123TokenResp
|
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"passport": account.Username,
|
|
||||||
"password": account.Password,
|
|
||||||
}).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.Code != 200 {
|
|
||||||
err = fmt.Errorf(resp.Message)
|
|
||||||
account.Status = resp.Message
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
account.AccessToken = resp.Data.Token
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) FormatFile(file *File) *model.File {
|
|
||||||
f := &model.File{
|
|
||||||
Id: strconv.FormatInt(file.FileId, 10),
|
|
||||||
Name: file.FileName,
|
|
||||||
Size: file.Size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: file.UpdateAt,
|
|
||||||
Thumbnail: file.DownloadUrl,
|
|
||||||
}
|
|
||||||
f.Type = file.GetType()
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]File, error) {
|
|
||||||
next := "0"
|
|
||||||
res := make([]File, 0)
|
|
||||||
for next != "-1" {
|
|
||||||
var resp Pan123Files
|
|
||||||
query := map[string]string{
|
|
||||||
"driveId": "0",
|
|
||||||
"limit": "100",
|
|
||||||
"next": next,
|
|
||||||
"orderBy": account.OrderBy,
|
|
||||||
"orderDirection": account.OrderDirection,
|
|
||||||
"parentFileId": parentId,
|
|
||||||
"trashed": "false",
|
|
||||||
}
|
|
||||||
_, err := driver.Request("https://www.123pan.com/api/file/list/new",
|
|
||||||
base.Get, nil, query, nil, &resp, false, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
next = resp.Data.Next
|
|
||||||
res = append(res, resp.Data.InfoList...)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Request(url string, method int, headers, query map[string]string, data *base.Json, resp interface{}, proxy bool, account *model.Account) ([]byte, error) {
|
|
||||||
rawUrl := url
|
|
||||||
if account.APIProxyUrl != "" && proxy {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var res *resty.Response
|
|
||||||
var err error
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(url)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(url)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
body := res.Body()
|
|
||||||
code := jsoniter.Get(body, "code").ToInt()
|
|
||||||
if code != 0 {
|
|
||||||
if code == 401 {
|
|
||||||
err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driver.Request(rawUrl, method, headers, query, data, resp, proxy, account)
|
|
||||||
}
|
|
||||||
return nil, errors.New(jsoniter.Get(body, "message").ToString())
|
|
||||||
}
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Pan123) Post(url string, data base.Json, account *model.Account) ([]byte, error) {
|
|
||||||
// res, err := pan123Client.R().
|
|
||||||
// SetHeader("authorization", "Bearer "+account.AccessToken).
|
|
||||||
// SetBody(data).Post(url)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// body := res.Body()
|
|
||||||
// if jsoniter.Get(body, "code").ToInt() != 0 {
|
|
||||||
// return nil, errors.New(jsoniter.Get(body, "message").ToString())
|
|
||||||
// }
|
|
||||||
// return body, nil
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Pan123) GetFile(path string, account *model.Account) (*File, error) {
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
dir = utils.ParsePath(dir)
|
|
||||||
_, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
parentFiles_, _ := base.GetCache(dir, account)
|
|
||||||
parentFiles, _ := parentFiles_.([]File)
|
|
||||||
for _, file := range parentFiles {
|
|
||||||
if file.FileName == name {
|
|
||||||
//if file.Type != conf.FOLDER {
|
|
||||||
// return &file, err
|
|
||||||
//} else {
|
|
||||||
// return nil, base.ErrNotFile
|
|
||||||
//}
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
//func HMAC(message string, secret string) string {
|
|
||||||
// key := []byte(secret)
|
|
||||||
// h := hmac.New(sha256.New, key)
|
|
||||||
// h.Write([]byte(message))
|
|
||||||
// // fmt.Println(h.Sum(nil))
|
|
||||||
// //sha := hex.EncodeToString(h.Sum(nil))
|
|
||||||
// // fmt.Println(sha)
|
|
||||||
// //return sha
|
|
||||||
// return string(h.Sum(nil))
|
|
||||||
//}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Pan123{})
|
|
||||||
}
|
|
@ -1,460 +0,0 @@
|
|||||||
package _23
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Pan123 struct{}
|
|
||||||
|
|
||||||
func (driver Pan123) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "123Pan",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "username",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "account username/phone number",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "account password",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "name,fileId,updateAt,createAt",
|
|
||||||
Required: true,
|
|
||||||
Default: "name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "asc,desc",
|
|
||||||
Required: true,
|
|
||||||
Default: "asc",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "bool_1",
|
|
||||||
Label: "stream upload",
|
|
||||||
Type: base.TypeBool,
|
|
||||||
Description: "io stream upload (test)",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if account.RootFolder == "" {
|
|
||||||
account.RootFolder = "0"
|
|
||||||
}
|
|
||||||
err := driver.Login(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var rawFiles []File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
rawFiles, _ = cache.([]File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(rawFiles) > 0 {
|
|
||||||
_ = base.SetCache(path, rawFiles, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0, len(rawFiles))
|
|
||||||
for _, file := range rawFiles {
|
|
||||||
files = append(files, *driver.FormatFile(&file))
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
log.Debugf("%+v", args)
|
|
||||||
file, err := driver.GetFile(utils.ParsePath(args.Path), account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var resp Pan123DownResp
|
|
||||||
var headers map[string]string
|
|
||||||
if !utils.IsLocalIPAddr(args.IP) {
|
|
||||||
headers = map[string]string{
|
|
||||||
//"X-Real-IP": "1.1.1.1",
|
|
||||||
"X-Forwarded-For": args.IP,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"driveId": 0,
|
|
||||||
"etag": file.Etag,
|
|
||||||
"fileId": file.FileId,
|
|
||||||
"fileName": file.FileName,
|
|
||||||
"s3keyFlag": file.S3KeyFlag,
|
|
||||||
"size": file.Size,
|
|
||||||
"type": file.Type,
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://www.123pan.com/api/file/download_info",
|
|
||||||
base.Post, headers, nil, &data, &resp, false, account)
|
|
||||||
//_, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken).
|
|
||||||
// SetBody().Post("https://www.123pan.com/api/file/download_info")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u, err := url.Parse(resp.Data.DownloadUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
|
|
||||||
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Get(u_)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
link := base.Link{
|
|
||||||
Url: resp.Data.DownloadUrl,
|
|
||||||
}
|
|
||||||
if res.StatusCode() == 302 {
|
|
||||||
link.Url = res.Header().Get("location")
|
|
||||||
}
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("pan123 path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Pan123) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
// r.Header.Del("origin")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) MakeDir(path string, account *model.Account) error {
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
parentFile, err := driver.File(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
parentFileId, _ := strconv.Atoi(parentFile.Id)
|
|
||||||
data := base.Json{
|
|
||||||
"driveId": 0,
|
|
||||||
"etag": "",
|
|
||||||
"fileName": name,
|
|
||||||
"parentFileId": parentFileId,
|
|
||||||
"size": 0,
|
|
||||||
"type": 1,
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://www.123pan.com/api/file/upload_request",
|
|
||||||
base.Post, nil, nil, &data, nil, false, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Move(src string, dst string, account *model.Account) error {
|
|
||||||
dstDir, _ := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fileId, _ := strconv.Atoi(srcFile.Id)
|
|
||||||
|
|
||||||
dstDirFile, err := driver.File(dstDir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
parentFileId, _ := strconv.Atoi(dstDirFile.Id)
|
|
||||||
data := base.Json{
|
|
||||||
"fileIdList": []base.Json{{"FileId": fileId}},
|
|
||||||
"parentFileId": parentFileId,
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://www.123pan.com/api/file/mod_pid",
|
|
||||||
base.Post, nil, nil, &data, nil, false, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
_, dstName := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fileId, _ := strconv.Atoi(srcFile.Id)
|
|
||||||
|
|
||||||
data := base.Json{
|
|
||||||
"driveId": 0,
|
|
||||||
"fileId": fileId,
|
|
||||||
"fileName": dstName,
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://www.123pan.com/api/file/rename",
|
|
||||||
base.Post, nil, nil, &data, nil, false, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Delete(path string, account *model.Account) error {
|
|
||||||
file, err := driver.GetFile(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugln("delete 123 file: ", file)
|
|
||||||
data := base.Json{
|
|
||||||
"driveId": 0,
|
|
||||||
"operation": true,
|
|
||||||
"fileTrashInfoList": []File{*file},
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://www.123pan.com/b/api/file/trash",
|
|
||||||
base.Post, nil, nil, &data, nil, false, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Pan123) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT int64 = 10485760
|
|
||||||
var uploadFile io.Reader
|
|
||||||
h := md5.New()
|
|
||||||
if account.Bool1 && file.GetSize() > uint64(DEFAULT) {
|
|
||||||
// 只计算前10MIB
|
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, DEFAULT))
|
|
||||||
if n, err := io.CopyN(io.MultiWriter(buf, h), file, DEFAULT); err != io.EOF && n == 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// 增加额外参数防止MD5碰撞
|
|
||||||
h.Write([]byte(file.Name))
|
|
||||||
num := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(num, file.Size)
|
|
||||||
h.Write(num)
|
|
||||||
// 拼装
|
|
||||||
uploadFile = io.MultiReader(buf, file)
|
|
||||||
} else {
|
|
||||||
// 计算完整文件MD5
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err = io.Copy(io.MultiWriter(tempFile, h), file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uploadFile = tempFile
|
|
||||||
}
|
|
||||||
etag := hex.EncodeToString(h.Sum(nil))
|
|
||||||
data := base.Json{
|
|
||||||
"driveId": 0,
|
|
||||||
"duplicate": 2, // 2->覆盖 1->重命名 0->默认
|
|
||||||
"etag": etag,
|
|
||||||
"fileName": file.GetFileName(),
|
|
||||||
"parentFileId": parentFile.Id,
|
|
||||||
"size": file.GetSize(),
|
|
||||||
"type": 0,
|
|
||||||
}
|
|
||||||
var resp UploadResp
|
|
||||||
_, err = driver.Request("https://www.123pan.com/api/file/upload_request",
|
|
||||||
base.Post, map[string]string{"app-version": "1.1"}, nil, &data, &resp, false, account)
|
|
||||||
//res, err := driver.Post("https://www.123pan.com/api/file/upload_request", data, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.Data.Key == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cfg := &aws.Config{
|
|
||||||
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
|
|
||||||
Region: aws.String("123pan"),
|
|
||||||
Endpoint: aws.String("file.123pan.com"),
|
|
||||||
S3ForcePathStyle: aws.Bool(true),
|
|
||||||
}
|
|
||||||
s, err := session.NewSession(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uploader := s3manager.NewUploader(s)
|
|
||||||
input := &s3manager.UploadInput{
|
|
||||||
Bucket: &resp.Data.Bucket,
|
|
||||||
Key: &resp.Data.Key,
|
|
||||||
Body: uploadFile,
|
|
||||||
}
|
|
||||||
_, err = uploader.Upload(input)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://www.123pan.com/api/file/upload_complete", base.Post, nil, nil, &base.Json{
|
|
||||||
"fileId": resp.Data.FileId,
|
|
||||||
}, nil, false, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
//type UploadResp struct {
|
|
||||||
// XMLName xml.Name `xml:"InitiateMultipartUploadResult"`
|
|
||||||
// Bucket string `xml:"Bucket"`
|
|
||||||
// Key string `xml:"Key"`
|
|
||||||
// UploadId string `xml:"UploadId"`
|
|
||||||
//}
|
|
||||||
|
|
||||||
// TODO unfinished
|
|
||||||
//func (driver Pan123) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
// return base.ErrNotImplement
|
|
||||||
// parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// if !parentFile.IsDir() {
|
|
||||||
// return base.ErrNotFolder
|
|
||||||
// }
|
|
||||||
// parentFileId, _ := strconv.Atoi(parentFile.Id)
|
|
||||||
// data := base.Json{
|
|
||||||
// "driveId": 0,
|
|
||||||
// "duplicate": true,
|
|
||||||
// "etag": RandStr(32), //maybe file's md5
|
|
||||||
// "fileName": file.GetFileName(),
|
|
||||||
// "parentFileId": parentFileId,
|
|
||||||
// "size": file.GetSize(),
|
|
||||||
// "type": 0,
|
|
||||||
// }
|
|
||||||
// res, err := driver.Request("https://www.123pan.com/api/file/upload_request",
|
|
||||||
// base.Post, nil, nil, &data, nil, false, account)
|
|
||||||
// //res, err := driver.Post("https://www.123pan.com/api/file/upload_request", data, account)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// baseUrl := fmt.Sprintf("https://file.123pan.com/%s/%s", jsoniter.Get(res, "data.Bucket").ToString(), jsoniter.Get(res, "data.Key").ToString())
|
|
||||||
// var resp UploadResp
|
|
||||||
// kSecret := jsoniter.Get(res, "data.SecretAccessKey").ToString()
|
|
||||||
// nowTimeStr := time.Now().String()
|
|
||||||
// Date := strings.ReplaceAll(strings.Split(nowTimeStr, "T")[0], "-", "")
|
|
||||||
//
|
|
||||||
// StringToSign := fmt.Sprintf("%s\n%s\n%s\n%s",
|
|
||||||
// "AWS4-HMAC-SHA256",
|
|
||||||
// nowTimeStr,
|
|
||||||
// fmt.Sprintf("%s/us-east-1/s3/aws4_request", Date),
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// kDate := HMAC("AWS4"+kSecret, Date)
|
|
||||||
// kRegion := HMAC(kDate, "us-east-1")
|
|
||||||
// kService := HMAC(kRegion, "s3")
|
|
||||||
// kSigning := HMAC(kService, "aws4_request")
|
|
||||||
// _, err = base.RestyClient.R().SetResult(&resp).SetHeaders(map[string]string{
|
|
||||||
// "Authorization": fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent, Signature=%s",
|
|
||||||
// jsoniter.Get(res, "data.AccessKeyId"),
|
|
||||||
// Date,
|
|
||||||
// hex.EncodeToString([]byte(HMAC(StringToSign, kSigning)))),
|
|
||||||
// "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD",
|
|
||||||
// "X-Amz-Date": nowTimeStr,
|
|
||||||
// "x-amz-security-token": jsoniter.Get(res, "data.SessionToken").ToString(),
|
|
||||||
// }).Post(fmt.Sprintf("%s?uploads", baseUrl))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// return base.ErrNotImplement
|
|
||||||
//}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Pan123)(nil)
|
|
@ -1,74 +0,0 @@
|
|||||||
package _23
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
FileName string `json:"FileName"`
|
|
||||||
Size int64 `json:"Size"`
|
|
||||||
UpdateAt *time.Time `json:"UpdateAt"`
|
|
||||||
FileId int64 `json:"FileId"`
|
|
||||||
Type int `json:"Type"`
|
|
||||||
Etag string `json:"Etag"`
|
|
||||||
S3KeyFlag string `json:"S3KeyFlag"`
|
|
||||||
DownloadUrl string `json:"DownloadUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) GetSize() uint64 {
|
|
||||||
return uint64(f.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) GetName() string {
|
|
||||||
return f.FileName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) GetType() int {
|
|
||||||
if f.Type == 1 {
|
|
||||||
return conf.FOLDER
|
|
||||||
}
|
|
||||||
return utils.GetFileType(path.Ext(f.FileName))
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseResp struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pan123TokenResp struct {
|
|
||||||
BaseResp
|
|
||||||
Data struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pan123Files struct {
|
|
||||||
BaseResp
|
|
||||||
Data struct {
|
|
||||||
InfoList []File `json:"InfoList"`
|
|
||||||
Next string `json:"Next"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pan123DownResp struct {
|
|
||||||
BaseResp
|
|
||||||
Data struct {
|
|
||||||
DownloadUrl string `json:"DownloadUrl"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadResp struct {
|
|
||||||
BaseResp
|
|
||||||
Data struct {
|
|
||||||
AccessKeyId string `json:"AccessKeyId"`
|
|
||||||
Bucket string `json:"Bucket"`
|
|
||||||
Key string `json:"Key"`
|
|
||||||
SecretAccessKey string `json:"SecretAccessKey"`
|
|
||||||
SessionToken string `json:"SessionToken"`
|
|
||||||
FileId int64 `json:"FileId"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
@ -1,175 +0,0 @@
|
|||||||
package _39
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (driver Cloud139) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
url := "https://yun.139.com" + pathname
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
randStr := utils.RandomStr(16)
|
|
||||||
ts := time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
log.Debugf("%+v", data)
|
|
||||||
body, err := utils.Json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sign := calSign(string(body), ts, randStr)
|
|
||||||
svcType := "1"
|
|
||||||
if isFamily(account) {
|
|
||||||
svcType = "2"
|
|
||||||
}
|
|
||||||
req.SetHeaders(map[string]string{
|
|
||||||
"Accept": "application/json, text/plain, */*",
|
|
||||||
"CMS-DEVICE": "default",
|
|
||||||
"Cookie": account.AccessToken,
|
|
||||||
"mcloud-channel": "1000101",
|
|
||||||
"mcloud-client": "10701",
|
|
||||||
//"mcloud-route": "001",
|
|
||||||
"mcloud-sign": fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
|
|
||||||
//"mcloud-skey":"",
|
|
||||||
"mcloud-version": "6.6.0",
|
|
||||||
"Origin": "https://yun.139.com",
|
|
||||||
"Referer": "https://yun.139.com/w/",
|
|
||||||
"x-DeviceInfo": "||9|6.6.0|chrome|95.0.4638.69|uwIy75obnsRPIwlJSd7D9GhUvFwG96ce||macos 10.15.2||zh-CN|||",
|
|
||||||
"x-huawei-channelSrc": "10000034",
|
|
||||||
"x-inner-ntwk": "2",
|
|
||||||
"x-m4c-caller": "PC",
|
|
||||||
"x-m4c-src": "10002",
|
|
||||||
"x-SvcType": svcType,
|
|
||||||
})
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
var e BaseResp
|
|
||||||
//var err error
|
|
||||||
var res *resty.Response
|
|
||||||
req.SetResult(&e)
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(url)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(url)
|
|
||||||
case base.Delete:
|
|
||||||
res, err = req.Delete(url)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(url)
|
|
||||||
case base.Put:
|
|
||||||
res, err = req.Put(url)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugln(res.String())
|
|
||||||
if !e.Success {
|
|
||||||
return nil, errors.New(e.Message)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
err = utils.Json.Unmarshal(res.Body(), resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Post(pathname string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
return driver.Request(pathname, base.Post, nil, nil, nil, data, resp, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) GetFiles(catalogID string, account *model.Account) ([]model.File, error) {
|
|
||||||
start := 0
|
|
||||||
limit := 100
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
for {
|
|
||||||
data := base.Json{
|
|
||||||
"catalogID": catalogID,
|
|
||||||
"sortDirection": 1,
|
|
||||||
"startNumber": start + 1,
|
|
||||||
"endNumber": start + limit,
|
|
||||||
"filterType": 0,
|
|
||||||
"catalogSortType": 0,
|
|
||||||
"contentSortType": 0,
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var resp GetDiskResp
|
|
||||||
_, err := driver.Post("/orchestration/personalCloud/catalog/v1.0/getDisk", data, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, catalog := range resp.Data.GetDiskResult.CatalogList {
|
|
||||||
f := model.File{
|
|
||||||
Id: catalog.CatalogID,
|
|
||||||
Name: catalog.CatalogName,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: getTime(catalog.UpdateTime),
|
|
||||||
}
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
for _, content := range resp.Data.GetDiskResult.ContentList {
|
|
||||||
f := model.File{
|
|
||||||
Id: content.ContentID,
|
|
||||||
Name: content.ContentName,
|
|
||||||
Size: content.ContentSize,
|
|
||||||
Type: utils.GetFileType(path.Ext(content.ContentName)),
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: getTime(content.UpdateTime),
|
|
||||||
Thumbnail: content.ThumbnailURL,
|
|
||||||
//Thumbnail: content.BigthumbnailURL,
|
|
||||||
}
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
if start+limit >= resp.Data.GetDiskResult.NodeCount {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
start += limit
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) GetLink(contentId string, account *model.Account) (string, error) {
|
|
||||||
data := base.Json{
|
|
||||||
"appName": "",
|
|
||||||
"contentID": contentId,
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
res, err := driver.Post("/orchestration/personalCloud/uploadAndDownload/v1.0/downloadRequest",
|
|
||||||
data, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Cloud139{})
|
|
||||||
}
|
|
@ -1,457 +0,0 @@
|
|||||||
package _39
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Cloud139 struct{}
|
|
||||||
|
|
||||||
func (driver Cloud139) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "139Yun",
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "phone",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "phone number",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "access_token",
|
|
||||||
Label: "Cookie",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "Unknown expiration time",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "139yun type",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Values: "Personal,Family",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "site_id",
|
|
||||||
Label: "cloud_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err := driver.Request("/orchestration/personalCloud/user/v1.0/qryUserExternInfo", base.Post, nil, nil, nil, base.Json{
|
|
||||||
"qryUserExternInfoReq": base.Json{
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isFamily(account) {
|
|
||||||
files, err = driver.familyGetFiles(file.Id, account)
|
|
||||||
} else {
|
|
||||||
files, err = driver.GetFiles(file.Id, account)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var u string
|
|
||||||
//if isFamily(account) {
|
|
||||||
// u, err = driver.familyLink(file.Id, account)
|
|
||||||
//} else {
|
|
||||||
u, err = driver.GetLink(file.Id, account)
|
|
||||||
//}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &base.Link{Url: u}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("139 path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Cloud139) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) MakeDir(path string, account *model.Account) error {
|
|
||||||
parentFile, err := driver.File(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"createCatalogExtReq": base.Json{
|
|
||||||
"parentCatalogID": parentFile.Id,
|
|
||||||
"newCatalogName": utils.Base(path),
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pathname := "/orchestration/personalCloud/catalog/v1.0/createCatalogExt"
|
|
||||||
if isFamily(account) {
|
|
||||||
data = base.Json{
|
|
||||||
"cloudID": account.SiteId,
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
"docLibName": utils.Base(path),
|
|
||||||
}
|
|
||||||
pathname = "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
|
|
||||||
}
|
|
||||||
_, err = driver.Post(pathname,
|
|
||||||
data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Move(src string, dst string, account *model.Account) error {
|
|
||||||
if isFamily(account) {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var contentInfoList []string
|
|
||||||
var catalogInfoList []string
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
catalogInfoList = append(catalogInfoList, srcFile.Id)
|
|
||||||
} else {
|
|
||||||
contentInfoList = append(contentInfoList, srcFile.Id)
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"createBatchOprTaskReq": base.Json{
|
|
||||||
"taskType": 3,
|
|
||||||
"actionType": "304",
|
|
||||||
"taskInfo": base.Json{
|
|
||||||
"contentInfoList": contentInfoList,
|
|
||||||
"catalogInfoList": catalogInfoList,
|
|
||||||
"newCatalogID": dstParentFile.Id,
|
|
||||||
},
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
|
||||||
_, err = driver.Post(pathname, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
if isFamily(account) {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var data base.Json
|
|
||||||
var pathname string
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
data = base.Json{
|
|
||||||
"catalogID": srcFile.Id,
|
|
||||||
"catalogName": utils.Base(dst),
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pathname = "/orchestration/personalCloud/catalog/v1.0/updateCatalogInfo"
|
|
||||||
} else {
|
|
||||||
data = base.Json{
|
|
||||||
"contentID": srcFile.Id,
|
|
||||||
"contentName": utils.Base(dst),
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pathname = "/orchestration/personalCloud/catalog/v1.0/updateContentInfo"
|
|
||||||
}
|
|
||||||
_, err = driver.Post(pathname, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
if isFamily(account) {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var contentInfoList []string
|
|
||||||
var catalogInfoList []string
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
catalogInfoList = append(catalogInfoList, srcFile.Id)
|
|
||||||
} else {
|
|
||||||
contentInfoList = append(contentInfoList, srcFile.Id)
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"createBatchOprTaskReq": base.Json{
|
|
||||||
"taskType": 3,
|
|
||||||
"actionType": 309,
|
|
||||||
"taskInfo": base.Json{
|
|
||||||
"contentInfoList": contentInfoList,
|
|
||||||
"catalogInfoList": catalogInfoList,
|
|
||||||
"newCatalogID": dstParentFile.Id,
|
|
||||||
},
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
|
||||||
_, err = driver.Post(pathname, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Delete(path string, account *model.Account) error {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var contentInfoList []string
|
|
||||||
var catalogInfoList []string
|
|
||||||
if file.IsDir() {
|
|
||||||
catalogInfoList = append(catalogInfoList, file.Id)
|
|
||||||
} else {
|
|
||||||
contentInfoList = append(contentInfoList, file.Id)
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"createBatchOprTaskReq": base.Json{
|
|
||||||
"taskType": 2,
|
|
||||||
"actionType": 201,
|
|
||||||
"taskInfo": base.Json{
|
|
||||||
"newCatalogID": "",
|
|
||||||
"contentInfoList": contentInfoList,
|
|
||||||
"catalogInfoList": catalogInfoList,
|
|
||||||
},
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
|
||||||
if isFamily(account) {
|
|
||||||
data = base.Json{
|
|
||||||
"catalogList": catalogInfoList,
|
|
||||||
"contentList": contentInfoList,
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
"sourceCatalogType": 1002,
|
|
||||||
"taskType": 2,
|
|
||||||
}
|
|
||||||
pathname = "/orchestration/familyCloud/batchOprTask/v1.0/createBatchOprTask"
|
|
||||||
}
|
|
||||||
_, err = driver.Post(pathname, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"manualRename": 2,
|
|
||||||
"operation": 0,
|
|
||||||
"fileCount": 1,
|
|
||||||
"totalSize": file.Size,
|
|
||||||
"uploadContentList": []base.Json{{
|
|
||||||
"contentName": file.Name,
|
|
||||||
"contentSize": file.Size,
|
|
||||||
// "digest": "5a3231986ce7a6b46e408612d385bafa"
|
|
||||||
}},
|
|
||||||
"parentCatalogID": parentFile.Id,
|
|
||||||
"newCatalogName": "",
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
pathname := "/orchestration/personalCloud/uploadAndDownload/v1.0/pcUploadFileRequest"
|
|
||||||
if isFamily(account) {
|
|
||||||
data = newJson(base.Json{
|
|
||||||
"fileCount": 1,
|
|
||||||
"manualRename": 2,
|
|
||||||
"operation": 0,
|
|
||||||
"path": "",
|
|
||||||
"seqNo": "",
|
|
||||||
"totalSize": file.Size,
|
|
||||||
"uploadContentList": []base.Json{{
|
|
||||||
"contentName": file.Name,
|
|
||||||
"contentSize": file.Size,
|
|
||||||
// "digest": "5a3231986ce7a6b46e408612d385bafa"
|
|
||||||
}},
|
|
||||||
}, account)
|
|
||||||
pathname = "/orchestration/familyCloud/content/v1.0/getFileUploadURL"
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
var resp UploadResp
|
|
||||||
_, err = driver.Post(pathname, data, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var Default uint64 = 10485760
|
|
||||||
part := int(math.Ceil(float64(file.Size) / float64(Default)))
|
|
||||||
var start uint64 = 0
|
|
||||||
for i := 0; i < part; i++ {
|
|
||||||
byteSize := file.Size - start
|
|
||||||
if byteSize > Default {
|
|
||||||
byteSize = Default
|
|
||||||
}
|
|
||||||
byteData := make([]byte, byteSize)
|
|
||||||
_, err = io.ReadFull(file, byteData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("POST", resp.Data.UploadResult.RedirectionURL, bytes.NewBuffer(byteData))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
headers := map[string]string{
|
|
||||||
"Accept": "*/*",
|
|
||||||
"Content-Type": "text/plain;name=" + unicode(file.Name),
|
|
||||||
"contentSize": strconv.FormatUint(file.Size, 10),
|
|
||||||
"range": fmt.Sprintf("bytes=%d-%d", start, start+byteSize-1),
|
|
||||||
"content-length": strconv.FormatUint(byteSize, 10),
|
|
||||||
"uploadtaskID": resp.Data.UploadResult.UploadTaskID,
|
|
||||||
"rangeType": "0",
|
|
||||||
"Referer": "https://yun.139.com/",
|
|
||||||
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44",
|
|
||||||
"x-SvcType": "1",
|
|
||||||
}
|
|
||||||
for k, v := range headers {
|
|
||||||
req.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
res, err := base.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugf("%+v", res)
|
|
||||||
res.Body.Close()
|
|
||||||
start += byteSize
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Cloud139)(nil)
|
|
@ -1,74 +0,0 @@
|
|||||||
package _39
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (driver Cloud139) familyGetFiles(catalogID string, account *model.Account) ([]model.File, error) {
|
|
||||||
pageNum := 1
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
for {
|
|
||||||
data := newJson(base.Json{
|
|
||||||
"catalogID": catalogID,
|
|
||||||
"contentSortType": 0,
|
|
||||||
"pageInfo": base.Json{
|
|
||||||
"pageNum": pageNum,
|
|
||||||
"pageSize": 100,
|
|
||||||
},
|
|
||||||
"sortDirection": 1,
|
|
||||||
}, account)
|
|
||||||
|
|
||||||
var resp QueryContentListResp
|
|
||||||
_, err := driver.Post("/orchestration/familyCloud/content/v1.0/queryContentList", data, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, catalog := range resp.Data.CloudCatalogList {
|
|
||||||
f := model.File{
|
|
||||||
Id: catalog.CatalogID,
|
|
||||||
Name: catalog.CatalogName,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: getTime(catalog.LastUpdateTime),
|
|
||||||
}
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
for _, content := range resp.Data.CloudContentList {
|
|
||||||
f := model.File{
|
|
||||||
Id: content.ContentID,
|
|
||||||
Name: content.ContentName,
|
|
||||||
Size: content.ContentSize,
|
|
||||||
Type: utils.GetFileType(path.Ext(content.ContentName)),
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: getTime(content.LastUpdateTime),
|
|
||||||
Thumbnail: content.ThumbnailURL,
|
|
||||||
//Thumbnail: content.BigthumbnailURL,
|
|
||||||
}
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
if 100*pageNum > resp.Data.TotalCount {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pageNum++
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud139) familyLink(contentId string, account *model.Account) (string, error) {
|
|
||||||
data := newJson(base.Json{
|
|
||||||
"contentID": contentId,
|
|
||||||
//"path":"",
|
|
||||||
}, account)
|
|
||||||
res, err := driver.Post("/orchestration/familyCloud/content/v1.0/getFileDownLoadURL",
|
|
||||||
data, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
|
|
||||||
}
|
|
@ -1,187 +0,0 @@
|
|||||||
package _39
|
|
||||||
|
|
||||||
type BaseResp struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Catalog struct {
|
|
||||||
CatalogID string `json:"catalogID"`
|
|
||||||
CatalogName string `json:"catalogName"`
|
|
||||||
//CatalogType int `json:"catalogType"`
|
|
||||||
//CreateTime string `json:"createTime"`
|
|
||||||
UpdateTime string `json:"updateTime"`
|
|
||||||
//IsShared bool `json:"isShared"`
|
|
||||||
//CatalogLevel int `json:"catalogLevel"`
|
|
||||||
//ShareDoneeCount int `json:"shareDoneeCount"`
|
|
||||||
//OpenType int `json:"openType"`
|
|
||||||
//ParentCatalogID string `json:"parentCatalogId"`
|
|
||||||
//DirEtag int `json:"dirEtag"`
|
|
||||||
//Tombstoned int `json:"tombstoned"`
|
|
||||||
//ProxyID interface{} `json:"proxyID"`
|
|
||||||
//Moved int `json:"moved"`
|
|
||||||
//IsFixedDir int `json:"isFixedDir"`
|
|
||||||
//IsSynced interface{} `json:"isSynced"`
|
|
||||||
//Owner string `json:"owner"`
|
|
||||||
//Modifier interface{} `json:"modifier"`
|
|
||||||
//Path string `json:"path"`
|
|
||||||
//ShareType int `json:"shareType"`
|
|
||||||
//SoftLink interface{} `json:"softLink"`
|
|
||||||
//ExtProp1 interface{} `json:"extProp1"`
|
|
||||||
//ExtProp2 interface{} `json:"extProp2"`
|
|
||||||
//ExtProp3 interface{} `json:"extProp3"`
|
|
||||||
//ExtProp4 interface{} `json:"extProp4"`
|
|
||||||
//ExtProp5 interface{} `json:"extProp5"`
|
|
||||||
//ETagOprType int `json:"ETagOprType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Content struct {
|
|
||||||
ContentID string `json:"contentID"`
|
|
||||||
ContentName string `json:"contentName"`
|
|
||||||
//ContentSuffix string `json:"contentSuffix"`
|
|
||||||
ContentSize int64 `json:"contentSize"`
|
|
||||||
//ContentDesc string `json:"contentDesc"`
|
|
||||||
//ContentType int `json:"contentType"`
|
|
||||||
//ContentOrigin int `json:"contentOrigin"`
|
|
||||||
UpdateTime string `json:"updateTime"`
|
|
||||||
//CommentCount int `json:"commentCount"`
|
|
||||||
ThumbnailURL string `json:"thumbnailURL"`
|
|
||||||
//BigthumbnailURL string `json:"bigthumbnailURL"`
|
|
||||||
//PresentURL string `json:"presentURL"`
|
|
||||||
//PresentLURL string `json:"presentLURL"`
|
|
||||||
//PresentHURL string `json:"presentHURL"`
|
|
||||||
//ContentTAGList interface{} `json:"contentTAGList"`
|
|
||||||
//ShareDoneeCount int `json:"shareDoneeCount"`
|
|
||||||
//Safestate int `json:"safestate"`
|
|
||||||
//Transferstate int `json:"transferstate"`
|
|
||||||
//IsFocusContent int `json:"isFocusContent"`
|
|
||||||
//UpdateShareTime interface{} `json:"updateShareTime"`
|
|
||||||
//UploadTime string `json:"uploadTime"`
|
|
||||||
//OpenType int `json:"openType"`
|
|
||||||
//AuditResult int `json:"auditResult"`
|
|
||||||
//ParentCatalogID string `json:"parentCatalogId"`
|
|
||||||
//Channel string `json:"channel"`
|
|
||||||
//GeoLocFlag string `json:"geoLocFlag"`
|
|
||||||
//Digest string `json:"digest"`
|
|
||||||
//Version string `json:"version"`
|
|
||||||
//FileEtag string `json:"fileEtag"`
|
|
||||||
//FileVersion string `json:"fileVersion"`
|
|
||||||
//Tombstoned int `json:"tombstoned"`
|
|
||||||
//ProxyID string `json:"proxyID"`
|
|
||||||
//Moved int `json:"moved"`
|
|
||||||
//MidthumbnailURL string `json:"midthumbnailURL"`
|
|
||||||
//Owner string `json:"owner"`
|
|
||||||
//Modifier string `json:"modifier"`
|
|
||||||
//ShareType int `json:"shareType"`
|
|
||||||
//ExtInfo struct {
|
|
||||||
// Uploader string `json:"uploader"`
|
|
||||||
// Address string `json:"address"`
|
|
||||||
//} `json:"extInfo"`
|
|
||||||
//Exif struct {
|
|
||||||
// CreateTime string `json:"createTime"`
|
|
||||||
// Longitude interface{} `json:"longitude"`
|
|
||||||
// Latitude interface{} `json:"latitude"`
|
|
||||||
// LocalSaveTime interface{} `json:"localSaveTime"`
|
|
||||||
//} `json:"exif"`
|
|
||||||
//CollectionFlag interface{} `json:"collectionFlag"`
|
|
||||||
//TreeInfo interface{} `json:"treeInfo"`
|
|
||||||
//IsShared bool `json:"isShared"`
|
|
||||||
//ETagOprType int `json:"ETagOprType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetDiskResp struct {
|
|
||||||
BaseResp
|
|
||||||
Data struct {
|
|
||||||
Result struct {
|
|
||||||
ResultCode string `json:"resultCode"`
|
|
||||||
ResultDesc interface{} `json:"resultDesc"`
|
|
||||||
} `json:"result"`
|
|
||||||
GetDiskResult struct {
|
|
||||||
ParentCatalogID string `json:"parentCatalogID"`
|
|
||||||
NodeCount int `json:"nodeCount"`
|
|
||||||
CatalogList []Catalog `json:"catalogList"`
|
|
||||||
ContentList []Content `json:"contentList"`
|
|
||||||
IsCompleted int `json:"isCompleted"`
|
|
||||||
} `json:"getDiskResult"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadResp struct {
|
|
||||||
BaseResp
|
|
||||||
Data struct {
|
|
||||||
Result struct {
|
|
||||||
ResultCode string `json:"resultCode"`
|
|
||||||
ResultDesc interface{} `json:"resultDesc"`
|
|
||||||
} `json:"result"`
|
|
||||||
UploadResult struct {
|
|
||||||
UploadTaskID string `json:"uploadTaskID"`
|
|
||||||
RedirectionURL string `json:"redirectionUrl"`
|
|
||||||
NewContentIDList []struct {
|
|
||||||
ContentID string `json:"contentID"`
|
|
||||||
ContentName string `json:"contentName"`
|
|
||||||
IsNeedUpload string `json:"isNeedUpload"`
|
|
||||||
FileEtag int64 `json:"fileEtag"`
|
|
||||||
FileVersion int64 `json:"fileVersion"`
|
|
||||||
OverridenFlag int `json:"overridenFlag"`
|
|
||||||
} `json:"newContentIDList"`
|
|
||||||
CatalogIDList interface{} `json:"catalogIDList"`
|
|
||||||
IsSlice interface{} `json:"isSlice"`
|
|
||||||
} `json:"uploadResult"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudContent struct {
|
|
||||||
ContentID string `json:"contentID"`
|
|
||||||
//Modifier string `json:"modifier"`
|
|
||||||
//Nickname string `json:"nickname"`
|
|
||||||
//CloudNickName string `json:"cloudNickName"`
|
|
||||||
ContentName string `json:"contentName"`
|
|
||||||
//ContentType int `json:"contentType"`
|
|
||||||
//ContentSuffix string `json:"contentSuffix"`
|
|
||||||
ContentSize int64 `json:"contentSize"`
|
|
||||||
//ContentDesc string `json:"contentDesc"`
|
|
||||||
//CreateTime string `json:"createTime"`
|
|
||||||
//Shottime interface{} `json:"shottime"`
|
|
||||||
LastUpdateTime string `json:"lastUpdateTime"`
|
|
||||||
ThumbnailURL string `json:"thumbnailURL"`
|
|
||||||
//MidthumbnailURL string `json:"midthumbnailURL"`
|
|
||||||
//BigthumbnailURL string `json:"bigthumbnailURL"`
|
|
||||||
//PresentURL string `json:"presentURL"`
|
|
||||||
//PresentLURL string `json:"presentLURL"`
|
|
||||||
//PresentHURL string `json:"presentHURL"`
|
|
||||||
//ParentCatalogID string `json:"parentCatalogID"`
|
|
||||||
//Uploader string `json:"uploader"`
|
|
||||||
//UploaderNickName string `json:"uploaderNickName"`
|
|
||||||
//TreeInfo interface{} `json:"treeInfo"`
|
|
||||||
//UpdateTime interface{} `json:"updateTime"`
|
|
||||||
//ExtInfo struct {
|
|
||||||
// Uploader string `json:"uploader"`
|
|
||||||
//} `json:"extInfo"`
|
|
||||||
//EtagOprType interface{} `json:"etagOprType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloudCatalog struct {
|
|
||||||
CatalogID string `json:"catalogID"`
|
|
||||||
CatalogName string `json:"catalogName"`
|
|
||||||
//CloudID string `json:"cloudID"`
|
|
||||||
//CreateTime string `json:"createTime"`
|
|
||||||
LastUpdateTime string `json:"lastUpdateTime"`
|
|
||||||
//Creator string `json:"creator"`
|
|
||||||
//CreatorNickname string `json:"creatorNickname"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type QueryContentListResp struct {
|
|
||||||
BaseResp
|
|
||||||
Data struct {
|
|
||||||
Result struct {
|
|
||||||
ResultCode string `json:"resultCode"`
|
|
||||||
ResultDesc string `json:"resultDesc"`
|
|
||||||
} `json:"result"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
CloudContentList []CloudContent `json:"cloudContentList"`
|
|
||||||
CloudCatalogList []CloudCatalog `json:"cloudCatalogList"`
|
|
||||||
TotalCount int `json:"totalCount"`
|
|
||||||
RecallContent interface{} `json:"recallContent"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package _39
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encodeURIComponent(str string) string {
|
|
||||||
r := url.QueryEscape(str)
|
|
||||||
r = strings.Replace(r, "+", "%20", -1)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func calSign(body, ts, randStr string) string {
|
|
||||||
body = strings.ReplaceAll(body, "\n", "")
|
|
||||||
body = strings.ReplaceAll(body, " ", "")
|
|
||||||
body = encodeURIComponent(body)
|
|
||||||
strs := strings.Split(body, "")
|
|
||||||
sort.Strings(strs)
|
|
||||||
body = strings.Join(strs, "")
|
|
||||||
body = base64.StdEncoding.EncodeToString([]byte(body))
|
|
||||||
res := utils.GetMD5Encode(body) + utils.GetMD5Encode(ts+":"+randStr)
|
|
||||||
res = strings.ToUpper(utils.GetMD5Encode(res))
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTime(t string) *time.Time {
|
|
||||||
stamp, _ := time.ParseInLocation("20060102150405", t, time.Local)
|
|
||||||
return &stamp
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFamily(account *model.Account) bool {
|
|
||||||
return account.InternalType == "Family"
|
|
||||||
}
|
|
||||||
|
|
||||||
func unicode(str string) string {
|
|
||||||
textQuoted := strconv.QuoteToASCII(str)
|
|
||||||
textUnquoted := textQuoted[1 : len(textQuoted)-1]
|
|
||||||
return textUnquoted
|
|
||||||
}
|
|
||||||
|
|
||||||
func MergeMap(mObj ...map[string]interface{}) map[string]interface{} {
|
|
||||||
newObj := map[string]interface{}{}
|
|
||||||
for _, m := range mObj {
|
|
||||||
for k, v := range m {
|
|
||||||
newObj[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newObj
|
|
||||||
}
|
|
||||||
|
|
||||||
func newJson(data map[string]interface{}, account *model.Account) map[string]interface{} {
|
|
||||||
common := map[string]interface{}{
|
|
||||||
"catalogType": 3,
|
|
||||||
"cloudID": account.SiteId,
|
|
||||||
"cloudType": 1,
|
|
||||||
"commonAccountInfo": base.Json{
|
|
||||||
"account": account.Username,
|
|
||||||
"accountType": 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return MergeMap(data, common)
|
|
||||||
}
|
|
@ -1,620 +0,0 @@
|
|||||||
package _89
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var client189Map map[string]*resty.Client
|
|
||||||
var infoMap = make(map[string]Rsa)
|
|
||||||
|
|
||||||
func (driver Cloud189) getClient(account *model.Account) (*resty.Client, error) {
|
|
||||||
client, ok := client189Map[account.Name]
|
|
||||||
if ok {
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client, ok = client189Map[account.Name]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("can't find [%s] client", account.Name)
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
|
||||||
f := &model.File{
|
|
||||||
Id: strconv.FormatInt(file.Id, 10),
|
|
||||||
Name: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: nil,
|
|
||||||
Thumbnail: file.Icon.SmallUrl,
|
|
||||||
Url: file.Url,
|
|
||||||
}
|
|
||||||
loc, _ := time.LoadLocation("Local")
|
|
||||||
lastOpTime, err := time.ParseInLocation("2006-01-02 15:04:05", file.LastOpTime, loc)
|
|
||||||
if err == nil {
|
|
||||||
f.UpdatedAt = &lastOpTime
|
|
||||||
}
|
|
||||||
if file.Size == -1 {
|
|
||||||
f.Size = 0
|
|
||||||
}
|
|
||||||
f.Type = file.GetType()
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) {
|
|
||||||
// dir, name := filepath.Split(path)
|
|
||||||
// dir = utils.ParsePath(dir)
|
|
||||||
// _, _, err := c.ParentPath(dir, account)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
|
|
||||||
// parentFiles, _ := parentFiles_.([]Cloud189File)
|
|
||||||
// for _, file := range parentFiles {
|
|
||||||
// if file.Name == name {
|
|
||||||
// if file.Size != -1 {
|
|
||||||
// return &file, err
|
|
||||||
// } else {
|
|
||||||
// return nil, ErrNotFile
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return nil, ErrPathNotFound
|
|
||||||
//}
|
|
||||||
|
|
||||||
type LoginResp struct {
|
|
||||||
Msg string `json:"msg"`
|
|
||||||
Result int `json:"result"`
|
|
||||||
ToUrl string `json:"toUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login refer to PanIndex
|
|
||||||
func (driver Cloud189) Login(account *model.Account) error {
|
|
||||||
client := resty.New()
|
|
||||||
//client.SetCookieJar(cookieJar)
|
|
||||||
client.SetTimeout(base.DefaultTimeout)
|
|
||||||
client.SetRetryCount(3)
|
|
||||||
client.SetHeader("Referer", "https://cloud.189.cn/")
|
|
||||||
client.SetHeader("User-Agent", base.UserAgent)
|
|
||||||
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
|
|
||||||
b := ""
|
|
||||||
lt := ""
|
|
||||||
ltText := regexp.MustCompile(`lt = "(.+?)"`)
|
|
||||||
var res *resty.Response
|
|
||||||
var err error
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
res, err = client.R().Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// 已经登陆
|
|
||||||
if res.RawResponse.Request.URL.String() == "https://cloud.189.cn/web/main" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
b = res.String()
|
|
||||||
ltTextArr := ltText.FindStringSubmatch(b)
|
|
||||||
if len(ltTextArr) > 0 {
|
|
||||||
lt = ltTextArr[1]
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
<-time.After(time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if lt == "" {
|
|
||||||
return fmt.Errorf("get page: %s \nstatus: %d \nrequest url: %s\nredirect url: %s",
|
|
||||||
b, res.StatusCode(), res.RawResponse.Request.URL.String(), res.Header().Get("location"))
|
|
||||||
}
|
|
||||||
captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
|
|
||||||
returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
|
|
||||||
paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
|
|
||||||
//reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
|
|
||||||
jRsakey := regexp.MustCompile(`j_rsaKey" value="(\S+)"`).FindStringSubmatch(b)[1]
|
|
||||||
vCodeID := regexp.MustCompile(`picCaptcha\.do\?token\=([A-Za-z0-9\&\=]+)`).FindStringSubmatch(b)[1]
|
|
||||||
vCodeRS := ""
|
|
||||||
if vCodeID != "" {
|
|
||||||
// need ValidateCode
|
|
||||||
log.Debugf("try to identify verification codes")
|
|
||||||
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
|
|
||||||
u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
|
|
||||||
imgRes, err := client.R().SetHeaders(map[string]string{
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
|
|
||||||
"Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
|
|
||||||
"Sec-Fetch-Dest": "image",
|
|
||||||
"Sec-Fetch-Mode": "no-cors",
|
|
||||||
"Sec-Fetch-Site": "same-origin",
|
|
||||||
}).Get(u)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
vRes, err := client.R().SetMultipartField(
|
|
||||||
"image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
|
||||||
Post(conf.GetStr("ocr api"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
|
||||||
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
|
||||||
}
|
|
||||||
vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
|
|
||||||
log.Debugln("code: ", vCodeRS)
|
|
||||||
}
|
|
||||||
userRsa := RsaEncode([]byte(account.Username), jRsakey, true)
|
|
||||||
passwordRsa := RsaEncode([]byte(account.Password), jRsakey, true)
|
|
||||||
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
|
|
||||||
var loginResp LoginResp
|
|
||||||
res, err = client.R().
|
|
||||||
SetHeaders(map[string]string{
|
|
||||||
"lt": lt,
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
|
|
||||||
"Referer": "https://open.e.189.cn/",
|
|
||||||
"accept": "application/json;charset=UTF-8",
|
|
||||||
}).SetFormData(map[string]string{
|
|
||||||
"appKey": "cloud",
|
|
||||||
"accountType": "01",
|
|
||||||
"userName": "{RSA}" + userRsa,
|
|
||||||
"password": "{RSA}" + passwordRsa,
|
|
||||||
"validateCode": vCodeRS,
|
|
||||||
"captchaToken": captchaToken,
|
|
||||||
"returnUrl": returnUrl,
|
|
||||||
"mailSuffix": "@pan.cn",
|
|
||||||
"paramId": paramId,
|
|
||||||
"clientType": "10010",
|
|
||||||
"dynamicCheck": "FALSE",
|
|
||||||
"cb_SaveName": "1",
|
|
||||||
"isOauth2": "false",
|
|
||||||
}).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = utils.Json.Unmarshal(res.Body(), &loginResp)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if loginResp.Result != 0 {
|
|
||||||
return fmt.Errorf(loginResp.Msg)
|
|
||||||
}
|
|
||||||
_, err = client.R().Get(loginResp.ToUrl)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf(err.Error())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
client189Map[account.Name] = client
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) isFamily(account *model.Account) bool {
|
|
||||||
return account.InternalType == "Family"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud189File, error) {
|
|
||||||
res := make([]Cloud189File, 0)
|
|
||||||
pageNum := 1
|
|
||||||
|
|
||||||
for {
|
|
||||||
var resp Cloud189Files
|
|
||||||
body, err := driver.Request("https://cloud.189.cn/api/open/file/listFiles.action", base.Get, map[string]string{
|
|
||||||
//"noCache": random(),
|
|
||||||
"pageSize": "60",
|
|
||||||
"pageNum": strconv.Itoa(pageNum),
|
|
||||||
"mediaType": "0",
|
|
||||||
"folderId": fileId,
|
|
||||||
"iconOption": "5",
|
|
||||||
"orderBy": "lastOpTime", //account.OrderBy
|
|
||||||
"descending": "true", //account.OrderDirection
|
|
||||||
}, nil, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = utils.Json.Unmarshal(body, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.ResCode != 0 {
|
|
||||||
return nil, fmt.Errorf(resp.ResMessage)
|
|
||||||
}
|
|
||||||
if resp.FileListAO.Count == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, folder := range resp.FileListAO.FolderList {
|
|
||||||
res = append(res, Cloud189File{
|
|
||||||
Id: folder.Id,
|
|
||||||
LastOpTime: folder.LastOpTime,
|
|
||||||
Name: folder.Name,
|
|
||||||
Size: -1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
res = append(res, resp.FileListAO.FileList...)
|
|
||||||
pageNum++
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Request(url string, method int, query, form map[string]string, headers map[string]string, account *model.Account) ([]byte, error) {
|
|
||||||
client, err := driver.getClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//var resp base.Json
|
|
||||||
if driver.isFamily(account) {
|
|
||||||
url = strings.Replace(url, "/api/open", "/api/open/family", 1)
|
|
||||||
if query != nil {
|
|
||||||
query["familyId"] = account.SiteId
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
form["familyId"] = account.SiteId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var e Cloud189Error
|
|
||||||
req := client.R().SetError(&e).
|
|
||||||
SetHeader("Accept", "application/json;charset=UTF-8").
|
|
||||||
SetQueryParams(map[string]string{
|
|
||||||
"noCache": random(),
|
|
||||||
})
|
|
||||||
if query != nil {
|
|
||||||
req = req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req = req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if headers != nil {
|
|
||||||
req = req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
var res *resty.Response
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(url)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(url)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//log.Debug(res.String())
|
|
||||||
if e.ErrorCode != "" {
|
|
||||||
if e.ErrorCode == "InvalidSessionKey" {
|
|
||||||
err = driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driver.Request(url, method, query, form, nil, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if jsoniter.Get(res.Body(), "res_code").ToInt() != 0 {
|
|
||||||
err = errors.New(jsoniter.Get(res.Body(), "res_message").ToString())
|
|
||||||
}
|
|
||||||
return res.Body(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) GetSessionKey(account *model.Account) (string, error) {
|
|
||||||
//info, ok := infoMap[account.Name]
|
|
||||||
//if !ok {
|
|
||||||
// info = Info{}
|
|
||||||
// infoMap[account.Name] = info
|
|
||||||
//} else {
|
|
||||||
// log.Debugf("hit")
|
|
||||||
//}
|
|
||||||
//if info.SessionKey != "" {
|
|
||||||
// return info.SessionKey, nil
|
|
||||||
//}
|
|
||||||
resp, err := driver.Request("https://cloud.189.cn/v2/getUserBriefInfo.action", base.Get, nil, nil, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
sessionKey := jsoniter.Get(resp, "sessionKey").ToString()
|
|
||||||
//info.SessionKey = sessionKey
|
|
||||||
return sessionKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) GetResKey(account *model.Account) (string, string, error) {
|
|
||||||
rsa, ok := infoMap[account.Name]
|
|
||||||
if !ok {
|
|
||||||
rsa = Rsa{}
|
|
||||||
infoMap[account.Name] = rsa
|
|
||||||
}
|
|
||||||
now := time.Now().UnixMilli()
|
|
||||||
if rsa.Expire > now {
|
|
||||||
return rsa.PubKey, rsa.PkId, nil
|
|
||||||
}
|
|
||||||
resp, err := driver.Request("https://cloud.189.cn/api/security/generateRsaKey.action", base.Get, nil, nil, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
pubKey, pkId := jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString()
|
|
||||||
rsa.PubKey, rsa.PkId = pubKey, pkId
|
|
||||||
rsa.Expire = jsoniter.Get(resp, "expire").ToInt64()
|
|
||||||
return pubKey, pkId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Cloud189) UploadRequest1(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
|
|
||||||
// //sessionKey, err := driver.GetSessionKey(account)
|
|
||||||
// //if err != nil {
|
|
||||||
// // return nil, err
|
|
||||||
// //}
|
|
||||||
// sessionKey := account.DriveId
|
|
||||||
// pubKey, pkId, err := driver.GetResKey(account)
|
|
||||||
// log.Debugln(sessionKey, pubKey, pkId)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// xRId := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
|
|
||||||
// pkey := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")[0 : 16+int(16*mathRand.Float32())]
|
|
||||||
// params := hex.EncodeToString(AesEncrypt([]byte(qs(form)), []byte(pkey[0:16])))
|
|
||||||
// date := strconv.FormatInt(time.Now().Unix(), 10)
|
|
||||||
// a := make(url.Values)
|
|
||||||
// a.Set("SessionKey", sessionKey)
|
|
||||||
// a.Set("Operate", http.MethodGet)
|
|
||||||
// a.Set("RequestURI", uri)
|
|
||||||
// a.Set("Date", date)
|
|
||||||
// a.Set("params", params)
|
|
||||||
// signature := hex.EncodeToString(SHA1(EncodeParam(a), pkey))
|
|
||||||
// encryptionText := RsaEncode([]byte(pkey), pubKey, false)
|
|
||||||
// headers := map[string]string{
|
|
||||||
// "signature": signature,
|
|
||||||
// "sessionKey": sessionKey,
|
|
||||||
// "encryptionText": encryptionText,
|
|
||||||
// "pkId": pkId,
|
|
||||||
// "x-request-id": xRId,
|
|
||||||
// "x-request-date": date,
|
|
||||||
// }
|
|
||||||
// req := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params)
|
|
||||||
// if resp != nil {
|
|
||||||
// req.SetResult(resp)
|
|
||||||
// }
|
|
||||||
// res, err := req.Get("https://upload.cloud.189.cn" + uri)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// //log.Debug(res.String())
|
|
||||||
// data := res.Body()
|
|
||||||
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
|
|
||||||
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
|
|
||||||
// }
|
|
||||||
// return data, nil
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (driver Cloud189) UploadRequest2(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
|
|
||||||
// c := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
|
||||||
// r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
|
|
||||||
// l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
|
|
||||||
// l = l[0 : 16+int(16*mathRand.Float32())]
|
|
||||||
//
|
|
||||||
// e := qs(form)
|
|
||||||
// data := AesEncrypt([]byte(e), []byte(l[0:16]))
|
|
||||||
// h := hex.EncodeToString(data)
|
|
||||||
//
|
|
||||||
// sessionKey := account.DriveId
|
|
||||||
// a := make(url.Values)
|
|
||||||
// a.Set("SessionKey", sessionKey)
|
|
||||||
// a.Set("Operate", http.MethodGet)
|
|
||||||
// a.Set("RequestURI", uri)
|
|
||||||
// a.Set("Date", c)
|
|
||||||
// a.Set("params", h)
|
|
||||||
// g := SHA1(EncodeParam(a), l)
|
|
||||||
//
|
|
||||||
// pubKey, pkId, err := driver.GetResKey(account)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// b := RsaEncode([]byte(l), pubKey, false)
|
|
||||||
// client, err := driver.getClient(account)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// req := client.R()
|
|
||||||
// req.Header.Set("accept", "application/json;charset=UTF-8")
|
|
||||||
// req.Header.Set("SessionKey", sessionKey)
|
|
||||||
// req.Header.Set("Signature", hex.EncodeToString(g))
|
|
||||||
// req.Header.Set("X-Request-Date", c)
|
|
||||||
// req.Header.Set("X-Request-ID", r)
|
|
||||||
// req.Header.Set("EncryptionText", b)
|
|
||||||
// req.Header.Set("PkId", pkId)
|
|
||||||
// if resp != nil {
|
|
||||||
// req.SetResult(resp)
|
|
||||||
// }
|
|
||||||
// res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
|
|
||||||
// if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
// }
|
|
||||||
// //log.Debug(res.String())
|
|
||||||
// data = res.Body()
|
|
||||||
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
|
|
||||||
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
|
|
||||||
// }
|
|
||||||
// return data, nil
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Cloud189) UploadRequest(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
|
|
||||||
c := strconv.FormatInt(time.Now().UnixMilli(), 10)
|
|
||||||
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
|
|
||||||
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
|
|
||||||
l = l[0 : 16+int(16*utils.Rand.Float32())]
|
|
||||||
|
|
||||||
e := qs(form)
|
|
||||||
data := AesEncrypt([]byte(e), []byte(l[0:16]))
|
|
||||||
h := hex.EncodeToString(data)
|
|
||||||
|
|
||||||
sessionKey := account.DriveId
|
|
||||||
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s¶ms=%s", sessionKey, uri, c, h), l)
|
|
||||||
|
|
||||||
pubKey, pkId, err := driver.GetResKey(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b := RsaEncode([]byte(l), pubKey, false)
|
|
||||||
client, err := driver.getClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req := client.R()
|
|
||||||
req.Header.Set("accept", "application/json;charset=UTF-8")
|
|
||||||
req.Header.Set("SessionKey", sessionKey)
|
|
||||||
req.Header.Set("Signature", signature)
|
|
||||||
req.Header.Set("X-Request-Date", c)
|
|
||||||
req.Header.Set("X-Request-ID", r)
|
|
||||||
req.Header.Set("EncryptionText", b)
|
|
||||||
req.Header.Set("PkId", pkId)
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//log.Debug(res.String())
|
|
||||||
data = res.Body()
|
|
||||||
if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
|
|
||||||
return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUpload Error: signature check false
|
|
||||||
func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account) error {
|
|
||||||
sessionKey, err := driver.GetSessionKey(account)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
account.DriveId = sessionKey
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
const DEFAULT uint64 = 10485760
|
|
||||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
|
||||||
var finish uint64 = 0
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{
|
|
||||||
"parentFolderId": parentFile.Id,
|
|
||||||
"fileName": encode(file.Name),
|
|
||||||
"fileSize": strconv.FormatInt(int64(file.Size), 10),
|
|
||||||
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
|
|
||||||
"lazyCheck": "1",
|
|
||||||
}, account, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uploadFileId := jsoniter.Get(res, "data", "uploadFileId").ToString()
|
|
||||||
//_, err = driver.UploadRequest("/person/getUploadedPartsInfo", map[string]string{
|
|
||||||
// "uploadFileId": uploadFileId,
|
|
||||||
//}, account, nil)
|
|
||||||
var i int64
|
|
||||||
var byteSize uint64
|
|
||||||
md5s := make([]string, 0)
|
|
||||||
md5Sum := md5.New()
|
|
||||||
for i = 1; i <= count; i++ {
|
|
||||||
byteSize = file.GetSize() - finish
|
|
||||||
if DEFAULT < byteSize {
|
|
||||||
byteSize = DEFAULT
|
|
||||||
}
|
|
||||||
//log.Debugf("%d,%d", byteSize, finish)
|
|
||||||
byteData := make([]byte, byteSize)
|
|
||||||
n, err := io.ReadFull(file, byteData)
|
|
||||||
//log.Debug(err, n)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
finish += uint64(n)
|
|
||||||
md5Bytes := getMd5(byteData)
|
|
||||||
md5Hex := hex.EncodeToString(md5Bytes)
|
|
||||||
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
|
|
||||||
md5s = append(md5s, strings.ToUpper(md5Hex))
|
|
||||||
md5Sum.Write(byteData)
|
|
||||||
//log.Debugf("md5Bytes: %+v,md5Str:%s,md5Base64:%s", md5Bytes, md5Hex, md5Base64)
|
|
||||||
var resp UploadUrlsResp
|
|
||||||
res, err = driver.UploadRequest("/person/getMultiUploadUrls", map[string]string{
|
|
||||||
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64),
|
|
||||||
"uploadFileId": uploadFileId,
|
|
||||||
}, account, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uploadData := resp.UploadUrls["partNumber_"+strconv.FormatInt(i, 10)]
|
|
||||||
log.Debugf("uploadData: %+v", uploadData)
|
|
||||||
requestURL := uploadData.RequestURL
|
|
||||||
uploadHeaders := strings.Split(decodeURIComponent(uploadData.RequestHeader), "&")
|
|
||||||
req, _ := http.NewRequest(http.MethodPut, requestURL, bytes.NewReader(byteData))
|
|
||||||
for _, v := range uploadHeaders {
|
|
||||||
i := strings.Index(v, "=")
|
|
||||||
req.Header.Set(v[0:i], v[i+1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := base.HttpClient.Do(req)
|
|
||||||
log.Debugf("%+v %+v", r, r.Request.Header)
|
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
|
|
||||||
sliceMd5 := fileMd5
|
|
||||||
if file.GetSize() > DEFAULT {
|
|
||||||
sliceMd5 = utils.GetMD5Encode(strings.Join(md5s, "\n"))
|
|
||||||
}
|
|
||||||
res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{
|
|
||||||
"uploadFileId": uploadFileId,
|
|
||||||
"fileMd5": fileMd5,
|
|
||||||
"sliceMd5": sliceMd5,
|
|
||||||
"lazyCheck": "1",
|
|
||||||
}, account, nil)
|
|
||||||
account.DriveId, _ = driver.GetSessionKey(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) OldUpload(file *model.FileStream, account *model.Account) error {
|
|
||||||
//return base.ErrNotImplement
|
|
||||||
client, err := driver.getClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// api refer to PanIndex
|
|
||||||
res, err := client.R().SetMultipartFormData(map[string]string{
|
|
||||||
"parentId": parentFile.Id,
|
|
||||||
"sessionKey": account.DriveId,
|
|
||||||
"opertype": "1",
|
|
||||||
"fname": file.GetFileName(),
|
|
||||||
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Debugf(res.String())
|
|
||||||
return errors.New(res.String())
|
|
||||||
}
|
|
@ -1,382 +0,0 @@
|
|||||||
package _89
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Cloud189 struct{}
|
|
||||||
|
|
||||||
func (driver Cloud189) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "189Cloud",
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "username",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "account username/phone number",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "account password",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
//{
|
|
||||||
// Name: "internal_type",
|
|
||||||
// Label: "189cloud type",
|
|
||||||
// Type: base.TypeSelect,
|
|
||||||
// Required: true,
|
|
||||||
// Values: "Personal,Family",
|
|
||||||
//},
|
|
||||||
//{
|
|
||||||
// Name: "site_id",
|
|
||||||
// Label: "family id",
|
|
||||||
// Type: base.TypeString,
|
|
||||||
//},
|
|
||||||
//{
|
|
||||||
// Name: "order_by",
|
|
||||||
// Label: "order_by",
|
|
||||||
// Type: base.TypeSelect,
|
|
||||||
// Values: "name,size,lastOpTime,createdDate",
|
|
||||||
// Required: true,
|
|
||||||
//},
|
|
||||||
//{
|
|
||||||
// Name: "order_direction",
|
|
||||||
// Label: "desc",
|
|
||||||
// Type: base.TypeSelect,
|
|
||||||
// Values: "true,false",
|
|
||||||
// Required: true,
|
|
||||||
//},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if old != nil {
|
|
||||||
delete(client189Map, old.Name)
|
|
||||||
}
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := driver.Login(account); err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sessionKey, err := driver.GetSessionKey(account)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
account.DriveId = sessionKey
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var rawFiles []Cloud189File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
rawFiles, _ = cache.([]Cloud189File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(rawFiles) > 0 {
|
|
||||||
_ = base.SetCache(path, rawFiles, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
for _, file := range rawFiles {
|
|
||||||
files = append(files, *driver.FormatFile(&file))
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(utils.ParsePath(args.Path), account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if file.Type == conf.FOLDER {
|
|
||||||
return nil, base.ErrNotFile
|
|
||||||
}
|
|
||||||
var resp DownResp
|
|
||||||
u := "https://cloud.189.cn/api/portal/getFileInfo.action"
|
|
||||||
body, err := driver.Request(u, base.Get, map[string]string{
|
|
||||||
"fileId": file.Id,
|
|
||||||
}, nil, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugln(string(body))
|
|
||||||
err = utils.Json.Unmarshal(body, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.ResCode != 0 {
|
|
||||||
return nil, fmt.Errorf(resp.ResMessage)
|
|
||||||
}
|
|
||||||
client, err := driver.getClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client = resty.NewWithClient(client.GetClient()).SetRedirectPolicy(
|
|
||||||
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
}))
|
|
||||||
res, err := client.R().SetHeader("User-Agent", base.UserAgent).Get("https:" + resp.FileDownloadUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugln(res.Status())
|
|
||||||
log.Debugln(res.String())
|
|
||||||
link := base.Link{
|
|
||||||
Headers: []base.Header{
|
|
||||||
{Name: "User-Agent", Value: base.UserAgent},
|
|
||||||
//{Name: "Authorization", Value: ""},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
log.Debugln("first url:", resp.FileDownloadUrl)
|
|
||||||
if res.StatusCode() == 302 {
|
|
||||||
link.Url = res.Header().Get("location")
|
|
||||||
log.Debugln("second url:", link.Url)
|
|
||||||
_, _ = client.R().Get(link.Url)
|
|
||||||
if res.StatusCode() == 302 {
|
|
||||||
link.Url = res.Header().Get("location")
|
|
||||||
}
|
|
||||||
log.Debugln("third url:", link.Url)
|
|
||||||
} else {
|
|
||||||
link.Url = resp.FileDownloadUrl
|
|
||||||
}
|
|
||||||
link.Url = strings.Replace(link.Url, "http://", "https://", 1)
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("189 path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Cloud189) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
// r.Header.Del("Origin")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
parent, err := driver.File(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parent.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
form := map[string]string{
|
|
||||||
"parentFolderId": parent.Id,
|
|
||||||
"folderName": name,
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://cloud.189.cn/api/open/file/createFolder.action", base.Post, nil, form, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
|
|
||||||
dstDir, dstName := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstDirFile, err := driver.File(dstDir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
isFolder := 0
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
isFolder = 1
|
|
||||||
}
|
|
||||||
taskInfos := []base.Json{
|
|
||||||
{
|
|
||||||
"fileId": srcFile.Id,
|
|
||||||
"fileName": dstName,
|
|
||||||
"isFolder": isFolder,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
form := map[string]string{
|
|
||||||
"type": "MOVE",
|
|
||||||
"targetFolderId": dstDirFile.Id,
|
|
||||||
"taskInfos": string(taskInfosBytes),
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
_, dstName := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
url := "https://cloud.189.cn/api/open/file/renameFile.action"
|
|
||||||
idKey := "fileId"
|
|
||||||
nameKey := "destFileName"
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
url = "https://cloud.189.cn/api/open/file/renameFolder.action"
|
|
||||||
idKey = "folderId"
|
|
||||||
nameKey = "destFolderName"
|
|
||||||
}
|
|
||||||
form := map[string]string{
|
|
||||||
idKey: srcFile.Id,
|
|
||||||
nameKey: dstName,
|
|
||||||
}
|
|
||||||
_, err = driver.Request(url, base.Post, nil, form, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
dstDir, dstName := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstDirFile, err := driver.File(dstDir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
isFolder := 0
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
isFolder = 1
|
|
||||||
}
|
|
||||||
taskInfos := []base.Json{
|
|
||||||
{
|
|
||||||
"fileId": srcFile.Id,
|
|
||||||
"fileName": dstName,
|
|
||||||
"isFolder": isFolder,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
form := map[string]string{
|
|
||||||
"type": "COPY",
|
|
||||||
"targetFolderId": dstDirFile.Id,
|
|
||||||
"taskInfos": string(taskInfosBytes),
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Delete(path string, account *model.Account) error {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
isFolder := 0
|
|
||||||
if file.IsDir() {
|
|
||||||
isFolder = 1
|
|
||||||
}
|
|
||||||
taskInfos := []base.Json{
|
|
||||||
{
|
|
||||||
"fileId": file.Id,
|
|
||||||
"fileName": file.Name,
|
|
||||||
"isFolder": isFolder,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
form := map[string]string{
|
|
||||||
"type": "DELETE",
|
|
||||||
"targetFolderId": "",
|
|
||||||
"taskInfos": string(taskInfosBytes),
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
//return base.ErrNotImplement
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
return driver.NewUpload(file, account)
|
|
||||||
//return driver.OldUpload(file, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Cloud189)(nil)
|
|
@ -1,91 +0,0 @@
|
|||||||
package _89
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Cloud189Error struct {
|
|
||||||
ErrorCode string `json:"errorCode"`
|
|
||||||
ErrorMsg string `json:"errorMsg"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cloud189File struct {
|
|
||||||
Id int64 `json:"id"`
|
|
||||||
LastOpTime string `json:"lastOpTime"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Icon struct {
|
|
||||||
SmallUrl string `json:"smallUrl"`
|
|
||||||
//LargeUrl string `json:"largeUrl"`
|
|
||||||
} `json:"icon"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Cloud189File) GetSize() uint64 {
|
|
||||||
if f.Size == -1 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return uint64(f.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Cloud189File) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Cloud189File) GetType() int {
|
|
||||||
if f.Size == -1 {
|
|
||||||
return conf.FOLDER
|
|
||||||
}
|
|
||||||
return utils.GetFileType(path.Ext(f.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cloud189Folder struct {
|
|
||||||
Id int64 `json:"id"`
|
|
||||||
LastOpTime string `json:"lastOpTime"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cloud189Files struct {
|
|
||||||
ResCode int `json:"res_code"`
|
|
||||||
ResMessage string `json:"res_message"`
|
|
||||||
FileListAO struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
FileList []Cloud189File `json:"fileList"`
|
|
||||||
FolderList []Cloud189Folder `json:"folderList"`
|
|
||||||
} `json:"fileListAO"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadUrlsResp struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
UploadUrls map[string]Part `json:"uploadUrls"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Part struct {
|
|
||||||
RequestURL string `json:"requestURL"`
|
|
||||||
RequestHeader string `json:"requestHeader"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//type Info struct {
|
|
||||||
// SessionKey string
|
|
||||||
// Rsa Rsa
|
|
||||||
//}
|
|
||||||
|
|
||||||
type Rsa struct {
|
|
||||||
Expire int64 `json:"expire"`
|
|
||||||
PkId string `json:"pkId"`
|
|
||||||
PubKey string `json:"pubKey"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cloud189Down struct {
|
|
||||||
ResCode int `json:"res_code"`
|
|
||||||
ResMessage string `json:"res_message"`
|
|
||||||
FileDownloadUrl string `json:"fileDownloadUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownResp struct {
|
|
||||||
ResCode int `json:"res_code"`
|
|
||||||
ResMessage string `json:"res_message"`
|
|
||||||
FileDownloadUrl string `json:"downloadUrl"`
|
|
||||||
}
|
|
@ -1,199 +0,0 @@
|
|||||||
package _89
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func random() string {
|
|
||||||
return fmt.Sprintf("0.%17v", utils.Rand.Int63n(100000000000000000))
|
|
||||||
}
|
|
||||||
|
|
||||||
func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
|
|
||||||
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
|
|
||||||
block, _ := pem.Decode(publicKey)
|
|
||||||
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
|
|
||||||
pub := pubInterface.(*rsa.PublicKey)
|
|
||||||
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("err: %s", err.Error())
|
|
||||||
}
|
|
||||||
res := base64.StdEncoding.EncodeToString(b)
|
|
||||||
if hex {
|
|
||||||
return b64tohex(res)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
||||||
|
|
||||||
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
|
|
||||||
|
|
||||||
func int2char(a int) string {
|
|
||||||
return strings.Split(BI_RM, "")[a]
|
|
||||||
}
|
|
||||||
|
|
||||||
func b64tohex(a string) string {
|
|
||||||
d := ""
|
|
||||||
e := 0
|
|
||||||
c := 0
|
|
||||||
for i := 0; i < len(a); i++ {
|
|
||||||
m := strings.Split(a, "")[i]
|
|
||||||
if m != "=" {
|
|
||||||
v := strings.Index(b64map, m)
|
|
||||||
if 0 == e {
|
|
||||||
e = 1
|
|
||||||
d += int2char(v >> 2)
|
|
||||||
c = 3 & v
|
|
||||||
} else if 1 == e {
|
|
||||||
e = 2
|
|
||||||
d += int2char(c<<2 | v>>4)
|
|
||||||
c = 15 & v
|
|
||||||
} else if 2 == e {
|
|
||||||
e = 3
|
|
||||||
d += int2char(c)
|
|
||||||
d += int2char(v >> 2)
|
|
||||||
c = 3 & v
|
|
||||||
} else {
|
|
||||||
e = 0
|
|
||||||
d += int2char(c<<2 | v>>4)
|
|
||||||
d += int2char(15 & v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if e == 1 {
|
|
||||||
d += int2char(c << 2)
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func qs(form map[string]string) string {
|
|
||||||
f := make(url.Values)
|
|
||||||
for k, v := range form {
|
|
||||||
f.Set(k, v)
|
|
||||||
}
|
|
||||||
return EncodeParam(f)
|
|
||||||
//strList := make([]string, 0)
|
|
||||||
//for k, v := range form {
|
|
||||||
// strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
|
|
||||||
//}
|
|
||||||
//return strings.Join(strList, "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
func EncodeParam(v url.Values) string {
|
|
||||||
if v == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var buf strings.Builder
|
|
||||||
keys := make([]string, 0, len(v))
|
|
||||||
for k := range v {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
for _, k := range keys {
|
|
||||||
vs := v[k]
|
|
||||||
for _, v := range vs {
|
|
||||||
if buf.Len() > 0 {
|
|
||||||
buf.WriteByte('&')
|
|
||||||
}
|
|
||||||
buf.WriteString(k)
|
|
||||||
buf.WriteByte('=')
|
|
||||||
//if k == "fileName" {
|
|
||||||
// buf.WriteString(encode(v))
|
|
||||||
//} else {
|
|
||||||
buf.WriteString(v)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(str string) string {
|
|
||||||
//str = strings.ReplaceAll(str, "%", "%25")
|
|
||||||
//str = strings.ReplaceAll(str, "&", "%26")
|
|
||||||
//str = strings.ReplaceAll(str, "+", "%2B")
|
|
||||||
//return str
|
|
||||||
return url.QueryEscape(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func AesEncrypt(data, key []byte) []byte {
|
|
||||||
block, _ := aes.NewCipher(key)
|
|
||||||
if block == nil {
|
|
||||||
return []byte{}
|
|
||||||
}
|
|
||||||
data = PKCS7Padding(data, block.BlockSize())
|
|
||||||
decrypted := make([]byte, len(data))
|
|
||||||
size := block.BlockSize()
|
|
||||||
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
|
|
||||||
block.Encrypt(decrypted[bs:be], data[bs:be])
|
|
||||||
}
|
|
||||||
return decrypted
|
|
||||||
}
|
|
||||||
|
|
||||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
|
||||||
padding := blockSize - len(ciphertext)%blockSize
|
|
||||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
|
||||||
return append(ciphertext, padtext...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hmacSha1(data string, secret string) string {
|
|
||||||
h := hmac.New(sha1.New, []byte(secret))
|
|
||||||
h.Write([]byte(data))
|
|
||||||
return hex.EncodeToString(h.Sum(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMd5(data []byte) []byte {
|
|
||||||
h := md5.New()
|
|
||||||
h.Write(data)
|
|
||||||
return h.Sum(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Cloud189{})
|
|
||||||
client189Map = make(map[string]*resty.Client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeURIComponent(str string) string {
|
|
||||||
r, _ := url.PathUnescape(str)
|
|
||||||
//r = strings.ReplaceAll(r, " ", "+")
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func Random(v string) string {
|
|
||||||
reg := regexp.MustCompilePOSIX("[xy]")
|
|
||||||
data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte {
|
|
||||||
var i int64
|
|
||||||
t := int64(16 * utils.Rand.Float32())
|
|
||||||
if msg[0] == 120 {
|
|
||||||
i = t
|
|
||||||
} else {
|
|
||||||
i = 3&t | 8
|
|
||||||
}
|
|
||||||
return []byte(strconv.FormatInt(i, 16))
|
|
||||||
})
|
|
||||||
return string(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
//func SHA1(v, l string) []byte {
|
|
||||||
// key := []byte(l)
|
|
||||||
// mac := hmac.New(sha1.New, key)
|
|
||||||
// mac.Write([]byte(v))
|
|
||||||
// return mac.Sum(nil)
|
|
||||||
//}
|
|
@ -1,318 +0,0 @@
|
|||||||
package _189
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var userStateCache = struct {
|
|
||||||
sync.Mutex
|
|
||||||
States map[string]*State
|
|
||||||
}{States: make(map[string]*State)}
|
|
||||||
|
|
||||||
func GetState(account *model.Account) *State {
|
|
||||||
userStateCache.Lock()
|
|
||||||
defer userStateCache.Unlock()
|
|
||||||
if v, ok := userStateCache.States[account.Username]; ok && v != nil {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
state := &State{client: resty.New().
|
|
||||||
SetHeaders(map[string]string{
|
|
||||||
"Accept": "application/json;charset=UTF-8",
|
|
||||||
"User-Agent": base.UserAgent,
|
|
||||||
}).SetTimeout(base.DefaultTimeout),
|
|
||||||
}
|
|
||||||
userStateCache.States[account.Username] = state
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
|
||||||
type State struct {
|
|
||||||
sync.Mutex
|
|
||||||
client *resty.Client
|
|
||||||
|
|
||||||
RsaPublicKey string
|
|
||||||
|
|
||||||
SessionKey string
|
|
||||||
SessionSecret string
|
|
||||||
FamilySessionKey string
|
|
||||||
FamilySessionSecret string
|
|
||||||
|
|
||||||
AccessToken string
|
|
||||||
|
|
||||||
//怎么刷新的???
|
|
||||||
RefreshToken string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) login(account *model.Account) error {
|
|
||||||
// 清除cookie
|
|
||||||
jar, _ := cookiejar.New(nil)
|
|
||||||
s.client.SetCookieJar(jar)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
var res *resty.Response
|
|
||||||
defer func() {
|
|
||||||
account.Status = "work"
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
}
|
|
||||||
model.SaveAccount(account)
|
|
||||||
if res != nil {
|
|
||||||
log.Debug(res.String())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var param *LoginParam
|
|
||||||
param, err = s.getLoginParam()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交登录
|
|
||||||
s.RsaPublicKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", param.jRsaKey)
|
|
||||||
res, err = s.client.R().
|
|
||||||
SetHeaders(map[string]string{
|
|
||||||
"Referer": AUTH_URL,
|
|
||||||
"REQID": param.ReqId,
|
|
||||||
"lt": param.Lt,
|
|
||||||
}).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"appKey": APP_ID,
|
|
||||||
"accountType": "02",
|
|
||||||
"userName": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Username),
|
|
||||||
"password": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Password),
|
|
||||||
"validateCode": param.vCodeRS,
|
|
||||||
"captchaToken": param.CaptchaToken,
|
|
||||||
"returnUrl": RETURN_URL,
|
|
||||||
"mailSuffix": "@189.cn",
|
|
||||||
"dynamicCheck": "FALSE",
|
|
||||||
"clientType": CLIENT_TYPE,
|
|
||||||
"cb_SaveName": "1",
|
|
||||||
"isOauth2": "false",
|
|
||||||
"state": "",
|
|
||||||
"paramId": param.ParamId,
|
|
||||||
}).
|
|
||||||
Post(AUTH_URL + "/api/logbox/oauth2/loginSubmit.do")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
toUrl := utils.Json.Get(res.Body(), "toUrl").ToString()
|
|
||||||
if toUrl == "" {
|
|
||||||
log.Error(res.String())
|
|
||||||
return fmt.Errorf(res.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取Session
|
|
||||||
var erron Erron
|
|
||||||
var sessionResp appSessionResp
|
|
||||||
res, err = s.client.R().
|
|
||||||
SetResult(&sessionResp).SetError(&erron).
|
|
||||||
SetQueryParams(clientSuffix()).
|
|
||||||
SetQueryParam("redirectURL", url.QueryEscape(toUrl)).
|
|
||||||
Post(API_URL + "/getSessionForPC.action")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if erron.ResCode != "" {
|
|
||||||
err = fmt.Errorf(erron.ResMessage)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if sessionResp.ResCode != 0 {
|
|
||||||
err = fmt.Errorf(sessionResp.ResMessage)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.SessionKey = sessionResp.SessionKey
|
|
||||||
s.SessionSecret = sessionResp.SessionSecret
|
|
||||||
s.FamilySessionKey = sessionResp.FamilySessionKey
|
|
||||||
s.FamilySessionSecret = sessionResp.FamilySessionSecret
|
|
||||||
s.AccessToken = sessionResp.AccessToken
|
|
||||||
s.RefreshToken = sessionResp.RefreshToken
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) getLoginParam() (*LoginParam, error) {
|
|
||||||
res, err := s.client.R().
|
|
||||||
SetQueryParams(map[string]string{
|
|
||||||
"appId": APP_ID,
|
|
||||||
"clientType": CLIENT_TYPE,
|
|
||||||
"returnURL": RETURN_URL,
|
|
||||||
"timeStamp": fmt.Sprint(timestamp()),
|
|
||||||
}).
|
|
||||||
Get(WEB_URL + "/api/portal/unifyLoginForPC.action")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
param := &LoginParam{
|
|
||||||
CaptchaToken: regexp.MustCompile(`'captchaToken' value='(.+?)'`).FindStringSubmatch(res.String())[1],
|
|
||||||
Lt: regexp.MustCompile(`lt = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
|
||||||
ParamId: regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
|
||||||
ReqId: regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
|
||||||
jRsaKey: regexp.MustCompile(`"j_rsaKey" value="(.+?)"`).FindStringSubmatch(res.String())[1],
|
|
||||||
|
|
||||||
vCodeID: regexp.MustCompile(`token=([A-Za-z0-9&=]+)`).FindStringSubmatch(res.String())[1],
|
|
||||||
}
|
|
||||||
|
|
||||||
imgRes, err := s.client.R().Get(fmt.Sprint(AUTH_URL, "/api/logbox/oauth2/picCaptcha.do?token=", param.vCodeID, timestamp()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(imgRes.Body()) > 0 {
|
|
||||||
vRes, err := resty.New().R().
|
|
||||||
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
|
||||||
Post(conf.GetStr("ocr api"))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if utils.Json.Get(vRes.Body(), "status").ToInt() != 200 {
|
|
||||||
return nil, errors.New("ocr error:" + utils.Json.Get(vRes.Body(), "msg").ToString())
|
|
||||||
}
|
|
||||||
param.vCodeRS = utils.Json.Get(vRes.Body(), "result").ToString()
|
|
||||||
log.Debugln("code: ", param.vCodeRS)
|
|
||||||
}
|
|
||||||
return param, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) refreshSession(account *model.Account) error {
|
|
||||||
var erron Erron
|
|
||||||
var userSessionResp UserSessionResp
|
|
||||||
res, err := s.client.R().
|
|
||||||
SetResult(&userSessionResp).SetError(&erron).
|
|
||||||
SetQueryParams(clientSuffix()).
|
|
||||||
SetQueryParams(map[string]string{
|
|
||||||
"appId": APP_ID,
|
|
||||||
"accessToken": s.AccessToken,
|
|
||||||
}).
|
|
||||||
SetHeader("X-Request-ID", uuid.NewString()).
|
|
||||||
Get(API_URL + "/getSessionForPC.action")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if erron.ResCode != "" {
|
|
||||||
return fmt.Errorf(erron.ResMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch userSessionResp.ResCode {
|
|
||||||
case 0:
|
|
||||||
s.SessionKey = userSessionResp.SessionKey
|
|
||||||
s.SessionSecret = userSessionResp.SessionSecret
|
|
||||||
s.FamilySessionKey = userSessionResp.FamilySessionKey
|
|
||||||
s.FamilySessionSecret = userSessionResp.FamilySessionSecret
|
|
||||||
case 11, 18:
|
|
||||||
return s.login(account)
|
|
||||||
default:
|
|
||||||
account.Status = userSessionResp.ResMessage
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return fmt.Errorf(userSessionResp.ResMessage)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) IsLogin(account *model.Account) bool {
|
|
||||||
_, err := s.Request(http.MethodGet, API_URL+"/getUserInfo.action", nil, func(r *resty.Request) { r.SetQueryParams(clientSuffix()) }, account)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) Login(account *model.Account) error {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
return s.login(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) RefreshSession(account *model.Account) error {
|
|
||||||
s.Lock()
|
|
||||||
defer s.Unlock()
|
|
||||||
return s.refreshSession(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) Request(method string, fullUrl string, params Params, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
|
||||||
s.Lock()
|
|
||||||
dateOfGmt := getHttpDateStr()
|
|
||||||
sessionKey := s.SessionKey
|
|
||||||
sessionSecret := s.SessionSecret
|
|
||||||
if isFamily(account) {
|
|
||||||
sessionKey = s.FamilySessionKey
|
|
||||||
sessionSecret = s.FamilySessionSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
req := s.client.R()
|
|
||||||
req.SetHeaders(map[string]string{
|
|
||||||
"Date": dateOfGmt,
|
|
||||||
"SessionKey": sessionKey,
|
|
||||||
"X-Request-ID": uuid.NewString(),
|
|
||||||
})
|
|
||||||
|
|
||||||
// 设置params
|
|
||||||
var paramsData string
|
|
||||||
if params != nil {
|
|
||||||
paramsData = AesECBEncrypt(params.Encode(), s.SessionSecret[:16])
|
|
||||||
req.SetQueryParam("params", paramsData)
|
|
||||||
}
|
|
||||||
req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, fullUrl, dateOfGmt, paramsData))
|
|
||||||
|
|
||||||
if callback != nil {
|
|
||||||
callback(req)
|
|
||||||
}
|
|
||||||
s.Unlock()
|
|
||||||
|
|
||||||
res, err := req.Execute(method, fullUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
|
|
||||||
var erron Erron
|
|
||||||
utils.Json.Unmarshal(res.Body(), &erron)
|
|
||||||
if erron.ResCode != "" {
|
|
||||||
return nil, fmt.Errorf(erron.ResMessage)
|
|
||||||
}
|
|
||||||
if erron.Code != "" && erron.Code != "SUCCESS" {
|
|
||||||
if erron.Msg == "" {
|
|
||||||
if erron.Message == "" {
|
|
||||||
return nil, fmt.Errorf(res.String())
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf(erron.Message)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf(erron.Msg)
|
|
||||||
}
|
|
||||||
if erron.ErrorCode != "" {
|
|
||||||
switch erron.ErrorCode {
|
|
||||||
case "InvalidSessionKey":
|
|
||||||
if err := s.RefreshSession(account); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return s.Request(method, fullUrl, params, callback, account)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf(erron.ErrorMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch utils.Json.Get(res.Body(), "res_code").ToInt64() {
|
|
||||||
case 11, 18:
|
|
||||||
if err := s.RefreshSession(account); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return s.Request(method, fullUrl, params, callback, account)
|
|
||||||
case 0:
|
|
||||||
if res.StatusCode() == http.StatusOK {
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf(res.String())
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf(utils.Json.Get(res.Body(), "res_message").ToString())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,923 +0,0 @@
|
|||||||
package _189
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(new(Cloud189))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cloud189 struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "189CloudPC",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "username",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "account username/phone number",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "account password",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "189cloud type",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Values: "Personal,Family",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "site_id",
|
|
||||||
Label: "family id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "filename,filesize,lastOpTime",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "desc",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "true,false",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "bool_1",
|
|
||||||
Label: "fast upload",
|
|
||||||
Type: base.TypeBool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isFamily(account) && account.RootFolder == "" {
|
|
||||||
account.RootFolder = "-11"
|
|
||||||
account.SiteId = ""
|
|
||||||
}
|
|
||||||
if isFamily(account) && account.RootFolder == "-11" {
|
|
||||||
account.RootFolder = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
state := GetState(account)
|
|
||||||
if !state.IsLogin(account) {
|
|
||||||
if err := state.Login(account); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if isFamily(account) {
|
|
||||||
list, err := driver.getFamilyInfoList(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, l := range list {
|
|
||||||
if account.SiteId == "" {
|
|
||||||
account.SiteId = fmt.Sprint(l.FamilyID)
|
|
||||||
}
|
|
||||||
log.Infof("天翼家庭云 用户名:%s FamilyID %d\n", l.RemarkName, l.FamilyID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
account.Status = "work"
|
|
||||||
model.SaveAccount(account)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) getFamilyInfoList(account *model.Account) ([]FamilyInfoResp, error) {
|
|
||||||
var resp FamilyInfoListResp
|
|
||||||
_, err := GetState(account).Request(http.MethodGet, API_URL+"/family/manage/getFamilyList.action", nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix())
|
|
||||||
r.SetResult(&resp)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.FamilyInfoResp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ := cache.([]model.File)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fullUrl := API_URL
|
|
||||||
if isFamily(account) {
|
|
||||||
fullUrl += "/family/file"
|
|
||||||
}
|
|
||||||
fullUrl += "/listFiles.action"
|
|
||||||
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
client := GetState(account)
|
|
||||||
for pageNum := 1; ; pageNum++ {
|
|
||||||
var resp Cloud189FilesResp
|
|
||||||
_, err = client.Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix()).
|
|
||||||
SetQueryParams(map[string]string{
|
|
||||||
"folderId": file.Id,
|
|
||||||
"fileType": "0",
|
|
||||||
"mediaAttr": "0",
|
|
||||||
"iconOption": "5",
|
|
||||||
"pageNum": fmt.Sprint(pageNum),
|
|
||||||
"pageSize": "130",
|
|
||||||
})
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
"orderBy": toFamilyOrderBy(account.OrderBy),
|
|
||||||
"descending": account.OrderDirection,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"recursive": "0",
|
|
||||||
"orderBy": account.OrderBy,
|
|
||||||
"descending": account.OrderDirection,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
r.SetResult(&resp)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// 获取完毕跳出
|
|
||||||
if resp.FileListAO.Count == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, folder := range resp.FileListAO.FolderList {
|
|
||||||
files = append(files, model.File{
|
|
||||||
Id: fmt.Sprint(folder.ID),
|
|
||||||
Name: folder.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: MustParseTime(folder.LastOpTime),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, file := range resp.FileListAO.FileList {
|
|
||||||
files = append(files, model.File{
|
|
||||||
Id: fmt.Sprint(file.ID),
|
|
||||||
Name: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
Type: utils.GetFileType(filepath.Ext(file.Name)),
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: MustParseTime(file.LastOpTime),
|
|
||||||
Thumbnail: file.Icon.SmallUrl,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("189PC path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(utils.ParsePath(args.Path), account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if file.Type == conf.FOLDER {
|
|
||||||
return nil, base.ErrNotFile
|
|
||||||
}
|
|
||||||
|
|
||||||
fullUrl := API_URL
|
|
||||||
if isFamily(account) {
|
|
||||||
fullUrl += "/family/file"
|
|
||||||
}
|
|
||||||
fullUrl += "/getFileDownloadUrl.action"
|
|
||||||
|
|
||||||
var downloadUrl struct {
|
|
||||||
URL string `json:"fileDownloadUrl"`
|
|
||||||
}
|
|
||||||
_, err = GetState(account).Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix()).SetQueryParam("fileId", file.Id)
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"dt": "3",
|
|
||||||
"flag": "1",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
r.SetResult(&downloadUrl)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Headers: []base.Header{
|
|
||||||
{Name: "User-Agent", Value: base.UserAgent},
|
|
||||||
},
|
|
||||||
Url: strings.ReplaceAll(downloadUrl.URL, "&", "&"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
parentFile, err := driver.File(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
fullUrl := API_URL
|
|
||||||
if isFamily(account) {
|
|
||||||
fullUrl += "/family/file"
|
|
||||||
}
|
|
||||||
fullUrl += "/createFolder.action"
|
|
||||||
|
|
||||||
_, err = GetState(account).Request(http.MethodPost, fullUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix()).SetQueryParams(map[string]string{
|
|
||||||
"folderName": name,
|
|
||||||
"relativePath": "",
|
|
||||||
})
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
"parentId": parentFile.Id,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"parentFolderId": parentFile.Id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
|
||||||
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
|
||||||
"type": "MOVE",
|
|
||||||
"taskInfos": string(MustToBytes(utils.Json.Marshal(
|
|
||||||
[]*BatchTaskInfo{
|
|
||||||
{
|
|
||||||
FileId: srcFile.Id,
|
|
||||||
FileName: srcFile.Name,
|
|
||||||
IsFolder: BoolToNumber(srcFile.IsDir()),
|
|
||||||
},
|
|
||||||
}))),
|
|
||||||
"targetFolderId": dstDirFile.Id,
|
|
||||||
})
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetFormData(map[string]string{
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryParam map[string]string
|
|
||||||
fullUrl := API_URL
|
|
||||||
method := http.MethodPost
|
|
||||||
if isFamily(account) {
|
|
||||||
fullUrl += "/family/file"
|
|
||||||
method = http.MethodGet
|
|
||||||
}
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
fullUrl += "/moveFolder.action"
|
|
||||||
queryParam = map[string]string{
|
|
||||||
"folderId": srcFile.Id,
|
|
||||||
"destFolderName": srcFile.Name,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fullUrl += "/moveFile.action"
|
|
||||||
queryParam = map[string]string{
|
|
||||||
"fileId": srcFile.Id,
|
|
||||||
"destFileName": srcFile.Name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
"destParentId": dstDirFile.Id,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
r.SetQueryParam("destParentFolderId", dstDirFile.Id)
|
|
||||||
}
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func (driver Cloud189) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var queryParam map[string]string
|
|
||||||
fullUrl := API_URL
|
|
||||||
method := http.MethodPost
|
|
||||||
if isFamily(account) {
|
|
||||||
fullUrl += "/family/file"
|
|
||||||
method = http.MethodGet
|
|
||||||
}
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
fullUrl += "/renameFolder.action"
|
|
||||||
queryParam = map[string]string{
|
|
||||||
"folderId": srcFile.Id,
|
|
||||||
"destFolderName": filepath.Base(dst),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fullUrl += "/renameFile.action"
|
|
||||||
queryParam = map[string]string{
|
|
||||||
"fileId": srcFile.Id,
|
|
||||||
"destFileName": filepath.Base(dst),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetQueryParam("familyId", account.SiteId)
|
|
||||||
}
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
|
||||||
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
|
||||||
"type": "COPY",
|
|
||||||
"taskInfos": string(MustToBytes(utils.Json.Marshal(
|
|
||||||
[]*BatchTaskInfo{
|
|
||||||
{
|
|
||||||
FileId: srcFile.Id,
|
|
||||||
FileName: srcFile.Name,
|
|
||||||
IsFolder: BoolToNumber(srcFile.IsDir()),
|
|
||||||
},
|
|
||||||
}))),
|
|
||||||
"targetFolderId": dstDirFile.Id,
|
|
||||||
"targetFileName": filepath.Base(dst),
|
|
||||||
})
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetFormData(map[string]string{
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Delete(path string, account *model.Account) error {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
srcFile, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = GetState(account).Request(http.MethodPost, API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
|
||||||
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
|
||||||
"type": "DELETE",
|
|
||||||
"taskInfos": string(MustToBytes(utils.Json.Marshal(
|
|
||||||
[]*BatchTaskInfo{
|
|
||||||
{
|
|
||||||
FileId: srcFile.Id,
|
|
||||||
FileName: srcFile.Name,
|
|
||||||
IsFolder: BoolToNumber(srcFile.IsDir()),
|
|
||||||
},
|
|
||||||
}))),
|
|
||||||
})
|
|
||||||
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetFormData(map[string]string{
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.Bool1 {
|
|
||||||
return driver.FastUpload(file, parentFile, account)
|
|
||||||
}
|
|
||||||
return driver.CommonUpload(file, parentFile, account)
|
|
||||||
/*
|
|
||||||
if isFamily(account) {
|
|
||||||
return driver.uploadFamily(file, parentFile, account)
|
|
||||||
}
|
|
||||||
return driver.uploadPerson(file, parentFile, account)
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) CommonUpload(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
|
||||||
// 初始化上传
|
|
||||||
state := GetState(account)
|
|
||||||
const DEFAULT int64 = 10485760
|
|
||||||
count := int(math.Ceil(float64(file.Size) / float64(DEFAULT)))
|
|
||||||
|
|
||||||
params := Params{
|
|
||||||
"parentFolderId": parentFile.Id,
|
|
||||||
"fileName": url.PathEscape(file.Name),
|
|
||||||
"fileSize": fmt.Sprint(file.Size),
|
|
||||||
"sliceSize": fmt.Sprint(DEFAULT),
|
|
||||||
"lazyCheck": "1",
|
|
||||||
}
|
|
||||||
|
|
||||||
fullUrl := UPLOAD_URL
|
|
||||||
if isFamily(account) {
|
|
||||||
params.Set("familyId", account.SiteId)
|
|
||||||
fullUrl += "/family"
|
|
||||||
} else {
|
|
||||||
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
|
|
||||||
fullUrl += "/person"
|
|
||||||
}
|
|
||||||
|
|
||||||
var initMultiUpload InitMultiUploadResp
|
|
||||||
_, err := state.Request(http.MethodGet, fullUrl+"/initMultiUpload", params, func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&initMultiUpload) }, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
fileMd5 := md5.New()
|
|
||||||
silceMd5 := md5.New()
|
|
||||||
silceMd5Hexs := make([]string, 0, count)
|
|
||||||
byteData := bytes.NewBuffer(make([]byte, DEFAULT))
|
|
||||||
for i := 1; i <= count; i++ {
|
|
||||||
byteData.Reset()
|
|
||||||
silceMd5.Reset()
|
|
||||||
if n, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, byteData), file, DEFAULT); err != io.EOF && n == 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
md5Bytes := silceMd5.Sum(nil)
|
|
||||||
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Bytes)))
|
|
||||||
silceMd5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
|
|
||||||
|
|
||||||
var uploadUrl UploadUrlsResp
|
|
||||||
_, err = state.Request(http.MethodGet, fullUrl+"/getMultiUploadUrls",
|
|
||||||
Params{"partInfo": fmt.Sprintf("%d-%s", i, silceMd5Base64), "uploadFileId": initMultiUpload.Data.UploadFileID},
|
|
||||||
func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadUrl) },
|
|
||||||
account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadData := uploadUrl.UploadUrls[fmt.Sprint("partNumber_", i)]
|
|
||||||
req, _ := http.NewRequest(http.MethodPut, uploadData.RequestURL, byteData)
|
|
||||||
req.Header.Set("User-Agent", "")
|
|
||||||
for k, v := range ParseHttpHeader(uploadData.RequestHeader) {
|
|
||||||
req.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
for k, v := range clientSuffix() {
|
|
||||||
req.URL.RawQuery += fmt.Sprintf("&%s=%s", k, v)
|
|
||||||
}
|
|
||||||
r, err := base.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.StatusCode != http.StatusOK {
|
|
||||||
data, _ := io.ReadAll(r.Body)
|
|
||||||
r.Body.Close()
|
|
||||||
return fmt.Errorf(string(data))
|
|
||||||
}
|
|
||||||
r.Body.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
|
||||||
sliceMd5Hex := fileMd5Hex
|
|
||||||
if int64(file.Size) > DEFAULT {
|
|
||||||
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = state.Request(http.MethodGet, fullUrl+"/commitMultiUploadFile",
|
|
||||||
Params{
|
|
||||||
"uploadFileId": initMultiUpload.Data.UploadFileID,
|
|
||||||
"fileMd5": fileMd5Hex,
|
|
||||||
"sliceMd5": sliceMd5Hex,
|
|
||||||
"lazyCheck": "1",
|
|
||||||
"isLog": "0",
|
|
||||||
"opertype": "3",
|
|
||||||
},
|
|
||||||
func(r *resty.Request) { r.SetQueryParams(clientSuffix()) }, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) FastUpload(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
// 初始化上传
|
|
||||||
state := GetState(account)
|
|
||||||
|
|
||||||
const DEFAULT int64 = 10485760
|
|
||||||
count := int(math.Ceil(float64(file.Size) / float64(DEFAULT)))
|
|
||||||
|
|
||||||
// 优先计算所需信息
|
|
||||||
fileMd5 := md5.New()
|
|
||||||
silceMd5 := md5.New()
|
|
||||||
silceMd5Hexs := make([]string, 0, count)
|
|
||||||
silceMd5Base64s := make([]string, 0, count)
|
|
||||||
for i := 1; i <= count; i++ {
|
|
||||||
silceMd5.Reset()
|
|
||||||
if n, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, tempFile), file, DEFAULT); err != nil && n == 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
md5Byte := silceMd5.Sum(nil)
|
|
||||||
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Byte)))
|
|
||||||
silceMd5Base64s = append(silceMd5Base64s, fmt.Sprint(i, "-", base64.StdEncoding.EncodeToString(md5Byte)))
|
|
||||||
}
|
|
||||||
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
|
||||||
sliceMd5Hex := fileMd5Hex
|
|
||||||
if int64(file.Size) > DEFAULT {
|
|
||||||
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
|
||||||
}
|
|
||||||
|
|
||||||
params := Params{
|
|
||||||
"parentFolderId": parentFile.Id,
|
|
||||||
"fileName": url.PathEscape(file.Name),
|
|
||||||
"fileSize": fmt.Sprint(file.Size),
|
|
||||||
"fileMd5": fileMd5Hex,
|
|
||||||
"sliceSize": fmt.Sprint(DEFAULT),
|
|
||||||
"sliceMd5": sliceMd5Hex,
|
|
||||||
}
|
|
||||||
|
|
||||||
fullUrl := UPLOAD_URL
|
|
||||||
if isFamily(account) {
|
|
||||||
params.Set("familyId", account.SiteId)
|
|
||||||
fullUrl += "/family"
|
|
||||||
} else {
|
|
||||||
//params.Set("extend", `{"opScene":"1","relativepath":"","rootfolderid":""}`)
|
|
||||||
fullUrl += "/person"
|
|
||||||
}
|
|
||||||
|
|
||||||
var uploadInfo InitMultiUploadResp
|
|
||||||
_, err = state.Request(http.MethodGet, fullUrl+"/initMultiUpload", params, func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadInfo) }, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if uploadInfo.Data.FileDataExists != 1 {
|
|
||||||
var uploadUrls UploadUrlsResp
|
|
||||||
_, err := state.Request(http.MethodGet, fullUrl+"/getMultiUploadUrls",
|
|
||||||
Params{
|
|
||||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
|
||||||
"partInfo": strings.Join(silceMd5Base64s, ","),
|
|
||||||
},
|
|
||||||
func(r *resty.Request) { r.SetQueryParams(clientSuffix()).SetResult(&uploadUrls) },
|
|
||||||
account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for i := 1; i <= count; i++ {
|
|
||||||
uploadData := uploadUrls.UploadUrls[fmt.Sprint("partNumber_", i)]
|
|
||||||
req, _ := http.NewRequest(http.MethodPut, uploadData.RequestURL, io.NewSectionReader(tempFile, int64(i-1)*DEFAULT, DEFAULT))
|
|
||||||
req.Header.Set("User-Agent", "")
|
|
||||||
for k, v := range ParseHttpHeader(uploadData.RequestHeader) {
|
|
||||||
req.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
for k, v := range clientSuffix() {
|
|
||||||
req.URL.RawQuery += fmt.Sprintf("&%s=%s", k, v)
|
|
||||||
}
|
|
||||||
r, err := base.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.StatusCode != http.StatusOK {
|
|
||||||
data, _ := io.ReadAll(r.Body)
|
|
||||||
r.Body.Close()
|
|
||||||
return fmt.Errorf(string(data))
|
|
||||||
}
|
|
||||||
r.Body.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = state.Request(http.MethodGet, fullUrl+"/commitMultiUploadFile",
|
|
||||||
Params{
|
|
||||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
|
||||||
"isLog": "0",
|
|
||||||
"opertype": "3",
|
|
||||||
},
|
|
||||||
func(r *resty.Request) { r.SetQueryParams(clientSuffix()) },
|
|
||||||
account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func (driver Cloud189) uploadFamily(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
|
|
||||||
fileMd5 := md5.New()
|
|
||||||
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := GetState(account)
|
|
||||||
var createUpload CreateUploadFileResult
|
|
||||||
_, err = client.Request(http.MethodGet, API_URL+"/family/file/createFamilyFile.action", nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"fileMd5": hex.EncodeToString(fileMd5.Sum(nil)),
|
|
||||||
"fileName": file.Name,
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
"parentId": parentFile.Id,
|
|
||||||
"resumePolicy": "1",
|
|
||||||
"fileSize": fmt.Sprint(file.Size),
|
|
||||||
})
|
|
||||||
r.SetQueryParams(clientSuffix())
|
|
||||||
r.SetResult(&createUpload)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if createUpload.FileDataExists != 1 {
|
|
||||||
if createUpload.UploadFileId, err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Request(http.MethodGet, createUpload.FileCommitUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix())
|
|
||||||
r.SetHeaders(map[string]string{
|
|
||||||
"FamilyId": account.SiteId,
|
|
||||||
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
|
|
||||||
"ResumePolicy": "1",
|
|
||||||
})
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) uploadPerson(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
|
|
||||||
fileMd5 := md5.New()
|
|
||||||
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := GetState(account)
|
|
||||||
var createUpload CreateUploadFileResult
|
|
||||||
_, err = client.Request(http.MethodPost, API_URL+"/createUploadFile.action", nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix())
|
|
||||||
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
|
||||||
"parentFolderId": parentFile.Id,
|
|
||||||
"baseFileId": "",
|
|
||||||
"fileName": file.Name,
|
|
||||||
"size": fmt.Sprint(file.Size),
|
|
||||||
"md5": hex.EncodeToString(fileMd5.Sum(nil)),
|
|
||||||
// "lastWrite": param.LastWrite,
|
|
||||||
// "localPath": strings.ReplaceAll(file.ParentPath, "\\", "/"),
|
|
||||||
"opertype": "1",
|
|
||||||
"flag": "1",
|
|
||||||
"resumePolicy": "1",
|
|
||||||
"isLog": "0",
|
|
||||||
"fileExt": "",
|
|
||||||
})
|
|
||||||
r.SetResult(&createUpload)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if createUpload.FileDataExists != 1 {
|
|
||||||
if createUpload.UploadFileId, err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.Request(http.MethodPost, createUpload.FileCommitUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix())
|
|
||||||
r.SetFormData(map[string]string{
|
|
||||||
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
|
|
||||||
"opertype": "5", //5 覆盖 1 重命名
|
|
||||||
"ResumePolicy": "1",
|
|
||||||
"isLog": "0",
|
|
||||||
})
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) uploadFileData(file *model.FileStream, tempFile *os.File, createUpload CreateUploadFileResult, account *model.Account) (int64, error) {
|
|
||||||
uploadFileState, err := driver.getUploadFileState(createUpload.UploadFileId, account)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if uploadFileState.FileDataExists == 1 || uploadFileState.DataSize == int64(file.Size) {
|
|
||||||
return uploadFileState.UploadFileId, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = tempFile.Seek(uploadFileState.DataSize, io.SeekStart); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = GetState(account).Request("PUT", uploadFileState.FileUploadUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix())
|
|
||||||
r.SetHeaders(map[string]string{
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
"ResumePolicy": "1",
|
|
||||||
"Edrive-UploadFileRange": fmt.Sprintf("bytes=%d-%d", uploadFileState.DataSize, file.Size),
|
|
||||||
"Expect": "100-continue",
|
|
||||||
})
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetHeaders(map[string]string{
|
|
||||||
"familyId": account.SiteId,
|
|
||||||
"UploadFileId": fmt.Sprint(uploadFileState.UploadFileId),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
r.SetHeader("Edrive-UploadFileId", fmt.Sprint(uploadFileState.UploadFileId))
|
|
||||||
}
|
|
||||||
r.SetBody(tempFile)
|
|
||||||
}, account)
|
|
||||||
return uploadFileState.UploadFileId, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Cloud189) getUploadFileState(uploadFileId int64, account *model.Account) (*UploadFileStatusResult, error) {
|
|
||||||
fullUrl := API_URL
|
|
||||||
if isFamily(account) {
|
|
||||||
fullUrl += "/family/file/getFamilyFileStatus.action"
|
|
||||||
} else {
|
|
||||||
fullUrl += "/getUploadFileStatus.action"
|
|
||||||
}
|
|
||||||
var uploadFileState UploadFileStatusResult
|
|
||||||
_, err := GetState(account).Request(http.MethodGet, fullUrl, nil, func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(clientSuffix())
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"uploadFileId": fmt.Sprint(uploadFileId),
|
|
||||||
"resumePolicy": "1",
|
|
||||||
})
|
|
||||||
if isFamily(account) {
|
|
||||||
r.SetQueryParam("familyId", account.SiteId)
|
|
||||||
}
|
|
||||||
r.SetResult(&uploadFileState)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &uploadFileState, nil
|
|
||||||
}*/
|
|
||||||
|
|
||||||
var _ base.Driver = (*Cloud189)(nil)
|
|
@ -1,180 +0,0 @@
|
|||||||
package _189
|
|
||||||
|
|
||||||
import "encoding/xml"
|
|
||||||
|
|
||||||
type LoginParam struct {
|
|
||||||
CaptchaToken string
|
|
||||||
Lt string
|
|
||||||
ParamId string
|
|
||||||
ReqId string
|
|
||||||
jRsaKey string
|
|
||||||
|
|
||||||
vCodeID string
|
|
||||||
vCodeRS string
|
|
||||||
}
|
|
||||||
|
|
||||||
// 居然有四种返回方式
|
|
||||||
type Erron struct {
|
|
||||||
ResCode string `json:"res_code"`
|
|
||||||
ResMessage string `json:"res_message"`
|
|
||||||
|
|
||||||
XMLName xml.Name `xml:"error"`
|
|
||||||
Code string `json:"code" xml:"code"`
|
|
||||||
Message string `json:"message" xml:"message"`
|
|
||||||
|
|
||||||
// Code string `json:"code"`
|
|
||||||
Msg string `json:"msg"`
|
|
||||||
|
|
||||||
ErrorCode string `json:"errorCode"`
|
|
||||||
ErrorMsg string `json:"errorMsg"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 刷新session返回
|
|
||||||
type UserSessionResp struct {
|
|
||||||
ResCode int `json:"res_code"`
|
|
||||||
ResMessage string `json:"res_message"`
|
|
||||||
|
|
||||||
LoginName string `json:"loginName"`
|
|
||||||
|
|
||||||
KeepAlive int `json:"keepAlive"`
|
|
||||||
GetFileDiffSpan int `json:"getFileDiffSpan"`
|
|
||||||
GetUserInfoSpan int `json:"getUserInfoSpan"`
|
|
||||||
|
|
||||||
// 个人云
|
|
||||||
SessionKey string `json:"sessionKey"`
|
|
||||||
SessionSecret string `json:"sessionSecret"`
|
|
||||||
// 家庭云
|
|
||||||
FamilySessionKey string `json:"familySessionKey"`
|
|
||||||
FamilySessionSecret string `json:"familySessionSecret"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//登录返回
|
|
||||||
type appSessionResp struct {
|
|
||||||
UserSessionResp
|
|
||||||
|
|
||||||
IsSaveName string `json:"isSaveName"`
|
|
||||||
|
|
||||||
// 会话刷新Token
|
|
||||||
AccessToken string `json:"accessToken"`
|
|
||||||
//Token刷新
|
|
||||||
RefreshToken string `json:"refreshToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FamilyInfoListResp struct {
|
|
||||||
FamilyInfoResp []FamilyInfoResp `json:"familyInfoResp"`
|
|
||||||
}
|
|
||||||
type FamilyInfoResp struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
CreateTime string `json:"createTime"`
|
|
||||||
FamilyID int `json:"familyId"`
|
|
||||||
RemarkName string `json:"remarkName"`
|
|
||||||
Type int `json:"type"`
|
|
||||||
UseFlag int `json:"useFlag"`
|
|
||||||
UserRole int `json:"userRole"`
|
|
||||||
}
|
|
||||||
|
|
||||||
/*文件部分*/
|
|
||||||
// 文件
|
|
||||||
type Cloud189File struct {
|
|
||||||
CreateDate string `json:"createDate"`
|
|
||||||
FileCata int64 `json:"fileCata"`
|
|
||||||
Icon struct {
|
|
||||||
//iconOption 5
|
|
||||||
SmallUrl string `json:"smallUrl"`
|
|
||||||
LargeUrl string `json:"largeUrl"`
|
|
||||||
|
|
||||||
// iconOption 10
|
|
||||||
Max600 string `json:"max600"`
|
|
||||||
MediumURL string `json:"mediumUrl"`
|
|
||||||
} `json:"icon"`
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
LastOpTime string `json:"lastOpTime"`
|
|
||||||
Md5 string `json:"md5"`
|
|
||||||
MediaType int `json:"mediaType"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Orientation int64 `json:"orientation"`
|
|
||||||
Rev string `json:"rev"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
StarLabel int64 `json:"starLabel"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 文件夹
|
|
||||||
type Cloud189Folder struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
ParentID int64 `json:"parentId"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
|
|
||||||
FileCata int64 `json:"fileCata"`
|
|
||||||
FileCount int64 `json:"fileCount"`
|
|
||||||
|
|
||||||
LastOpTime string `json:"lastOpTime"`
|
|
||||||
CreateDate string `json:"createDate"`
|
|
||||||
|
|
||||||
FileListSize int64 `json:"fileListSize"`
|
|
||||||
Rev string `json:"rev"`
|
|
||||||
StarLabel int64 `json:"starLabel"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Cloud189FilesResp struct {
|
|
||||||
//ResCode int `json:"res_code"`
|
|
||||||
//ResMessage string `json:"res_message"`
|
|
||||||
FileListAO struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
FileList []Cloud189File `json:"fileList"`
|
|
||||||
FolderList []Cloud189Folder `json:"folderList"`
|
|
||||||
} `json:"fileListAO"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TaskInfo 任务信息
|
|
||||||
type BatchTaskInfo struct {
|
|
||||||
// FileId 文件ID
|
|
||||||
FileId string `json:"fileId"`
|
|
||||||
// FileName 文件名
|
|
||||||
FileName string `json:"fileName"`
|
|
||||||
// IsFolder 是否是文件夹,0-否,1-是
|
|
||||||
IsFolder int `json:"isFolder"`
|
|
||||||
// SrcParentId 文件所在父目录ID
|
|
||||||
//SrcParentId string `json:"srcParentId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
type CreateUploadFileResult struct {
|
|
||||||
// UploadFileId 上传文件请求ID
|
|
||||||
UploadFileId int64 `json:"uploadFileId"`
|
|
||||||
// FileUploadUrl 上传文件数据的URL路径
|
|
||||||
FileUploadUrl string `json:"fileUploadUrl"`
|
|
||||||
// FileCommitUrl 上传文件完成后确认路径
|
|
||||||
FileCommitUrl string `json:"fileCommitUrl"`
|
|
||||||
// FileDataExists 文件是否已存在云盘中,0-未存在,1-已存在
|
|
||||||
FileDataExists int `json:"fileDataExists"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadFileStatusResult struct {
|
|
||||||
// 上传文件的ID
|
|
||||||
UploadFileId int64 `json:"uploadFileId"`
|
|
||||||
// 已上传的大小
|
|
||||||
DataSize int64 `json:"dataSize"`
|
|
||||||
FileUploadUrl string `json:"fileUploadUrl"`
|
|
||||||
FileCommitUrl string `json:"fileCommitUrl"`
|
|
||||||
FileDataExists int `json:"fileDataExists"`
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
type InitMultiUploadResp struct {
|
|
||||||
//Code string `json:"code"`
|
|
||||||
Data struct {
|
|
||||||
UploadType int `json:"uploadType"`
|
|
||||||
UploadHost string `json:"uploadHost"`
|
|
||||||
UploadFileID string `json:"uploadFileId"`
|
|
||||||
FileDataExists int `json:"fileDataExists"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
type UploadUrlsResp struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
UploadUrls map[string]Part `json:"uploadUrls"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Part struct {
|
|
||||||
RequestURL string `json:"requestURL"`
|
|
||||||
RequestHeader string `json:"requestHeader"`
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
package _189
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/pem"
|
|
||||||
"fmt"
|
|
||||||
rand2 "math/rand"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
APP_ID = "8025431004"
|
|
||||||
CLIENT_TYPE = "10020"
|
|
||||||
VERSION = "6.2"
|
|
||||||
|
|
||||||
WEB_URL = "https://cloud.189.cn"
|
|
||||||
AUTH_URL = "https://open.e.189.cn"
|
|
||||||
API_URL = "https://api.cloud.189.cn"
|
|
||||||
UPLOAD_URL = "https://upload.cloud.189.cn"
|
|
||||||
|
|
||||||
RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"
|
|
||||||
|
|
||||||
PC = "TELEPC"
|
|
||||||
MAC = "TELEMAC"
|
|
||||||
|
|
||||||
CHANNEL_ID = "web_cloud.189.cn"
|
|
||||||
)
|
|
||||||
|
|
||||||
func clientSuffix() map[string]string {
|
|
||||||
return map[string]string{
|
|
||||||
"clientType": PC,
|
|
||||||
"version": VERSION,
|
|
||||||
"channelId": CHANNEL_ID,
|
|
||||||
"rand": fmt.Sprintf("%d_%d", rand2.Int63n(1e5), rand2.Int63n(1e10)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 带params的SignatureOfHmac HMAC签名
|
|
||||||
func signatureOfHmac(sessionSecret, sessionKey, operate, fullUrl, dateOfGmt, param string) string {
|
|
||||||
u, _ := url.Parse(fullUrl)
|
|
||||||
mac := hmac.New(sha1.New, []byte(sessionSecret))
|
|
||||||
data := fmt.Sprintf("SessionKey=%s&Operate=%s&RequestURI=%s&Date=%s", sessionKey, operate, u.Path, dateOfGmt)
|
|
||||||
if param != "" {
|
|
||||||
data += fmt.Sprintf("¶ms=%s", param)
|
|
||||||
}
|
|
||||||
mac.Write([]byte(data))
|
|
||||||
return strings.ToUpper(hex.EncodeToString(mac.Sum(nil)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取http规范的时间
|
|
||||||
func getHttpDateStr() string {
|
|
||||||
return time.Now().UTC().Format(http.TimeFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RAS 加密用户名密码
|
|
||||||
func rsaEncrypt(publicKey, origData string) string {
|
|
||||||
block, _ := pem.Decode([]byte(publicKey))
|
|
||||||
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
|
|
||||||
data, _ := rsa.EncryptPKCS1v15(rand.Reader, pubInterface.(*rsa.PublicKey), []byte(origData))
|
|
||||||
return base64ToHex(base64.StdEncoding.EncodeToString(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// aes 加密params
|
|
||||||
func AesECBEncrypt(data, key string) string {
|
|
||||||
block, _ := aes.NewCipher([]byte(key))
|
|
||||||
paddingData := PKCS7Padding([]byte(data), block.BlockSize())
|
|
||||||
decrypted := make([]byte, len(paddingData))
|
|
||||||
size := block.BlockSize()
|
|
||||||
for src, dst := paddingData, decrypted; len(src) > 0; src, dst = src[size:], dst[size:] {
|
|
||||||
block.Encrypt(dst[:size], src[:size])
|
|
||||||
}
|
|
||||||
return strings.ToUpper(hex.EncodeToString(decrypted))
|
|
||||||
}
|
|
||||||
|
|
||||||
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
|
||||||
padding := blockSize - len(ciphertext)%blockSize
|
|
||||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
|
||||||
return append(ciphertext, padtext...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 时间戳
|
|
||||||
func timestamp() int64 {
|
|
||||||
return time.Now().UTC().UnixNano() / 1e6
|
|
||||||
}
|
|
||||||
|
|
||||||
func base64ToHex(a string) string {
|
|
||||||
v, _ := base64.StdEncoding.DecodeString(a)
|
|
||||||
return strings.ToUpper(hex.EncodeToString(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func isFamily(account *model.Account) bool {
|
|
||||||
return account.InternalType == "Family"
|
|
||||||
}
|
|
||||||
|
|
||||||
func toFamilyOrderBy(o string) string {
|
|
||||||
switch o {
|
|
||||||
case "filename":
|
|
||||||
return "1"
|
|
||||||
case "filesize":
|
|
||||||
return "2"
|
|
||||||
case "lastOpTime":
|
|
||||||
return "3"
|
|
||||||
default:
|
|
||||||
return "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseHttpHeader(str string) map[string]string {
|
|
||||||
header := make(map[string]string)
|
|
||||||
for _, value := range strings.Split(str, "&") {
|
|
||||||
i := strings.Index(value, "=")
|
|
||||||
header[strings.TrimSpace(value[0:i])] = strings.TrimSpace(value[i+1:])
|
|
||||||
}
|
|
||||||
return header
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustString(str string, err error) string {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustToBytes(b []byte, err error) []byte {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func BoolToNumber(b bool) int {
|
|
||||||
if b {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustParseTime(str string) *time.Time {
|
|
||||||
loc, _ := time.LoadLocation("Local")
|
|
||||||
lastOpTime, _ := time.ParseInLocation("2006-01-02 15:04:05", str, loc)
|
|
||||||
return &lastOpTime
|
|
||||||
}
|
|
||||||
|
|
||||||
type Params map[string]string
|
|
||||||
|
|
||||||
func (p Params) Set(k, v string) {
|
|
||||||
p[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Params) Encode() string {
|
|
||||||
if p == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
var buf strings.Builder
|
|
||||||
keys := make([]string, 0, len(p))
|
|
||||||
for k := range p {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
for _, k := range keys {
|
|
||||||
if buf.Len() > 0 {
|
|
||||||
buf.WriteByte('&')
|
|
||||||
}
|
|
||||||
buf.WriteString(k)
|
|
||||||
buf.WriteByte('=')
|
|
||||||
buf.WriteString(p[k])
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
@ -1,209 +0,0 @@
|
|||||||
package alidrive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
var aliClient = resty.New()
|
|
||||||
|
|
||||||
func (driver AliDrive) FormatFile(file *AliFile) *model.File {
|
|
||||||
f := &model.File{
|
|
||||||
Id: file.FileId,
|
|
||||||
Name: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
UpdatedAt: file.UpdatedAt,
|
|
||||||
Thumbnail: file.Thumbnail,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
Url: file.Url,
|
|
||||||
}
|
|
||||||
f.Type = file.GetType()
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFile, error) {
|
|
||||||
marker := "first"
|
|
||||||
res := make([]AliFile, 0)
|
|
||||||
for marker != "" {
|
|
||||||
if marker == "first" {
|
|
||||||
marker = ""
|
|
||||||
}
|
|
||||||
var resp AliFiles
|
|
||||||
var e AliRespError
|
|
||||||
_, err := aliClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"fields": "*",
|
|
||||||
"image_thumbnail_process": "image/resize,w_400/format,jpeg",
|
|
||||||
"image_url_process": "image/resize,w_1920/format,jpeg",
|
|
||||||
"limit": account.Limit,
|
|
||||||
"marker": marker,
|
|
||||||
"order_by": account.OrderBy,
|
|
||||||
"order_direction": account.OrderDirection,
|
|
||||||
"parent_file_id": fileId,
|
|
||||||
"video_thumbnail_process": "video/snapshot,t_0,f_jpg,ar_auto,w_300",
|
|
||||||
"url_expire_sec": 14400,
|
|
||||||
}).Post("https://api.aliyundrive.com/v2/file/list")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Code != "" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return driver.GetFiles(fileId, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
marker = resp.NextMarker
|
|
||||||
res = append(res, resp.Items...)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, error) {
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
dir = utils.ParsePath(dir)
|
|
||||||
_, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
parentFiles_, _ := base.GetCache(dir, account)
|
|
||||||
parentFiles, _ := parentFiles_.([]AliFile)
|
|
||||||
for _, file := range parentFiles {
|
|
||||||
if file.Name == name {
|
|
||||||
if file.Type == "file" {
|
|
||||||
return &file, err
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("not file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) RefreshToken(account *model.Account) error {
|
|
||||||
url := "https://auth.aliyundrive.com/v2/account/token"
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e AliRespError
|
|
||||||
_, err := aliClient.R().
|
|
||||||
//ForceContentType("application/json").
|
|
||||||
SetBody(base.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
|
|
||||||
SetResult(&resp).
|
|
||||||
SetError(&e).
|
|
||||||
Post(url)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugf("%+v,%+v", resp, e)
|
|
||||||
if e.Code != "" {
|
|
||||||
account.Status = e.Message
|
|
||||||
return fmt.Errorf("failed to refresh token: %s", e.Message)
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
}
|
|
||||||
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) rename(fileId, name string, account *model.Account) error {
|
|
||||||
var resp base.Json
|
|
||||||
var e AliRespError
|
|
||||||
_, err := aliClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"check_name_mode": "refuse",
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"file_id": fileId,
|
|
||||||
"name": name,
|
|
||||||
}).Post("https://api.aliyundrive.com/v3/file/update")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Code != "" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return driver.rename(fileId, name, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
if resp["name"] == name {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%+v", resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) batch(srcId, dstId string, url string, account *model.Account) error {
|
|
||||||
var e AliRespError
|
|
||||||
res, err := aliClient.R().SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"requests": []base.Json{
|
|
||||||
{
|
|
||||||
"headers": base.Json{
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
"method": "POST",
|
|
||||||
"id": srcId,
|
|
||||||
"body": base.Json{
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"file_id": srcId,
|
|
||||||
"to_drive_id": account.DriveId,
|
|
||||||
"to_parent_file_id": dstId,
|
|
||||||
},
|
|
||||||
"url": url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"resource": "file",
|
|
||||||
}).Post("https://api.aliyundrive.com/v3/batch")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Code != "" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return driver.batch(srcId, dstId, url, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
status := jsoniter.Get(res.Body(), "responses", 0, "status").ToInt()
|
|
||||||
if status < 400 && status >= 100 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New(res.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&AliDrive{})
|
|
||||||
aliClient.
|
|
||||||
SetTimeout(base.DefaultTimeout).
|
|
||||||
SetRetryCount(3).
|
|
||||||
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
|
|
||||||
SetHeader("content-type", "application/json").
|
|
||||||
SetHeader("origin", "https://www.aliyundrive.com")
|
|
||||||
}
|
|
@ -1,566 +0,0 @@
|
|||||||
package alidrive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliDrive struct{}
|
|
||||||
|
|
||||||
func (driver AliDrive) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "AliDrive",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "refresh_token",
|
|
||||||
Label: "refresh token",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "name,size,updated_at,created_at",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "ASC,DESC",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "limit",
|
|
||||||
Label: "limit",
|
|
||||||
Type: base.TypeNumber,
|
|
||||||
Required: false,
|
|
||||||
Description: ">0 and <=200",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "bool_1",
|
|
||||||
Label: "fast upload",
|
|
||||||
Type: base.TypeBool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if old != nil {
|
|
||||||
conf.Cron.Remove(cron.EntryID(old.CronId))
|
|
||||||
}
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if account.RootFolder == "" {
|
|
||||||
account.RootFolder = "root"
|
|
||||||
}
|
|
||||||
if account.Limit == 0 {
|
|
||||||
account.Limit = 200
|
|
||||||
}
|
|
||||||
err := driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var resp base.Json
|
|
||||||
_, _ = aliClient.R().SetResult(&resp).
|
|
||||||
SetBody("{}").
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
Post("https://api.aliyundrive.com/v2/user/get")
|
|
||||||
log.Debugf("user info: %+v", resp)
|
|
||||||
account.DriveId = resp["default_drive_id"].(string)
|
|
||||||
cronId, err := conf.Cron.AddFunc("@every 2h", func() {
|
|
||||||
id := account.ID
|
|
||||||
log.Debugf("ali account id: %d", id)
|
|
||||||
newAccount, err := model.GetAccountById(id)
|
|
||||||
log.Debugf("ali account: %+v", newAccount)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = driver.RefreshToken(newAccount)
|
|
||||||
_ = model.SaveAccount(newAccount)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
account.CronId = int(cronId)
|
|
||||||
err = model.SaveAccount(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var rawFiles []AliFile
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
rawFiles, _ = cache.([]AliFile)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(rawFiles) > 0 {
|
|
||||||
_ = base.SetCache(path, rawFiles, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
for _, file := range rawFiles {
|
|
||||||
files = append(files, *driver.FormatFile(&file))
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var resp base.Json
|
|
||||||
var e AliRespError
|
|
||||||
_, err = aliClient.R().SetResult(&resp).
|
|
||||||
SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"file_id": file.Id,
|
|
||||||
"expire_sec": 14400,
|
|
||||||
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Code != "" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return driver.Link(args, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Headers: []base.Header{
|
|
||||||
{
|
|
||||||
Name: "Referer",
|
|
||||||
Value: "https://www.aliyundrive.com/",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Url: resp["url"].(string),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("ali path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver AliDrive) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
// r.Header.Del("Origin")
|
|
||||||
// r.Header.Set("Referer", "https://www.aliyundrive.com/")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
file, err := driver.GetFile(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// office
|
|
||||||
var resp base.Json
|
|
||||||
var e AliRespError
|
|
||||||
var url string
|
|
||||||
req := base.Json{
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"file_id": file.FileId,
|
|
||||||
}
|
|
||||||
switch file.Category {
|
|
||||||
case "doc":
|
|
||||||
{
|
|
||||||
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
|
|
||||||
req["access_token"] = account.AccessToken
|
|
||||||
}
|
|
||||||
case "video":
|
|
||||||
{
|
|
||||||
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
|
|
||||||
req["category"] = "live_transcoding"
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(req).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Code != "" {
|
|
||||||
return nil, fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) MakeDir(path string, account *model.Account) error {
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
parentFile, err := driver.File(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
var resp base.Json
|
|
||||||
var e AliRespError
|
|
||||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"check_name_mode": "refuse",
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"name": name,
|
|
||||||
"parent_file_id": parentFile.Id,
|
|
||||||
"type": "folder",
|
|
||||||
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
|
||||||
if e.Code != "" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return driver.MakeDir(path, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
if resp["file_name"] == name {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%+v", resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Move(src string, dst string, account *model.Account) error {
|
|
||||||
dstDir, _ := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstDirFile, err := driver.File(dstDir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = driver.batch(srcFile.Id, dstDirFile.Id, "/file/move", account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
_, dstName := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = driver.rename(srcFile.Id, dstName, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
dstDir, _ := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstDirFile, err := driver.File(dstDir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = driver.batch(srcFile.Id, dstDirFile.Id, "/file/copy", account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Delete(path string, account *model.Account) error {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var e AliRespError
|
|
||||||
res, err := aliClient.R().SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"file_id": file.Id,
|
|
||||||
}).Post("https://api.aliyundrive.com/v2/recyclebin/trash")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Code != "" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return driver.Delete(path, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
if res.StatusCode() < 400 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New(res.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadResp struct {
|
|
||||||
FileId string `json:"file_id"`
|
|
||||||
UploadId string `json:"upload_id"`
|
|
||||||
PartInfoList []struct {
|
|
||||||
UploadUrl string `json:"upload_url"`
|
|
||||||
} `json:"part_info_list"`
|
|
||||||
|
|
||||||
RapidUpload bool `json:"rapid_upload"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
const DEFAULT int64 = 10485760
|
|
||||||
var count = int(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
|
||||||
|
|
||||||
partInfoList := make([]base.Json, 0, count)
|
|
||||||
for i := 1; i <= count; i++ {
|
|
||||||
partInfoList = append(partInfoList, base.Json{"part_number": i})
|
|
||||||
}
|
|
||||||
|
|
||||||
reqBody := base.Json{
|
|
||||||
"check_name_mode": "overwrite",
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"name": file.GetFileName(),
|
|
||||||
"parent_file_id": parentFile.Id,
|
|
||||||
"part_info_list": partInfoList,
|
|
||||||
"size": file.GetSize(),
|
|
||||||
"type": "file",
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.Bool1 {
|
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
|
||||||
io.CopyN(buf, file, 1024)
|
|
||||||
reqBody["pre_hash"] = utils.GetSHA1Encode(buf.String())
|
|
||||||
// 把头部拼接回去
|
|
||||||
file.File = struct {
|
|
||||||
io.Reader
|
|
||||||
io.Closer
|
|
||||||
}{
|
|
||||||
Reader: io.MultiReader(buf, file.File),
|
|
||||||
Closer: file.File,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reqBody["content_hash_name"] = "none"
|
|
||||||
reqBody["proof_version"] = "v1"
|
|
||||||
}
|
|
||||||
|
|
||||||
var resp UploadResp
|
|
||||||
var e AliRespError
|
|
||||||
client := aliClient.R().SetResult(&resp).SetError(&e).SetHeader("authorization", "Bearer\t"+account.AccessToken).SetBody(reqBody)
|
|
||||||
|
|
||||||
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Code != "" && e.Code != "PreHashMatched" {
|
|
||||||
if e.Code == "AccessTokenInvalid" {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return driver.Upload(file, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.Bool1 && e.Code == "PreHashMatched" {
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
|
|
||||||
delete(reqBody, "pre_hash")
|
|
||||||
h := sha1.New()
|
|
||||||
if _, err = io.Copy(io.MultiWriter(tempFile, h), file.File); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
|
|
||||||
reqBody["content_hash_name"] = "sha1"
|
|
||||||
reqBody["proof_version"] = "v1"
|
|
||||||
|
|
||||||
/*
|
|
||||||
js 隐性转换太坑不知道有没有bug
|
|
||||||
var n = e.access_token,
|
|
||||||
r = new BigNumber('0x'.concat(md5(n).slice(0, 16))),
|
|
||||||
i = new BigNumber(t.file.size),
|
|
||||||
o = i ? r.mod(i) : new gt.BigNumber(0);
|
|
||||||
(t.file.slice(o.toNumber(), Math.min(o.plus(8).toNumber(), t.file.size)))
|
|
||||||
*/
|
|
||||||
buf := make([]byte, 8)
|
|
||||||
r, _ := new(big.Int).SetString(utils.GetMD5Encode(account.AccessToken)[:16], 16)
|
|
||||||
i := new(big.Int).SetUint64(file.Size)
|
|
||||||
o := r.Mod(r, i)
|
|
||||||
n, _ := io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
|
|
||||||
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
|
||||||
|
|
||||||
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Code != "" && e.Code != "PreHashMatched" {
|
|
||||||
return fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp.RapidUpload {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 秒传失败
|
|
||||||
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
file.File = tempFile
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, partInfo := range resp.PartInfoList {
|
|
||||||
req, err := http.NewRequest("PUT", partInfo.UploadUrl, io.LimitReader(file.File, DEFAULT))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res, err := base.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugf("%+v", res)
|
|
||||||
res.Body.Close()
|
|
||||||
//res, err := base.BaseClient.R().
|
|
||||||
// SetHeader("Content-Type","").
|
|
||||||
// SetBody(byteData).Put(resp.PartInfoList[i].UploadUrl)
|
|
||||||
//if err != nil {
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
//log.Debugf("put to %s : %d,%s", resp.PartInfoList[i].UploadUrl, res.StatusCode(),res.String())
|
|
||||||
}
|
|
||||||
var resp2 base.Json
|
|
||||||
_, err = aliClient.R().SetResult(&resp2).SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"drive_id": account.DriveId,
|
|
||||||
"file_id": resp.FileId,
|
|
||||||
"upload_id": resp.UploadId,
|
|
||||||
}).Post("https://api.aliyundrive.com/v2/file/complete")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Code != "" && e.Code != "PreHashMatched" {
|
|
||||||
//if e.Code == "AccessTokenInvalid" {
|
|
||||||
// err = driver.RefreshToken(account)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// } else {
|
|
||||||
// _ = model.SaveAccount(account)
|
|
||||||
// return driver.Upload(file, account)
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
return fmt.Errorf("%s", e.Message)
|
|
||||||
}
|
|
||||||
if resp2["file_id"] == resp.FileId {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%+v", resp2)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*AliDrive)(nil)
|
|
@ -1,53 +0,0 @@
|
|||||||
package alidrive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AliRespError struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AliFiles struct {
|
|
||||||
Items []AliFile `json:"items"`
|
|
||||||
NextMarker string `json:"next_marker"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AliFile struct {
|
|
||||||
DriveId string `json:"drive_id"`
|
|
||||||
CreatedAt *time.Time `json:"created_at"`
|
|
||||||
FileExtension string `json:"file_extension"`
|
|
||||||
FileId string `json:"file_id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Category string `json:"category"`
|
|
||||||
ParentFileId string `json:"parent_file_id"`
|
|
||||||
UpdatedAt *time.Time `json:"updated_at"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f AliFile) GetSize() uint64 {
|
|
||||||
return uint64(f.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f AliFile) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f AliFile) GetType() int {
|
|
||||||
if f.Type == "folder" {
|
|
||||||
return conf.FOLDER
|
|
||||||
}
|
|
||||||
if f.Category == "video" {
|
|
||||||
return conf.VIDEO
|
|
||||||
}
|
|
||||||
if f.Category == "image" {
|
|
||||||
return conf.IMAGE
|
|
||||||
}
|
|
||||||
return utils.GetFileType(f.FileExtension)
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package alist
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BaseResp struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PathResp struct {
|
|
||||||
BaseResp
|
|
||||||
Data struct {
|
|
||||||
Type string `json:"type"`
|
|
||||||
//Meta Meta `json:"meta"`
|
|
||||||
Files []model.File `json:"files"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PreviewResp struct {
|
|
||||||
BaseResp
|
|
||||||
Data interface{} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Alist) Login(account *model.Account) error {
|
|
||||||
var resp BaseResp
|
|
||||||
_, err := base.RestyClient.R().SetResult(&resp).
|
|
||||||
SetHeader("Authorization", account.AccessToken).
|
|
||||||
Get(account.SiteUrl + "/api/admin/login")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.Code != 200 {
|
|
||||||
return errors.New(resp.Message)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Alist{})
|
|
||||||
}
|
|
@ -1,192 +0,0 @@
|
|||||||
package alist
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Alist struct{}
|
|
||||||
|
|
||||||
func (driver Alist) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Alist",
|
|
||||||
NoNeedSetLink: true,
|
|
||||||
NoCors: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "site_url",
|
|
||||||
Label: "alist site url",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "access_token",
|
|
||||||
Label: "token",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Description: "admin token",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
account.SiteUrl = strings.TrimRight(account.SiteUrl, "/")
|
|
||||||
if account.RootFolder == "" {
|
|
||||||
account.RootFolder = "/"
|
|
||||||
}
|
|
||||||
err := driver.Login(account)
|
|
||||||
if err == nil {
|
|
||||||
account.Status = "work"
|
|
||||||
} else {
|
|
||||||
account.Status = err.Error()
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
now := time.Now()
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: "root",
|
|
||||||
Name: "root",
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: &now,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
_, files, err := driver.Path(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if files == nil {
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
name := utils.Base(path)
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
//return nil, base.ErrNotImplement
|
|
||||||
_, files, err := driver.Path(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if files == nil {
|
|
||||||
return nil, base.ErrNotFolder
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
path := args.Path
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
name := utils.Base(path)
|
|
||||||
flag := "d"
|
|
||||||
if utils.GetFileType(filepath.Ext(path)) == conf.TEXT {
|
|
||||||
flag = "p"
|
|
||||||
}
|
|
||||||
link := base.Link{}
|
|
||||||
link.Url = fmt.Sprintf("%s/%s%s?sign=%s", account.SiteUrl, flag, path, utils.SignWithToken(name, conf.Token))
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
path = filepath.Join(account.RootFolder, path)
|
|
||||||
path = strings.ReplaceAll(path, "\\", "/")
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files := cache.([]model.File)
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
var resp PathResp
|
|
||||||
_, err = base.RestyClient.R().SetResult(&resp).
|
|
||||||
SetHeader("Authorization", account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"path": path,
|
|
||||||
}).Post(account.SiteUrl + "/api/public/path")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if resp.Code != 200 {
|
|
||||||
return nil, nil, errors.New(resp.Message)
|
|
||||||
}
|
|
||||||
if resp.Data.Type == "file" {
|
|
||||||
return &resp.Data.Files[0], nil, nil
|
|
||||||
}
|
|
||||||
if len(resp.Data.Files) > 0 {
|
|
||||||
_ = base.SetCache(path, resp.Data.Files, account)
|
|
||||||
}
|
|
||||||
return nil, resp.Data.Files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Alist) Proxy(r *http.Request, account *model.Account) {}
|
|
||||||
|
|
||||||
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
var resp PathResp
|
|
||||||
_, err := base.RestyClient.R().SetResult(&resp).
|
|
||||||
SetHeader("Authorization", account.AccessToken).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"path": path,
|
|
||||||
}).Post(account.SiteUrl + "/api/public/preview")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.Code != 200 {
|
|
||||||
return nil, errors.New(resp.Message)
|
|
||||||
}
|
|
||||||
return resp.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) MakeDir(path string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Move(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Delete(path string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Alist) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Alist)(nil)
|
|
@ -1,52 +0,0 @@
|
|||||||
package drivers
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/Xhofe/alist/drivers/123"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/139"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/189"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/189pc"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/alidrive"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/alist"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/baidu"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/ftp"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/google"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/lanzou"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/mediatrack"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/native"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/onedrive"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/pikpak"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/quark"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/s3"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/sftp"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/shandian"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/teambition"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/uss"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/webdav"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/xunlei"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/yandex"
|
|
||||||
_ "github.com/Xhofe/alist/drivers/baiduphoto"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var NoCors string
|
|
||||||
var NoUpload string
|
|
||||||
|
|
||||||
func GetConfig() {
|
|
||||||
for k, v := range base.GetDriversMap() {
|
|
||||||
if v.Config().NoCors {
|
|
||||||
NoCors += k + ","
|
|
||||||
}
|
|
||||||
if v.Upload(nil, nil) != base.ErrEmptyFile {
|
|
||||||
NoUpload += k + ","
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NoCors = strings.Trim(NoCors, ",")
|
|
||||||
NoUpload += "root"
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.Debug("all init")
|
|
||||||
GetConfig()
|
|
||||||
}
|
|
@ -1,185 +0,0 @@
|
|||||||
package baidu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (driver Baidu) RefreshToken(account *model.Account) error {
|
|
||||||
err := driver.refreshToken(account)
|
|
||||||
if err != nil && err == base.ErrEmptyToken {
|
|
||||||
err = driver.refreshToken(account)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) refreshToken(account *model.Account) error {
|
|
||||||
u := "https://openapi.baidu.com/oauth/2.0/token"
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e TokenErrResp
|
|
||||||
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
"refresh_token": account.RefreshToken,
|
|
||||||
"client_id": account.ClientId,
|
|
||||||
"client_secret": account.ClientSecret,
|
|
||||||
}).Get(u)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Error != "" {
|
|
||||||
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
|
|
||||||
}
|
|
||||||
if resp.RefreshToken == "" {
|
|
||||||
return base.ErrEmptyToken
|
|
||||||
}
|
|
||||||
account.Status = "work"
|
|
||||||
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Request(fullurl string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetQueryParam("access_token", account.AccessToken)
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var res *resty.Response
|
|
||||||
var err error
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(fullurl)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(fullurl)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(fullurl)
|
|
||||||
case base.Delete:
|
|
||||||
res, err = req.Delete(fullurl)
|
|
||||||
case base.Put:
|
|
||||||
res, err = req.Put(fullurl)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//log.Debug(res.String())
|
|
||||||
errno := jsoniter.Get(res.Body(), "errno").ToInt()
|
|
||||||
if errno != 0 {
|
|
||||||
if errno == -6 {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driver.Request(fullurl, method, headers, query, form, data, resp, account)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("errno: %d, refer to https://pan.baidu.com/union/doc/", errno)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Get(pathname string, params map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
return driver.Request("https://pan.baidu.com/rest/2.0"+pathname, base.Get, nil, params, nil, nil, resp, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Post(pathname string, params map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
return driver.Request("https://pan.baidu.com/rest/2.0"+pathname, base.Post, nil, params, nil, data, resp, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) manage(opera string, filelist interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
params := map[string]string{
|
|
||||||
"method": "filemanager",
|
|
||||||
"opera": opera,
|
|
||||||
}
|
|
||||||
marshal, err := utils.Json.Marshal(filelist)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := fmt.Sprintf("async=0&filelist=%s&ondup=newcopy", string(marshal))
|
|
||||||
return driver.Post("/xpan/file", params, data, nil, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) GetFiles(dir string, account *model.Account) ([]model.File, error) {
|
|
||||||
dir = utils.Join(account.RootFolder, dir)
|
|
||||||
start := 0
|
|
||||||
limit := 200
|
|
||||||
params := map[string]string{
|
|
||||||
"method": "list",
|
|
||||||
"dir": dir,
|
|
||||||
"web": "web",
|
|
||||||
}
|
|
||||||
if account.OrderBy != "" {
|
|
||||||
params["order"] = account.OrderBy
|
|
||||||
if account.OrderDirection == "desc" {
|
|
||||||
params["desc"] = "1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res := make([]model.File, 0)
|
|
||||||
for {
|
|
||||||
params["start"] = strconv.Itoa(start)
|
|
||||||
params["limit"] = strconv.Itoa(limit)
|
|
||||||
start += limit
|
|
||||||
var resp ListResp
|
|
||||||
_, err := driver.Get("/xpan/file", params, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(resp.List) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, f := range resp.List {
|
|
||||||
file := model.File{
|
|
||||||
Id: strconv.FormatInt(f.FsId, 10),
|
|
||||||
Name: f.ServerFilename,
|
|
||||||
Size: f.Size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: getTime(f.ServerMtime),
|
|
||||||
Thumbnail: f.Thumbs.Url3,
|
|
||||||
}
|
|
||||||
if f.Isdir == 1 {
|
|
||||||
file.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
file.Type = utils.GetFileType(path.Ext(f.ServerFilename))
|
|
||||||
}
|
|
||||||
res = append(res, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) create(path string, size uint64, isdir int, uploadid, block_list string, account *model.Account) ([]byte, error) {
|
|
||||||
params := map[string]string{
|
|
||||||
"method": "create",
|
|
||||||
}
|
|
||||||
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", path, size, isdir)
|
|
||||||
if uploadid != "" {
|
|
||||||
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
|
|
||||||
}
|
|
||||||
return driver.Post("/xpan/file", params, data, nil, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Baidu{})
|
|
||||||
}
|
|
@ -1,399 +0,0 @@
|
|||||||
package baidu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Baidu struct{}
|
|
||||||
|
|
||||||
func (driver Baidu) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Baidu.Disk",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "refresh_token",
|
|
||||||
Label: "refresh token",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Default: "/",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Default: "name",
|
|
||||||
Values: "name,time,size",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "asc,desc",
|
|
||||||
Default: "asc",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "download api",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Values: "official,crack",
|
|
||||||
Default: "official",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_id",
|
|
||||||
Label: "client id",
|
|
||||||
Default: "iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_secret",
|
|
||||||
Label: "client secret",
|
|
||||||
Default: "jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return driver.RefreshToken(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ := cache.([]model.File)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
files, err := driver.GetFiles(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
if account.InternalType == "crack" {
|
|
||||||
return driver.LinkCrack(args, account)
|
|
||||||
}
|
|
||||||
return driver.LinkOfficial(args, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) LinkOfficial(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if file.IsDir() {
|
|
||||||
return nil, base.ErrNotFile
|
|
||||||
}
|
|
||||||
var resp DownloadResp
|
|
||||||
params := map[string]string{
|
|
||||||
"method": "filemetas",
|
|
||||||
"fsids": fmt.Sprintf("[%s]", file.Id),
|
|
||||||
"dlink": "1",
|
|
||||||
}
|
|
||||||
_, err = driver.Get("/xpan/multimedia", params, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, account.AccessToken)
|
|
||||||
res, err := base.NoRedirectClient.R().SetHeader("User-Agent", "pan.baidu.com").Head(u)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//if res.StatusCode() == 302 {
|
|
||||||
u = res.Header().Get("location")
|
|
||||||
//}
|
|
||||||
return &base.Link{
|
|
||||||
Url: u,
|
|
||||||
Headers: []base.Header{
|
|
||||||
{Name: "User-Agent", Value: "pan.baidu.com"},
|
|
||||||
}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) LinkCrack(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if file.IsDir() {
|
|
||||||
return nil, base.ErrNotFile
|
|
||||||
}
|
|
||||||
var resp DownloadResp2
|
|
||||||
param := map[string]string{
|
|
||||||
"target": fmt.Sprintf("[\"%s\"]", utils.Join(account.RootFolder, args.Path)),
|
|
||||||
"dlink": "1",
|
|
||||||
"web": "5",
|
|
||||||
"origin": "dlna",
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://pan.baidu.com/api/filemetas", base.Get, nil, param, nil, nil, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Url: resp.Info[0].Dlink,
|
|
||||||
Headers: []base.Header{
|
|
||||||
{Name: "User-Agent", Value: "pan.baidu.com"},
|
|
||||||
}}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Baidu) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
// r.Header.Set("User-Agent", "pan.baidu.com")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Baidu) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) MakeDir(path string, account *model.Account) error {
|
|
||||||
_, err := driver.create(utils.Join(account.RootFolder, path), 0, 1, "", "", account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Move(src string, dst string, account *model.Account) error {
|
|
||||||
path := utils.Join(account.RootFolder, src)
|
|
||||||
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
|
|
||||||
data := []base.Json{
|
|
||||||
{
|
|
||||||
"path": path,
|
|
||||||
"dest": dest,
|
|
||||||
"newname": newname,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err := driver.manage("move", data, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
path := utils.Join(account.RootFolder, src)
|
|
||||||
newname := utils.Base(dst)
|
|
||||||
data := []base.Json{
|
|
||||||
{
|
|
||||||
"path": path,
|
|
||||||
"newname": newname,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err := driver.manage("rename", data, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
path := utils.Join(account.RootFolder, src)
|
|
||||||
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
|
|
||||||
data := []base.Json{
|
|
||||||
{
|
|
||||||
"path": path,
|
|
||||||
"dest": dest,
|
|
||||||
"newname": newname,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
_, err := driver.manage("copy", data, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Delete(path string, account *model.Account) error {
|
|
||||||
path = utils.Join(account.RootFolder, path)
|
|
||||||
data := []string{path}
|
|
||||||
_, err := driver.manage("delete", data, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
_, err = io.Copy(tempFile, file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var Default uint64 = 4 * 1024 * 1024
|
|
||||||
defaultByteData := make([]byte, Default)
|
|
||||||
count := int(math.Ceil(float64(file.GetSize()) / float64(Default)))
|
|
||||||
var SliceSize uint64 = 256 * 1024
|
|
||||||
// cal md5
|
|
||||||
h1 := md5.New()
|
|
||||||
h2 := md5.New()
|
|
||||||
block_list := make([]string, 0)
|
|
||||||
content_md5 := ""
|
|
||||||
slice_md5 := ""
|
|
||||||
left := file.GetSize()
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
byteSize := Default
|
|
||||||
var byteData []byte
|
|
||||||
if left < Default {
|
|
||||||
byteSize = left
|
|
||||||
byteData = make([]byte, byteSize)
|
|
||||||
} else {
|
|
||||||
byteData = defaultByteData
|
|
||||||
}
|
|
||||||
left -= byteSize
|
|
||||||
_, err = io.ReadFull(tempFile, byteData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h1.Write(byteData)
|
|
||||||
h2.Write(byteData)
|
|
||||||
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
|
|
||||||
h2.Reset()
|
|
||||||
}
|
|
||||||
content_md5 = hex.EncodeToString(h1.Sum(nil))
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if file.GetSize() <= SliceSize {
|
|
||||||
slice_md5 = content_md5
|
|
||||||
} else {
|
|
||||||
sliceData := make([]byte, SliceSize)
|
|
||||||
_, err = io.ReadFull(tempFile, sliceData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h2.Write(sliceData)
|
|
||||||
slice_md5 = hex.EncodeToString(h2.Sum(nil))
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path := encodeURIComponent(utils.Join(account.RootFolder, file.ParentPath, file.Name))
|
|
||||||
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
|
|
||||||
data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s&content-md5=%s&slice-md5=%s",
|
|
||||||
path, file.GetSize(),
|
|
||||||
block_list_str,
|
|
||||||
content_md5, slice_md5)
|
|
||||||
params := map[string]string{
|
|
||||||
"method": "precreate",
|
|
||||||
}
|
|
||||||
var precreateResp PrecreateResp
|
|
||||||
_, err = driver.Post("/xpan/file", params, data, &precreateResp, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugf("%+v", precreateResp)
|
|
||||||
if precreateResp.ReturnType == 2 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
params = map[string]string{
|
|
||||||
"method": "upload",
|
|
||||||
"access_token": account.AccessToken,
|
|
||||||
"type": "tmpfile",
|
|
||||||
"path": path,
|
|
||||||
"uploadid": precreateResp.Uploadid,
|
|
||||||
}
|
|
||||||
left = file.GetSize()
|
|
||||||
for _, partseq := range precreateResp.BlockList {
|
|
||||||
byteSize := Default
|
|
||||||
var byteData []byte
|
|
||||||
if left < Default {
|
|
||||||
byteSize = left
|
|
||||||
byteData = make([]byte, byteSize)
|
|
||||||
} else {
|
|
||||||
byteData = defaultByteData
|
|
||||||
}
|
|
||||||
left -= byteSize
|
|
||||||
_, err = io.ReadFull(tempFile, byteData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
|
|
||||||
params["partseq"] = strconv.Itoa(partseq)
|
|
||||||
res, err := base.RestyClient.R().SetQueryParams(params).SetFileReader("file", file.Name, bytes.NewReader(byteData)).Post(u)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugln(res.String())
|
|
||||||
}
|
|
||||||
_, err = driver.create(path, file.GetSize(), 0, precreateResp.Uploadid, block_list_str, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Baidu)(nil)
|
|
@ -1,143 +0,0 @@
|
|||||||
package baidu
|
|
||||||
|
|
||||||
type TokenErrResp struct {
|
|
||||||
ErrorDescription string `json:"error_description"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
//TkbindId int `json:"tkbind_id"`
|
|
||||||
//OwnerType int `json:"owner_type"`
|
|
||||||
//Category int `json:"category"`
|
|
||||||
//RealCategory string `json:"real_category"`
|
|
||||||
FsId int64 `json:"fs_id"`
|
|
||||||
ServerMtime int64 `json:"server_mtime"`
|
|
||||||
//OperId int `json:"oper_id"`
|
|
||||||
//ServerCtime int `json:"server_ctime"`
|
|
||||||
Thumbs struct {
|
|
||||||
//Icon string `json:"icon"`
|
|
||||||
Url3 string `json:"url3"`
|
|
||||||
//Url2 string `json:"url2"`
|
|
||||||
//Url1 string `json:"url1"`
|
|
||||||
} `json:"thumbs"`
|
|
||||||
//Wpfile int `json:"wpfile"`
|
|
||||||
//LocalMtime int `json:"local_mtime"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
//ExtentTinyint7 int `json:"extent_tinyint7"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
//Share int `json:"share"`
|
|
||||||
//ServerAtime int `json:"server_atime"`
|
|
||||||
//Pl int `json:"pl"`
|
|
||||||
//LocalCtime int `json:"local_ctime"`
|
|
||||||
ServerFilename string `json:"server_filename"`
|
|
||||||
//Md5 string `json:"md5"`
|
|
||||||
//OwnerId int `json:"owner_id"`
|
|
||||||
//Unlist int `json:"unlist"`
|
|
||||||
Isdir int `json:"isdir"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListResp struct {
|
|
||||||
Errno int `json:"errno"`
|
|
||||||
GuidInfo string `json:"guid_info"`
|
|
||||||
List []File `json:"list"`
|
|
||||||
RequestId int64 `json:"request_id"`
|
|
||||||
Guid int `json:"guid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloadResp struct {
|
|
||||||
Errmsg string `json:"errmsg"`
|
|
||||||
Errno int `json:"errno"`
|
|
||||||
List []struct {
|
|
||||||
//Category int `json:"category"`
|
|
||||||
//DateTaken int `json:"date_taken,omitempty"`
|
|
||||||
Dlink string `json:"dlink"`
|
|
||||||
//Filename string `json:"filename"`
|
|
||||||
//FsId int64 `json:"fs_id"`
|
|
||||||
//Height int `json:"height,omitempty"`
|
|
||||||
//Isdir int `json:"isdir"`
|
|
||||||
//Md5 string `json:"md5"`
|
|
||||||
//OperId int `json:"oper_id"`
|
|
||||||
//Path string `json:"path"`
|
|
||||||
//ServerCtime int `json:"server_ctime"`
|
|
||||||
//ServerMtime int `json:"server_mtime"`
|
|
||||||
//Size int `json:"size"`
|
|
||||||
//Thumbs struct {
|
|
||||||
// Icon string `json:"icon,omitempty"`
|
|
||||||
// Url1 string `json:"url1,omitempty"`
|
|
||||||
// Url2 string `json:"url2,omitempty"`
|
|
||||||
// Url3 string `json:"url3,omitempty"`
|
|
||||||
//} `json:"thumbs"`
|
|
||||||
//Width int `json:"width,omitempty"`
|
|
||||||
} `json:"list"`
|
|
||||||
//Names struct {
|
|
||||||
//} `json:"names"`
|
|
||||||
RequestId string `json:"request_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownloadResp2 struct {
|
|
||||||
Errno int `json:"errno"`
|
|
||||||
Info []struct {
|
|
||||||
//ExtentTinyint4 int `json:"extent_tinyint4"`
|
|
||||||
//ExtentTinyint1 int `json:"extent_tinyint1"`
|
|
||||||
//Bitmap string `json:"bitmap"`
|
|
||||||
//Category int `json:"category"`
|
|
||||||
//Isdir int `json:"isdir"`
|
|
||||||
//Videotag int `json:"videotag"`
|
|
||||||
Dlink string `json:"dlink"`
|
|
||||||
//OperID int64 `json:"oper_id"`
|
|
||||||
//PathMd5 int `json:"path_md5"`
|
|
||||||
//Wpfile int `json:"wpfile"`
|
|
||||||
//LocalMtime int `json:"local_mtime"`
|
|
||||||
/*Thumbs struct {
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
URL3 string `json:"url3"`
|
|
||||||
URL2 string `json:"url2"`
|
|
||||||
URL1 string `json:"url1"`
|
|
||||||
} `json:"thumbs"`*/
|
|
||||||
//PlaySource int `json:"play_source"`
|
|
||||||
//Share int `json:"share"`
|
|
||||||
//FileKey string `json:"file_key"`
|
|
||||||
//Errno int `json:"errno"`
|
|
||||||
//LocalCtime int `json:"local_ctime"`
|
|
||||||
//Rotate int `json:"rotate"`
|
|
||||||
//Metadata time.Time `json:"metadata"`
|
|
||||||
//Height int `json:"height"`
|
|
||||||
//SampleRate int `json:"sample_rate"`
|
|
||||||
//Width int `json:"width"`
|
|
||||||
//OwnerType int `json:"owner_type"`
|
|
||||||
//Privacy int `json:"privacy"`
|
|
||||||
//ExtentInt3 int64 `json:"extent_int3"`
|
|
||||||
//RealCategory string `json:"real_category"`
|
|
||||||
//SrcLocation string `json:"src_location"`
|
|
||||||
//MetaInfo string `json:"meta_info"`
|
|
||||||
//ID string `json:"id"`
|
|
||||||
//Duration int `json:"duration"`
|
|
||||||
//FileSize string `json:"file_size"`
|
|
||||||
//Channels int `json:"channels"`
|
|
||||||
//UseSegment int `json:"use_segment"`
|
|
||||||
//ServerCtime int `json:"server_ctime"`
|
|
||||||
//Resolution string `json:"resolution"`
|
|
||||||
//OwnerID int `json:"owner_id"`
|
|
||||||
//ExtraInfo string `json:"extra_info"`
|
|
||||||
//Size int `json:"size"`
|
|
||||||
//FsID int64 `json:"fs_id"`
|
|
||||||
//ExtentTinyint3 int `json:"extent_tinyint3"`
|
|
||||||
//Md5 string `json:"md5"`
|
|
||||||
//Path string `json:"path"`
|
|
||||||
//FrameRate int `json:"frame_rate"`
|
|
||||||
//ExtentTinyint2 int `json:"extent_tinyint2"`
|
|
||||||
//ServerFilename string `json:"server_filename"`
|
|
||||||
//ServerMtime int `json:"server_mtime"`
|
|
||||||
//TkbindID int `json:"tkbind_id"`
|
|
||||||
} `json:"info"`
|
|
||||||
RequestID int64 `json:"request_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PrecreateResp struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
Uploadid string `json:"uploadid"`
|
|
||||||
ReturnType int `json:"return_type"`
|
|
||||||
BlockList []int `json:"block_list"`
|
|
||||||
Errno int `json:"errno"`
|
|
||||||
RequestId int64 `json:"request_id"`
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package baidu
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getTime(t int64) *time.Time {
|
|
||||||
tm := time.Unix(t, 0)
|
|
||||||
return &tm
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeURIComponent(str string) string {
|
|
||||||
r := url.QueryEscape(str)
|
|
||||||
r = strings.ReplaceAll(r, "+", "%20")
|
|
||||||
return r
|
|
||||||
}
|
|
@ -1,259 +0,0 @@
|
|||||||
package baiduphoto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (driver Baidu) RefreshToken(account *model.Account) error {
|
|
||||||
err := driver.refreshToken(account)
|
|
||||||
if err != nil && err == base.ErrEmptyToken {
|
|
||||||
err = driver.refreshToken(account)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) refreshToken(account *model.Account) error {
|
|
||||||
u := "https://openapi.baidu.com/oauth/2.0/token"
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e TokenErrResp
|
|
||||||
_, err := base.RestyClient.R().
|
|
||||||
SetResult(&resp).
|
|
||||||
SetError(&e).
|
|
||||||
SetQueryParams(map[string]string{
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
"refresh_token": account.RefreshToken,
|
|
||||||
"client_id": account.ClientId,
|
|
||||||
"client_secret": account.ClientSecret,
|
|
||||||
}).Get(u)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.ErrorMsg != "" {
|
|
||||||
return &e
|
|
||||||
}
|
|
||||||
if resp.RefreshToken == "" {
|
|
||||||
return base.ErrEmptyToken
|
|
||||||
}
|
|
||||||
account.Status = "work"
|
|
||||||
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Request(method string, url string, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetQueryParam("access_token", account.AccessToken)
|
|
||||||
if callback != nil {
|
|
||||||
callback(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := req.Execute(method, url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
|
|
||||||
var erron Erron
|
|
||||||
if err = utils.Json.Unmarshal(res.Body(), &erron); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch erron.Errno {
|
|
||||||
case 0:
|
|
||||||
return res, nil
|
|
||||||
case -6:
|
|
||||||
if err = driver.RefreshToken(account); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("errno: %d, refer to https://photo.baidu.com/union/doc", erron.Errno)
|
|
||||||
}
|
|
||||||
return driver.Request(method, url, callback, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有根文件
|
|
||||||
func (driver Baidu) GetAllFile(account *model.Account) (files []File, err error) {
|
|
||||||
var cursor string
|
|
||||||
|
|
||||||
for {
|
|
||||||
var resp FileListResp
|
|
||||||
_, err = driver.Request(http.MethodGet, FILE_API_URL_V1+"/list", func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"need_thumbnail": "1",
|
|
||||||
"need_filter_hidden": "0",
|
|
||||||
"cursor": cursor,
|
|
||||||
})
|
|
||||||
r.SetResult(&resp)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = resp.Cursor
|
|
||||||
files = append(files, resp.List...)
|
|
||||||
|
|
||||||
if !resp.HasNextPage() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取所有相册
|
|
||||||
func (driver Baidu) GetAllAlbum(account *model.Account) (albums []Album, err error) {
|
|
||||||
var cursor string
|
|
||||||
for {
|
|
||||||
var resp AlbumListResp
|
|
||||||
_, err = driver.Request(http.MethodGet, ALBUM_API_URL+"/list", func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"need_amount": "1",
|
|
||||||
"limit": "100",
|
|
||||||
"cursor": cursor,
|
|
||||||
})
|
|
||||||
r.SetResult(&resp)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if albums == nil {
|
|
||||||
albums = make([]Album, 0, resp.TotalCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = resp.Cursor
|
|
||||||
albums = append(albums, resp.List...)
|
|
||||||
|
|
||||||
if !resp.HasNextPage() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取相册中所有文件
|
|
||||||
func (driver Baidu) GetAllAlbumFile(albumID string, account *model.Account) (files []AlbumFile, err error) {
|
|
||||||
var cursor string
|
|
||||||
for {
|
|
||||||
var resp AlbumFileListResp
|
|
||||||
_, err = driver.Request(http.MethodGet, ALBUM_API_URL+"/listfile", func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"album_id": splitID(albumID)[0],
|
|
||||||
"need_amount": "1",
|
|
||||||
"limit": "1000",
|
|
||||||
"cursor": cursor,
|
|
||||||
})
|
|
||||||
r.SetResult(&resp)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if files == nil {
|
|
||||||
files = make([]AlbumFile, 0, resp.TotalCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = resp.Cursor
|
|
||||||
files = append(files, resp.List...)
|
|
||||||
|
|
||||||
if !resp.HasNextPage() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建相册
|
|
||||||
func (driver Baidu) CreateAlbum(name string, account *model.Account) error {
|
|
||||||
if !checkName(name) {
|
|
||||||
return ErrNotSupportName
|
|
||||||
}
|
|
||||||
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/create", func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"title": name,
|
|
||||||
"tid": getTid(),
|
|
||||||
"source": "0",
|
|
||||||
})
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 相册改名
|
|
||||||
func (driver Baidu) SetAlbumName(albumID string, name string, account *model.Account) error {
|
|
||||||
if !checkName(name) {
|
|
||||||
return ErrNotSupportName
|
|
||||||
}
|
|
||||||
|
|
||||||
e := splitID(albumID)
|
|
||||||
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/settitle", func(r *resty.Request) {
|
|
||||||
r.SetFormData(map[string]string{
|
|
||||||
"title": name,
|
|
||||||
"album_id": e[0],
|
|
||||||
"tid": e[1],
|
|
||||||
})
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除相册
|
|
||||||
func (driver Baidu) DeleteAlbum(albumID string, account *model.Account) error {
|
|
||||||
e := splitID(albumID)
|
|
||||||
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/delete", func(r *resty.Request) {
|
|
||||||
r.SetFormData(map[string]string{
|
|
||||||
"album_id": e[0],
|
|
||||||
"tid": e[1],
|
|
||||||
"delete_origin_image": "0", // 是否删除原图 0 不删除
|
|
||||||
})
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除相册文件
|
|
||||||
func (driver Baidu) DeleteAlbumFile(albumID string, account *model.Account, fileIDs ...string) error {
|
|
||||||
e := splitID(albumID)
|
|
||||||
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/delfile", func(r *resty.Request) {
|
|
||||||
r.SetFormData(map[string]string{
|
|
||||||
"album_id": e[0],
|
|
||||||
"tid": e[1],
|
|
||||||
"list": fsidsFormat(fileIDs...),
|
|
||||||
"del_origin": "0", // 是否删除原图 0 不删除 1 删除
|
|
||||||
})
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 增加相册文件
|
|
||||||
func (driver Baidu) AddAlbumFile(albumID string, account *model.Account, fileIDs ...string) error {
|
|
||||||
e := splitID(albumID)
|
|
||||||
_, err := driver.Request(http.MethodGet, ALBUM_API_URL+"/addfile", func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"album_id": e[0],
|
|
||||||
"tid": e[1],
|
|
||||||
"list": fsidsFormatNotUk(fileIDs...),
|
|
||||||
})
|
|
||||||
}, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存相册文件为根文件
|
|
||||||
func (driver Baidu) CopyAlbumFile(albumID string, account *model.Account, fileID string) (*CopyFile, error) {
|
|
||||||
var resp CopyFileResp
|
|
||||||
e := splitID(fileID)
|
|
||||||
_, err := driver.Request(http.MethodPost, ALBUM_API_URL+"/copyfile", func(r *resty.Request) {
|
|
||||||
r.SetFormData(map[string]string{
|
|
||||||
"album_id": splitID(albumID)[0],
|
|
||||||
"tid": e[2],
|
|
||||||
"uk": e[1],
|
|
||||||
"list": fsidsFormatNotUk(fileID),
|
|
||||||
})
|
|
||||||
r.SetResult(&resp)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &resp.List[0], err
|
|
||||||
}
|
|
@ -1,502 +0,0 @@
|
|||||||
package baiduphoto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Baidu struct{}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(new(Baidu))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Baidu.Photo",
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "refresh_token",
|
|
||||||
Label: "refresh token",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "album_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "download api",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Values: "file,album",
|
|
||||||
Default: "album",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_id",
|
|
||||||
Label: "client id",
|
|
||||||
Default: "iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_secret",
|
|
||||||
Label: "client secret",
|
|
||||||
Default: "jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return driver.RefreshToken(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, name := utils.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsAlbum(file) {
|
|
||||||
albumFiles, err := driver.GetAllAlbumFile(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files = make([]model.File, 0, len(albumFiles))
|
|
||||||
for _, file := range albumFiles {
|
|
||||||
var thumbnail string
|
|
||||||
if len(file.Thumburl) > 0 {
|
|
||||||
thumbnail = file.Thumburl[0]
|
|
||||||
}
|
|
||||||
files = append(files, model.File{
|
|
||||||
Id: joinID(file.Fsid, file.Uk, file.Tid),
|
|
||||||
Name: file.Name(),
|
|
||||||
Size: file.Size,
|
|
||||||
Type: utils.GetFileType(utils.Ext(file.Path)),
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: getTime(file.Mtime),
|
|
||||||
Thumbnail: thumbnail,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if IsRoot(file) {
|
|
||||||
albums, err := driver.GetAllAlbum(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
files = make([]model.File, 0, len(albums))
|
|
||||||
for _, album := range albums {
|
|
||||||
files = append(files, model.File{
|
|
||||||
Id: joinID(album.AlbumID, album.Tid),
|
|
||||||
Name: album.Title,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: getTime(album.Mtime),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
if account.InternalType == "file" {
|
|
||||||
return driver.LinkFile(args, account)
|
|
||||||
}
|
|
||||||
return driver.LinkAlbum(args, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) LinkAlbum(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !IsAlbumFile(file) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
album, err := driver.File(utils.Dir(utils.ParsePath(args.Path)), account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
e := splitID(file.Id)
|
|
||||||
res, err := base.NoRedirectClient.R().
|
|
||||||
SetQueryParams(map[string]string{
|
|
||||||
"access_token": account.AccessToken,
|
|
||||||
"album_id": splitID(album.Id)[0],
|
|
||||||
"tid": e[2],
|
|
||||||
"fsid": e[0],
|
|
||||||
"uk": e[1],
|
|
||||||
}).
|
|
||||||
Head(ALBUM_API_URL + "/download")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Headers: []base.Header{
|
|
||||||
{Name: "User-Agent", Value: base.UserAgent},
|
|
||||||
},
|
|
||||||
Url: res.Header().Get("location"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) LinkFile(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !IsAlbumFile(file) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
album, err := driver.File(utils.Dir(utils.ParsePath(args.Path)), account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// 拷贝到根目录
|
|
||||||
cfile, err := driver.CopyAlbumFile(album.Id, account, file.Id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := driver.Request(http.MethodGet, FILE_API_URL_V2+"/download", func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(map[string]string{
|
|
||||||
"fsid": fmt.Sprint(cfile.Fsid),
|
|
||||||
})
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Headers: []base.Header{
|
|
||||||
{Name: "User-Agent", Value: base.UserAgent},
|
|
||||||
},
|
|
||||||
Url: utils.Json.Get(res.Body(), "dlink").ToString(),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsAlbum(srcFile) {
|
|
||||||
return driver.SetAlbumName(srcFile.Id, utils.Base(dst), account)
|
|
||||||
}
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) MakeDir(path string, account *model.Account) error {
|
|
||||||
dir, name := utils.Split(path)
|
|
||||||
parentFile, err := driver.File(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !IsRoot(parentFile) {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
return driver.CreateAlbum(name, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Move(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsAlbumFile(srcFile) {
|
|
||||||
// 移动相册文件
|
|
||||||
dstAlbum, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !IsAlbum(dstAlbum) {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
srcAlbum, err := driver.File(utils.Dir(src), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newFile, err := driver.CopyAlbumFile(srcAlbum.Id, account, srcFile.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = driver.DeleteAlbumFile(srcAlbum.Id, account, srcFile.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = driver.AddAlbumFile(dstAlbum.Id, account, joinID(newFile.Fsid))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsAlbumFile(srcFile) {
|
|
||||||
// 复制相册文件
|
|
||||||
dstAlbum, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !IsAlbum(dstAlbum) {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
srcAlbum, err := driver.File(utils.Dir(src), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newFile, err := driver.CopyAlbumFile(srcAlbum.Id, account, srcFile.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = driver.AddAlbumFile(dstAlbum.Id, account, joinID(newFile.Fsid))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Delete(path string, account *model.Account) error {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除相册
|
|
||||||
if IsAlbum(file) {
|
|
||||||
return driver.DeleteAlbum(file.Id, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成相册文件
|
|
||||||
if IsAlbumFile(file) {
|
|
||||||
// 删除相册文件
|
|
||||||
album, err := driver.File(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return driver.DeleteAlbumFile(album.Id, account, file.Id)
|
|
||||||
}
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !IsAlbum(parentFile) {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
tempFile.Close()
|
|
||||||
os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 计算需要的数据
|
|
||||||
const DEFAULT = 1 << 22
|
|
||||||
const SliceSize = 1 << 18
|
|
||||||
count := int(math.Ceil(float64(file.Size) / float64(DEFAULT)))
|
|
||||||
|
|
||||||
sliceMD5List := make([]string, 0, count)
|
|
||||||
fileMd5 := md5.New()
|
|
||||||
sliceMd5 := md5.New()
|
|
||||||
for i := 1; i <= count; i++ {
|
|
||||||
if n, err := io.CopyN(io.MultiWriter(fileMd5, sliceMd5, tempFile), file, DEFAULT); err != io.EOF && n == 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sliceMD5List = append(sliceMD5List, hex.EncodeToString(sliceMd5.Sum(nil)))
|
|
||||||
sliceMd5.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
content_md5 := hex.EncodeToString(fileMd5.Sum(nil))
|
|
||||||
slice_md5 := content_md5
|
|
||||||
if file.GetSize() > SliceSize {
|
|
||||||
sliceData := make([]byte, SliceSize)
|
|
||||||
if _, err = io.ReadFull(tempFile, sliceData); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sliceMd5.Write(sliceData)
|
|
||||||
slice_md5 = hex.EncodeToString(sliceMd5.Sum(nil))
|
|
||||||
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始执行上传
|
|
||||||
params := map[string]string{
|
|
||||||
"autoinit": "1",
|
|
||||||
"isdir": "0",
|
|
||||||
"rtype": "1",
|
|
||||||
"ctype": "11",
|
|
||||||
"path": utils.ParsePath(file.Name),
|
|
||||||
"size": fmt.Sprint(file.Size),
|
|
||||||
"slice-md5": slice_md5,
|
|
||||||
"content-md5": content_md5,
|
|
||||||
"block_list": MustString(utils.Json.MarshalToString(sliceMD5List)),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预上传
|
|
||||||
var precreateResp PrecreateResp
|
|
||||||
_, err = driver.Request(http.MethodPost, FILE_API_URL_V1+"/precreate", func(r *resty.Request) {
|
|
||||||
r.SetFormData(params)
|
|
||||||
r.SetResult(&precreateResp)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch precreateResp.ReturnType {
|
|
||||||
case 1: // 上传文件
|
|
||||||
uploadParams := map[string]string{
|
|
||||||
"method": "upload",
|
|
||||||
"path": params["path"],
|
|
||||||
"uploadid": precreateResp.UploadID,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
uploadParams["partseq"] = fmt.Sprint(i)
|
|
||||||
_, err = driver.Request(http.MethodPost, "https://c3.pcs.baidu.com/rest/2.0/pcs/superfile2", func(r *resty.Request) {
|
|
||||||
r.SetQueryParams(uploadParams)
|
|
||||||
r.SetFileReader("file", file.Name, io.LimitReader(tempFile, DEFAULT))
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 2: // 创建文件
|
|
||||||
params["uploadid"] = precreateResp.UploadID
|
|
||||||
_, err = driver.Request(http.MethodPost, FILE_API_URL_V1+"/create", func(r *resty.Request) {
|
|
||||||
r.SetFormData(params)
|
|
||||||
r.SetResult(&precreateResp)
|
|
||||||
}, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case 3: // 增加到相册
|
|
||||||
err = driver.AddAlbumFile(parentFile.Id, account, joinID(precreateResp.Data.FsID))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Baidu)(nil)
|
|
@ -1,126 +0,0 @@
|
|||||||
package baiduphoto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TokenErrResp struct {
|
|
||||||
ErrorDescription string `json:"error_description"`
|
|
||||||
ErrorMsg string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *TokenErrResp) Error() string {
|
|
||||||
return fmt.Sprint(e.ErrorMsg, " : ", e.ErrorDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Erron struct {
|
|
||||||
Errno int `json:"errno"`
|
|
||||||
RequestID int `json:"request_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Page struct {
|
|
||||||
HasMore int `json:"has_more"`
|
|
||||||
Cursor string `json:"cursor"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Page) HasNextPage() bool {
|
|
||||||
return p.HasMore == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
FileListResp struct {
|
|
||||||
Page
|
|
||||||
List []File `json:"list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
File struct {
|
|
||||||
Fsid int64 `json:"fsid"` // 文件ID
|
|
||||||
Path string `json:"path"` // 文件路径
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Ctime int64 `json:"ctime"` // 创建时间 s
|
|
||||||
Mtime int64 `json:"mtime"` // 修改时间 s
|
|
||||||
Thumburl []string `json:"thumburl"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (f File) Name() string {
|
|
||||||
return utils.Base(f.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*相册部分*/
|
|
||||||
type (
|
|
||||||
AlbumListResp struct {
|
|
||||||
Page
|
|
||||||
List []Album `json:"list"`
|
|
||||||
Reset int64 `json:"reset"`
|
|
||||||
TotalCount int64 `json:"total_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Album struct {
|
|
||||||
AlbumID string `json:"album_id"`
|
|
||||||
Tid int64 `json:"tid"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
JoinTime int64 `json:"join_time"`
|
|
||||||
CreateTime int64 `json:"create_time"`
|
|
||||||
Mtime int64 `json:"mtime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
AlbumFileListResp struct {
|
|
||||||
Page
|
|
||||||
List []AlbumFile `json:"list"`
|
|
||||||
Reset int64 `json:"reset"`
|
|
||||||
TotalCount int64 `json:"total_count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
AlbumFile struct {
|
|
||||||
File
|
|
||||||
Tid int64 `json:"tid"`
|
|
||||||
Uk int64 `json:"uk"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
CopyFileResp struct {
|
|
||||||
List []CopyFile `json:"list"`
|
|
||||||
}
|
|
||||||
CopyFile struct {
|
|
||||||
FromFsid int64 `json:"from_fsid"` // 源ID
|
|
||||||
Fsid int64 `json:"fsid"` // 目标ID
|
|
||||||
Path string `json:"path"`
|
|
||||||
ShootTime int `json:"shoot_time"`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/*上传部分*/
|
|
||||||
type (
|
|
||||||
UploadFile struct {
|
|
||||||
FsID int64 `json:"fs_id"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Md5 string `json:"md5"`
|
|
||||||
ServerFilename string `json:"server_filename"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Ctime int `json:"ctime"`
|
|
||||||
Mtime int `json:"mtime"`
|
|
||||||
Isdir int `json:"isdir"`
|
|
||||||
Category int `json:"category"`
|
|
||||||
ServerMd5 string `json:"server_md5"`
|
|
||||||
ShootTime int `json:"shoot_time"`
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateFileResp struct {
|
|
||||||
Data UploadFile `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
PrecreateResp struct {
|
|
||||||
ReturnType int `json:"return_type"` //存在返回2 不存在返回1 已经保存3
|
|
||||||
//存在返回
|
|
||||||
CreateFileResp
|
|
||||||
|
|
||||||
//不存在返回
|
|
||||||
Path string `json:"path"`
|
|
||||||
UploadID string `json:"uploadid"`
|
|
||||||
Blocklist []int64 `json:"block_list"`
|
|
||||||
}
|
|
||||||
)
|
|
@ -1,84 +0,0 @@
|
|||||||
package baiduphoto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
API_URL = "https://photo.baidu.com/youai"
|
|
||||||
ALBUM_API_URL = API_URL + "/album/v1"
|
|
||||||
FILE_API_URL_V1 = API_URL + "/file/v1"
|
|
||||||
FILE_API_URL_V2 = API_URL + "/file/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNotSupportName = errors.New("only chinese and english, numbers and underscores are supported, and the length is no more than 20")
|
|
||||||
)
|
|
||||||
|
|
||||||
//Tid生成
|
|
||||||
func getTid() string {
|
|
||||||
return fmt.Sprintf("3%d%.0f", time.Now().Unix(), math.Floor(9000000*rand.Float64()+1000000))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查名称
|
|
||||||
func checkName(name string) bool {
|
|
||||||
return len(name) <= 20 && regexp.MustCompile("[\u4e00-\u9fa5A-Za-z0-9_]").MatchString(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTime(t int64) *time.Time {
|
|
||||||
tm := time.Unix(t, 0)
|
|
||||||
return &tm
|
|
||||||
}
|
|
||||||
|
|
||||||
func fsidsFormat(ids ...string) string {
|
|
||||||
var buf []string
|
|
||||||
for _, id := range ids {
|
|
||||||
e := strings.Split(id, "|")
|
|
||||||
buf = append(buf, fmt.Sprintf("{\"fsid\":%s,\"uk\":%s}", e[0], e[1]))
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("[%s]", strings.Join(buf, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func fsidsFormatNotUk(ids ...string) string {
|
|
||||||
var buf []string
|
|
||||||
for _, id := range ids {
|
|
||||||
buf = append(buf, fmt.Sprintf("{\"fsid\":%s}", strings.Split(id, "|")[0]))
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("[%s]", strings.Join(buf, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitID(id string) []string {
|
|
||||||
return strings.SplitN(id, "|", 3)[:3]
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinID(ids ...interface{}) string {
|
|
||||||
idsStr := make([]string, 0, len(ids))
|
|
||||||
for _, id := range ids {
|
|
||||||
idsStr = append(idsStr, fmt.Sprint(id))
|
|
||||||
}
|
|
||||||
return strings.Join(idsStr, "|")
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsAlbum(file *model.File) bool {
|
|
||||||
return file.Id != "" && file.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsAlbumFile(file *model.File) bool {
|
|
||||||
return file.Id != "" && !file.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsRoot(file *model.File) bool {
|
|
||||||
return file.Id == "" && file.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustString(str string, err error) string {
|
|
||||||
return str
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package base
|
|
||||||
|
|
||||||
import "github.com/Xhofe/alist/model"
|
|
||||||
|
|
||||||
type Base struct{}
|
|
||||||
|
|
||||||
func (b Base) Config() DriverConfig {
|
|
||||||
return DriverConfig{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Items() []Item {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Save(account *model.Account, old *model.Account) error {
|
|
||||||
return ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
return nil, ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
return nil, ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Link(args Args, account *model.Account) (*Link, error) {
|
|
||||||
return nil, ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
return nil, nil, ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) MakeDir(path string, account *model.Account) error {
|
|
||||||
return ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Move(src string, dst string, account *model.Account) error {
|
|
||||||
return ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Delete(path string, account *model.Account) error {
|
|
||||||
return ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Base) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
return ErrNotImplement
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func KeyCache(path string, account *model.Account) string {
|
|
||||||
//path = utils.ParsePath(path)
|
|
||||||
key := utils.ParsePath(utils.Join(account.Name, path))
|
|
||||||
log.Debugln("cache key: ", key)
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveSearchFiles[T model.ISearchFile](key string, obj []T) {
|
|
||||||
if strings.Contains(key, ".balance") {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err := model.DeleteSearchFilesByPath(key)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("failed create search files", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
files := make([]model.SearchFile, len(obj))
|
|
||||||
for i := 0; i < len(obj); i++ {
|
|
||||||
files[i] = model.SearchFile{
|
|
||||||
Path: key,
|
|
||||||
Name: obj[i].GetName(),
|
|
||||||
Size: obj[i].GetSize(),
|
|
||||||
Type: obj[i].GetType(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = model.CreateSearchFiles(files)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("failed create search files", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetCache[T model.ISearchFile](path string, obj []T, account *model.Account) error {
|
|
||||||
key := KeyCache(path, account)
|
|
||||||
if conf.GetBool("enable search") {
|
|
||||||
go SaveSearchFiles(key, obj)
|
|
||||||
}
|
|
||||||
return conf.Cache.Set(conf.Ctx, key, obj, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCache(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return conf.Cache.Get(conf.Ctx, KeyCache(path, account))
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteCache(path string, account *model.Account) error {
|
|
||||||
err := conf.Cache.Delete(conf.Ctx, KeyCache(path, account))
|
|
||||||
log.Debugf("delete cache %s: %+v", path, err)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,181 +0,0 @@
|
|||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DriverConfig struct {
|
|
||||||
Name string
|
|
||||||
OnlyProxy bool // 必须使用代理(本机或者其他机器)
|
|
||||||
OnlyLocal bool // 必须本机返回的
|
|
||||||
ApiProxy bool // 使用API中转的
|
|
||||||
NoNeedSetLink bool // 不需要设置链接的
|
|
||||||
NoCors bool // 不可以跨域
|
|
||||||
LocalSort bool // 本地排序
|
|
||||||
}
|
|
||||||
|
|
||||||
type Args struct {
|
|
||||||
Path string
|
|
||||||
IP string
|
|
||||||
Header http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
type Driver interface {
|
|
||||||
// Config 配置
|
|
||||||
Config() DriverConfig
|
|
||||||
// Items 账号所需参数
|
|
||||||
Items() []Item
|
|
||||||
// Save 保存时处理
|
|
||||||
Save(account *model.Account, old *model.Account) error
|
|
||||||
// File 取文件
|
|
||||||
File(path string, account *model.Account) (*model.File, error)
|
|
||||||
// Files 取文件夹
|
|
||||||
Files(path string, account *model.Account) ([]model.File, error)
|
|
||||||
// Link 取链接
|
|
||||||
Link(args Args, account *model.Account) (*Link, error)
|
|
||||||
// Path 取路径(文件或文件夹)
|
|
||||||
Path(path string, account *model.Account) (*model.File, []model.File, error)
|
|
||||||
// Deprecated Proxy 代理处理
|
|
||||||
//Proxy(r *http.Request, account *model.Account)
|
|
||||||
// Preview 预览
|
|
||||||
Preview(path string, account *model.Account) (interface{}, error)
|
|
||||||
// MakeDir 创建文件夹
|
|
||||||
MakeDir(path string, account *model.Account) error
|
|
||||||
// Move 移动/改名
|
|
||||||
Move(src string, dst string, account *model.Account) error
|
|
||||||
// Rename 改名
|
|
||||||
Rename(src string, dst string, account *model.Account) error
|
|
||||||
// Copy 拷贝
|
|
||||||
Copy(src string, dst string, account *model.Account) error
|
|
||||||
// Delete 删除
|
|
||||||
Delete(path string, account *model.Account) error
|
|
||||||
// Upload 上传
|
|
||||||
Upload(file *model.FileStream, account *model.Account) error
|
|
||||||
// TODO
|
|
||||||
//Search(path string, keyword string, account *model.Account) ([]*model.File, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Item struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Default string `json:"default"`
|
|
||||||
Values string `json:"values"`
|
|
||||||
Required bool `json:"required"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var driversMap = map[string]Driver{}
|
|
||||||
|
|
||||||
func RegisterDriver(driver Driver) {
|
|
||||||
log.Infof("register driver: [%s]", driver.Config().Name)
|
|
||||||
driversMap[driver.Config().Name] = driver
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDriver(name string) (driver Driver, ok bool) {
|
|
||||||
driver, ok = driversMap[name]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDriversMap() map[string]Driver {
|
|
||||||
return driversMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetDrivers() map[string][]Item {
|
|
||||||
res := make(map[string][]Item)
|
|
||||||
for k, v := range driversMap {
|
|
||||||
webdavDirect := Item{
|
|
||||||
Name: "webdav_direct",
|
|
||||||
Label: "webdav direct",
|
|
||||||
Type: TypeBool,
|
|
||||||
Required: true,
|
|
||||||
Description: "Transfer the WebDAV of this account through the native",
|
|
||||||
}
|
|
||||||
if v.Config().OnlyProxy {
|
|
||||||
res[k] = append([]Item{
|
|
||||||
webdavDirect,
|
|
||||||
}, v.Items()...)
|
|
||||||
} else {
|
|
||||||
res[k] = append([]Item{
|
|
||||||
{
|
|
||||||
Name: "proxy",
|
|
||||||
Label: "proxy",
|
|
||||||
Type: TypeBool,
|
|
||||||
Required: true,
|
|
||||||
Description: "web proxy",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "webdav_proxy",
|
|
||||||
Label: "webdav proxy",
|
|
||||||
Type: TypeBool,
|
|
||||||
Required: true,
|
|
||||||
Description: "Transfer the WebDAV of this account through the server",
|
|
||||||
},
|
|
||||||
webdavDirect,
|
|
||||||
}, v.Items()...)
|
|
||||||
}
|
|
||||||
res[k] = append([]Item{
|
|
||||||
{
|
|
||||||
Name: "down_proxy_url",
|
|
||||||
Label: "down_proxy_url",
|
|
||||||
Type: TypeText,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "extract_folder",
|
|
||||||
Label: "extract_folder",
|
|
||||||
Values: "front,back",
|
|
||||||
Type: TypeSelect,
|
|
||||||
},
|
|
||||||
}, res[k]...)
|
|
||||||
if v.Config().ApiProxy {
|
|
||||||
res[k] = append([]Item{
|
|
||||||
{
|
|
||||||
Name: "api_proxy_url",
|
|
||||||
Label: "api_proxy_url",
|
|
||||||
Type: TypeString,
|
|
||||||
},
|
|
||||||
}, res[k]...)
|
|
||||||
}
|
|
||||||
if v.Config().LocalSort {
|
|
||||||
res[k] = append(res[k], []Item{
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: TypeSelect,
|
|
||||||
Values: "name,size,updated_at",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: TypeSelect,
|
|
||||||
Values: "ASC,DESC",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
var NoRedirectClient *resty.Client
|
|
||||||
var RestyClient = resty.New()
|
|
||||||
var HttpClient = &http.Client{}
|
|
||||||
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
|
||||||
var DefaultTimeout = time.Second * 20
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
NoRedirectClient = resty.New().SetRedirectPolicy(
|
|
||||||
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
|
||||||
return http.ErrUseLastResponse
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
NoRedirectClient.SetHeader("user-agent", UserAgent)
|
|
||||||
RestyClient.SetHeader("user-agent", UserAgent)
|
|
||||||
RestyClient.SetRetryCount(3)
|
|
||||||
RestyClient.SetTimeout(DefaultTimeout)
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
package base
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrPathNotFound = errors.New("path not found")
|
|
||||||
ErrNotFile = errors.New("not file")
|
|
||||||
ErrNotImplement = errors.New("not implement")
|
|
||||||
ErrNotSupport = errors.New("not support")
|
|
||||||
ErrNotFolder = errors.New("not a folder")
|
|
||||||
ErrEmptyFile = errors.New("empty file")
|
|
||||||
ErrRelativePath = errors.New("access using relative path is not allowed")
|
|
||||||
ErrEmptyToken = errors.New("empty token")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TypeString = "string"
|
|
||||||
TypeSelect = "select"
|
|
||||||
TypeBool = "bool"
|
|
||||||
TypeNumber = "number"
|
|
||||||
TypeText = "text"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Get = iota
|
|
||||||
Post
|
|
||||||
Put
|
|
||||||
Delete
|
|
||||||
Patch
|
|
||||||
)
|
|
||||||
|
|
||||||
type Json map[string]interface{}
|
|
||||||
|
|
||||||
type TokenResp struct {
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
RefreshToken string `json:"refresh_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Header struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Link struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
Headers []Header `json:"headers"`
|
|
||||||
Data io.ReadCloser
|
|
||||||
FilePath string `json:"path"` // for native
|
|
||||||
Status int
|
|
||||||
Header http.Header
|
|
||||||
}
|
|
@ -1,262 +0,0 @@
|
|||||||
package ftp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/jlaffaye/ftp"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FTP struct{}
|
|
||||||
|
|
||||||
func (driver FTP) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "FTP",
|
|
||||||
OnlyProxy: true,
|
|
||||||
OnlyLocal: true,
|
|
||||||
NoNeedSetLink: true,
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "site_url",
|
|
||||||
Label: "ftp host url",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "username",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if old != nil {
|
|
||||||
conn, ok := connMap[old.Name]
|
|
||||||
if ok {
|
|
||||||
err := conn.Quit()
|
|
||||||
log.Error("ftp:", err)
|
|
||||||
delete(connMap, old.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if account.RootFolder == "" {
|
|
||||||
account.RootFolder = "/"
|
|
||||||
}
|
|
||||||
_, err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
log.Debugf("file: %s", path)
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
log.Debugf("files: %s", path)
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ := cache.([]model.File)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
realPath := utils.Join(account.RootFolder, path)
|
|
||||||
conn, err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//defer func() { _ = conn.Quit() }()
|
|
||||||
entries, err := conn.List(realPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res := make([]model.File, 0)
|
|
||||||
for i, _ := range entries {
|
|
||||||
entry := entries[i]
|
|
||||||
if entry.Name == "." || entry.Name == ".." {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f := model.File{
|
|
||||||
Name: entry.Name,
|
|
||||||
Size: int64(entry.Size),
|
|
||||||
UpdatedAt: &entry.Time,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
}
|
|
||||||
if entry.Type == ftp.EntryTypeFolder {
|
|
||||||
f.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
f.Type = utils.GetFileType(filepath.Ext(entry.Name))
|
|
||||||
}
|
|
||||||
res = append(res, f)
|
|
||||||
}
|
|
||||||
if len(res) > 0 {
|
|
||||||
_ = base.SetCache(path, res, account)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
path := args.Path
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
realPath := utils.Join(account.RootFolder, path)
|
|
||||||
conn, err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//defer func() { _ = conn.Quit() }()
|
|
||||||
resp, err := conn.Retr(realPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//defer func() { _ = resp.Close() }()
|
|
||||||
//data, err := ioutil.ReadAll(resp)
|
|
||||||
//if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
//}
|
|
||||||
return &base.Link{
|
|
||||||
Data: resp,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
log.Debugf("ftp path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
//file.Url, _ = driver.Link(path, account)
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver FTP) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) MakeDir(path string, account *model.Account) error {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
realPath := utils.Join(account.RootFolder, path)
|
|
||||||
conn, err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//defer func() { _ = conn.Quit() }()
|
|
||||||
err = conn.MakeDir(realPath)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Move(src string, dst string, account *model.Account) error {
|
|
||||||
realSrc := utils.Join(account.RootFolder, src)
|
|
||||||
realDst := utils.Join(account.RootFolder, dst)
|
|
||||||
conn, err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//defer func() { _ = conn.Quit() }()
|
|
||||||
err = conn.Rename(realSrc, realDst)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return driver.Move(src, dst, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Delete(path string, account *model.Account) error {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
realPath := utils.Join(account.RootFolder, path)
|
|
||||||
conn, err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//defer func() { _ = conn.Quit() }()
|
|
||||||
if file.IsDir() {
|
|
||||||
err = conn.RemoveDirRecur(realPath)
|
|
||||||
} else {
|
|
||||||
err = conn.Delete(realPath)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver FTP) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
realPath := utils.Join(account.RootFolder, file.ParentPath, file.Name)
|
|
||||||
conn, err := driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//defer func() { _ = conn.Quit() }()
|
|
||||||
err = conn.Stor(realPath, file)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*FTP)(nil)
|
|
@ -1,36 +0,0 @@
|
|||||||
package ftp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/jlaffaye/ftp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var connMap map[string]*ftp.ServerConn
|
|
||||||
|
|
||||||
func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
|
|
||||||
conn, ok := connMap[account.Name]
|
|
||||||
if ok {
|
|
||||||
_, err := conn.CurrentDir()
|
|
||||||
if err == nil {
|
|
||||||
return conn, nil
|
|
||||||
} else {
|
|
||||||
delete(connMap, account.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn, err := ftp.Connect(account.SiteUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = conn.Login(account.Username, account.Password)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
connMap[account.Name] = conn
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&FTP{})
|
|
||||||
connMap = make(map[string]*ftp.ServerConn)
|
|
||||||
}
|
|
@ -1,289 +0,0 @@
|
|||||||
package google
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GoogleDrive struct{}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "GoogleDrive",
|
|
||||||
OnlyProxy: true,
|
|
||||||
ApiProxy: true,
|
|
||||||
NoNeedSetLink: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "client_id",
|
|
||||||
Label: "client id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Default: "202264815644.apps.googleusercontent.com",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_secret",
|
|
||||||
Label: "client secret",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Default: "X4Z3ca8xfWDb1Voo-F9a7ZxJ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "refresh_token",
|
|
||||||
Label: "refresh token",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
Description: "such as: folder,name,modifiedTime",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "asc,desc",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
account.Proxy = true
|
|
||||||
err := driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if account.RootFolder == "" {
|
|
||||||
account.RootFolder = "root"
|
|
||||||
}
|
|
||||||
account.Status = "work"
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var rawFiles []File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
rawFiles, _ = cache.([]File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(rawFiles) > 0 {
|
|
||||||
_ = base.SetCache(path, rawFiles, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
for _, file := range rawFiles {
|
|
||||||
files = append(files, *driver.FormatFile(&file, account))
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if file.Type == conf.FOLDER {
|
|
||||||
return nil, base.ErrNotFile
|
|
||||||
}
|
|
||||||
url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.Id)
|
|
||||||
_, err = driver.Request(url, base.Get, nil, nil, nil, nil, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
link := base.Link{
|
|
||||||
Url: url + "&alt=media",
|
|
||||||
Headers: []base.Header{
|
|
||||||
{
|
|
||||||
Name: "Authorization",
|
|
||||||
Value: "Bearer " + account.AccessToken,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("google path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver GoogleDrive) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
// r.Header.Add("Authorization", "Bearer "+account.AccessToken)
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) MakeDir(path string, account *model.Account) error {
|
|
||||||
parentFile, err := driver.File(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"name": utils.Base(path),
|
|
||||||
"parents": []string{parentFile.Id},
|
|
||||||
"mimeType": "application/vnd.google-apps.folder",
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://www.googleapis.com/drive/v3/files", base.Post, nil, nil, nil, &data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Move(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
url := "https://www.googleapis.com/drive/v3/files/" + srcFile.Id
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
query := map[string]string{
|
|
||||||
"addParents": dstParentFile.Id,
|
|
||||||
"removeParents": "root",
|
|
||||||
}
|
|
||||||
_, err = driver.Request(url, base.Patch, nil, query, nil, nil, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
url := "https://www.googleapis.com/drive/v3/files/" + srcFile.Id
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"name": utils.Base(dst),
|
|
||||||
}
|
|
||||||
_, err = driver.Request(url, base.Patch, nil, nil, nil, &data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Delete(path string, account *model.Account) error {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
url := "https://www.googleapis.com/drive/v3/files/" + file.Id
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = driver.Request(url, base.Delete, nil, nil, nil, nil, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"name": file.Name,
|
|
||||||
"parents": []string{parentFile.Id},
|
|
||||||
}
|
|
||||||
var e Error
|
|
||||||
url := "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true"
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
res, err := base.NoRedirectClient.R().SetHeader("Authorization", "Bearer "+account.AccessToken).
|
|
||||||
SetError(&e).SetBody(data).
|
|
||||||
Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Error.Code != 0 {
|
|
||||||
if e.Error.Code == 401 {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return driver.Upload(file, account)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
|
||||||
}
|
|
||||||
putUrl := res.Header().Get("location")
|
|
||||||
byteData, _ := ioutil.ReadAll(file)
|
|
||||||
_, err = driver.Request(putUrl, base.Put, nil, nil, nil, byteData, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*GoogleDrive)(nil)
|
|
@ -1,175 +0,0 @@
|
|||||||
package google
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TokenError struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
ErrorDescription string `json:"error_description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) RefreshToken(account *model.Account) error {
|
|
||||||
url := "https://www.googleapis.com/oauth2/v4/token"
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e TokenError
|
|
||||||
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"client_id": account.ClientId,
|
|
||||||
"client_secret": account.ClientSecret,
|
|
||||||
"refresh_token": account.RefreshToken,
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
}).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if e.Error != "" {
|
|
||||||
return fmt.Errorf(e.Error)
|
|
||||||
}
|
|
||||||
account.AccessToken = resp.AccessToken
|
|
||||||
account.Status = "work"
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) FormatFile(file *File, account *model.Account) *model.File {
|
|
||||||
f := &model.File{
|
|
||||||
Id: file.Id,
|
|
||||||
Name: file.Name,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: file.ModifiedTime,
|
|
||||||
Url: "",
|
|
||||||
}
|
|
||||||
f.Size = int64(file.GetSize())
|
|
||||||
f.Type = file.GetType()
|
|
||||||
if file.ThumbnailLink != "" {
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
f.Thumbnail = fmt.Sprintf("%s/%s", account.APIProxyUrl, file.ThumbnailLink)
|
|
||||||
} else {
|
|
||||||
f.Thumbnail = file.ThumbnailLink
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
type Files struct {
|
|
||||||
NextPageToken string `json:"nextPageToken"`
|
|
||||||
Files []File `json:"files"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Error struct {
|
|
||||||
Error struct {
|
|
||||||
Errors []struct {
|
|
||||||
Domain string `json:"domain"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
LocationType string `json:"location_type"`
|
|
||||||
Location string `json:"location"`
|
|
||||||
}
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
} `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]File, error) {
|
|
||||||
pageToken := "first"
|
|
||||||
res := make([]File, 0)
|
|
||||||
for pageToken != "" {
|
|
||||||
if pageToken == "first" {
|
|
||||||
pageToken = ""
|
|
||||||
}
|
|
||||||
var resp Files
|
|
||||||
orderBy := "folder,name,modifiedTime desc"
|
|
||||||
if account.OrderBy != "" {
|
|
||||||
orderBy = account.OrderBy + " " + account.OrderDirection
|
|
||||||
}
|
|
||||||
query := map[string]string{
|
|
||||||
"orderBy": orderBy,
|
|
||||||
"fields": "files(id,name,mimeType,size,modifiedTime,thumbnailLink),nextPageToken",
|
|
||||||
"pageSize": "1000",
|
|
||||||
"q": fmt.Sprintf("'%s' in parents and trashed = false", id),
|
|
||||||
//"includeItemsFromAllDrives": "true",
|
|
||||||
//"supportsAllDrives": "true",
|
|
||||||
"pageToken": pageToken,
|
|
||||||
}
|
|
||||||
_, err := driver.Request("https://www.googleapis.com/drive/v3/files",
|
|
||||||
base.Get, nil, query, nil, nil, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pageToken = resp.NextPageToken
|
|
||||||
res = append(res, resp.Files...)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver GoogleDrive) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
rawUrl := url
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
|
||||||
req.SetQueryParam("includeItemsFromAllDrives", "true")
|
|
||||||
req.SetQueryParam("supportsAllDrives", "true")
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var res *resty.Response
|
|
||||||
var err error
|
|
||||||
var e Error
|
|
||||||
req.SetError(&e)
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(url)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(url)
|
|
||||||
case base.Delete:
|
|
||||||
res, err = req.Delete(url)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(url)
|
|
||||||
case base.Put:
|
|
||||||
res, err = req.Put(url)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if e.Error.Code != 0 {
|
|
||||||
if e.Error.Code == 401 {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driver.Request(rawUrl, method, headers, query, form, data, resp, account)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&GoogleDrive{})
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package google
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
MimeType string `json:"mimeType"`
|
|
||||||
ModifiedTime *time.Time `json:"modifiedTime"`
|
|
||||||
Size string `json:"size"`
|
|
||||||
ThumbnailLink string `json:"thumbnailLink"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) GetSize() uint64 {
|
|
||||||
if f.GetType() == conf.FOLDER {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
size, _ := strconv.ParseUint(f.Size, 10, 64)
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) GetName() string {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f File) GetType() int {
|
|
||||||
mimeType := f.MimeType
|
|
||||||
if mimeType == "application/vnd.google-apps.folder" || mimeType == "application/vnd.google-apps.shortcut" {
|
|
||||||
return conf.FOLDER
|
|
||||||
}
|
|
||||||
return utils.GetFileType(path.Ext(f.Name))
|
|
||||||
}
|
|
@ -1,202 +0,0 @@
|
|||||||
package lanzou
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Lanzou struct{}
|
|
||||||
|
|
||||||
func (driver Lanzou) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Lanzou",
|
|
||||||
NoCors: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Lanzou) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "lanzou type",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Values: "cookie,url",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "access_token",
|
|
||||||
Label: "cookie",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Description: "about 15 days valid",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "site_url",
|
|
||||||
Label: "share url",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "share password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Lanzou) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if account.InternalType == "cookie" {
|
|
||||||
if account.RootFolder == "" {
|
|
||||||
account.RootFolder = "-1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
account.Status = "work"
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Lanzou) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Lanzou) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var rawFiles []LanZouFile
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
rawFiles, _ = cache.([]LanZouFile)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawFiles, err = driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(rawFiles) > 0 {
|
|
||||||
_ = base.SetCache(path, rawFiles, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
for _, file := range rawFiles {
|
|
||||||
files = append(files, *driver.FormatFile(&file))
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Lanzou) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("down file: %+v", file)
|
|
||||||
downId := file.Id
|
|
||||||
pwd := ""
|
|
||||||
if account.InternalType == "cookie" {
|
|
||||||
downId, pwd, err = driver.GetDownPageId(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var url string
|
|
||||||
//if pwd != "" {
|
|
||||||
//url, err = driver.GetLinkWithPassword(downId, pwd, account)
|
|
||||||
//} else {
|
|
||||||
url, err = driver.GetLink(downId, pwd, account)
|
|
||||||
//}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
link := base.Link{
|
|
||||||
Url: url,
|
|
||||||
Headers: []base.Header{
|
|
||||||
{Name: "User-Agent", Value: base.UserAgent},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("lanzou path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Lanzou) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
// r.Header.Del("Origin")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) MakeDir(path string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) Move(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) Delete(path string, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
return base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Lanzou)(nil)
|
|
@ -1,271 +0,0 @@
|
|||||||
package lanzou
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (driver *Lanzou) FormatFile(file *LanZouFile) *model.File {
|
|
||||||
now := time.Now()
|
|
||||||
f := &model.File{
|
|
||||||
Id: file.Id,
|
|
||||||
Name: file.Name,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
SizeStr: file.Size,
|
|
||||||
TimeStr: file.Time,
|
|
||||||
UpdatedAt: &now,
|
|
||||||
}
|
|
||||||
if file.Folder {
|
|
||||||
f.Id = file.FolId
|
|
||||||
} else {
|
|
||||||
f.Name = file.NameAll
|
|
||||||
}
|
|
||||||
f.Type = file.GetType()
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
type LanZouFilesResp struct {
|
|
||||||
Zt int `json:"zt"`
|
|
||||||
Info interface{} `json:"info"`
|
|
||||||
Text []LanZouFile `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZouFile, error) {
|
|
||||||
if account.InternalType == "cookie" {
|
|
||||||
files := make([]LanZouFile, 0)
|
|
||||||
var resp LanZouFilesResp
|
|
||||||
// folders
|
|
||||||
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"task": "47",
|
|
||||||
"folder_id": folderId,
|
|
||||||
}).Post("https://pc.woozooo.com/doupload.php")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if resp.Zt != 1 && resp.Zt != 2 {
|
|
||||||
return nil, fmt.Errorf("%v", resp.Info)
|
|
||||||
}
|
|
||||||
for _, file := range resp.Text {
|
|
||||||
file.Folder = true
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
// files
|
|
||||||
pg := 1
|
|
||||||
for {
|
|
||||||
_, err = base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"task": "5",
|
|
||||||
"folder_id": folderId,
|
|
||||||
"pg": strconv.Itoa(pg),
|
|
||||||
}).Post("https://pc.woozooo.com/doupload.php")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if resp.Zt != 1 {
|
|
||||||
return nil, fmt.Errorf("%v", resp.Info)
|
|
||||||
}
|
|
||||||
if len(resp.Text) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
files = append(files, resp.Text...)
|
|
||||||
pg++
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
} else {
|
|
||||||
return driver.GetFilesByUrl(account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error) {
|
|
||||||
files := make([]LanZouFile, 0)
|
|
||||||
shareUrl := account.SiteUrl
|
|
||||||
u, err := url.Parse(shareUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res, err := base.RestyClient.R().Get(shareUrl)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
lxArr := regexp.MustCompile(`'lx':(.+?),`).FindStringSubmatch(res.String())
|
|
||||||
if len(lxArr) == 0 {
|
|
||||||
return nil, fmt.Errorf("get empty page")
|
|
||||||
}
|
|
||||||
lx := lxArr[1]
|
|
||||||
fid := regexp.MustCompile(`'fid':(.+?),`).FindStringSubmatch(res.String())[1]
|
|
||||||
uid := regexp.MustCompile(`'uid':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
|
||||||
rep := regexp.MustCompile(`'rep':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
|
||||||
up := regexp.MustCompile(`'up':(.+?),`).FindStringSubmatch(res.String())[1]
|
|
||||||
ls := ""
|
|
||||||
if account.Password != "" {
|
|
||||||
ls = regexp.MustCompile(`'ls':(.+?),`).FindStringSubmatch(res.String())[1]
|
|
||||||
}
|
|
||||||
tName := regexp.MustCompile(`'t':(.+?),`).FindStringSubmatch(res.String())[1]
|
|
||||||
kName := regexp.MustCompile(`'k':(.+?),`).FindStringSubmatch(res.String())[1]
|
|
||||||
t := regexp.MustCompile(`var ` + tName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
|
||||||
k := regexp.MustCompile(`var ` + kName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
|
||||||
pg := 1
|
|
||||||
for {
|
|
||||||
var resp LanZouFilesResp
|
|
||||||
res, err = base.RestyClient.R().SetResult(&resp).SetFormData(map[string]string{
|
|
||||||
"lx": lx,
|
|
||||||
"fid": fid,
|
|
||||||
"uid": uid,
|
|
||||||
"pg": strconv.Itoa(pg),
|
|
||||||
"rep": rep,
|
|
||||||
"t": t,
|
|
||||||
"k": k,
|
|
||||||
"up": up,
|
|
||||||
"ls": ls,
|
|
||||||
"pwd": account.Password,
|
|
||||||
}).Post(fmt.Sprintf("https://%s/filemoreajax.php", u.Host))
|
|
||||||
if err != nil {
|
|
||||||
log.Debug(err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if resp.Zt != 1 {
|
|
||||||
return nil, fmt.Errorf("%v", resp.Info)
|
|
||||||
}
|
|
||||||
if len(resp.Text) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pg++
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
files = append(files, resp.Text...)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//type LanzouDownInfo struct {
|
|
||||||
// FId string `json:"f_id"`
|
|
||||||
// IsNewd string `json:"is_newd"`
|
|
||||||
//}
|
|
||||||
|
|
||||||
// GetDownPageId 获取下载页面的ID
|
|
||||||
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, string, error) {
|
|
||||||
var resp DownPageResp
|
|
||||||
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
|
||||||
SetFormData(map[string]string{
|
|
||||||
"task": "22",
|
|
||||||
"file_id": fileId,
|
|
||||||
}).Post("https://pc.woozooo.com/doupload.php")
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if resp.Zt != 1 {
|
|
||||||
return "", "", fmt.Errorf("%v", resp.Info)
|
|
||||||
}
|
|
||||||
return resp.Info.FId, resp.Info.Pwd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type LanzouLinkResp struct {
|
|
||||||
Dom string `json:"dom"`
|
|
||||||
Url string `json:"url"`
|
|
||||||
Zt int `json:"zt"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) GetLink(downId string, pwd string, account *model.Account) (string, error) {
|
|
||||||
shareUrl := account.SiteUrl
|
|
||||||
u, err := url.Parse(shareUrl)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
log.Debugln(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
|
||||||
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
iframe := regexp.MustCompile(`<iframe class="ifr2" name=".{2,20}" src="(.+?)"`).FindStringSubmatch(res.String())
|
|
||||||
if len(iframe) == 0 {
|
|
||||||
return driver.GetLinkWithPassword(downId, pwd, res.String(), account)
|
|
||||||
}
|
|
||||||
iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
|
|
||||||
res, err = base.RestyClient.R().Get(iframeUrl)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
log.Debugln(res.String())
|
|
||||||
ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String())
|
|
||||||
if len(ajaxdata) == 0 {
|
|
||||||
return "", fmt.Errorf("get iframe empty page")
|
|
||||||
}
|
|
||||||
signs := ajaxdata[1]
|
|
||||||
//sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
|
||||||
sign := regexp.MustCompile(`'sign':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
|
||||||
//websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
|
||||||
websign := ""
|
|
||||||
websignR := regexp.MustCompile(`var websign = '(.+?)'`).FindStringSubmatch(res.String())
|
|
||||||
if len(websignR) > 1 {
|
|
||||||
websign = websignR[1]
|
|
||||||
}
|
|
||||||
//websign := ""
|
|
||||||
//websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
|
||||||
websignkey := regexp.MustCompile(`var websignkey = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
|
||||||
var resp LanzouLinkResp
|
|
||||||
form := map[string]string{
|
|
||||||
"action": "downprocess",
|
|
||||||
"signs": signs,
|
|
||||||
"sign": sign,
|
|
||||||
"ves": "1",
|
|
||||||
"websign": websign,
|
|
||||||
"websignkey": websignkey,
|
|
||||||
}
|
|
||||||
log.Debugf("form: %+v", form)
|
|
||||||
res, err = base.RestyClient.R().SetResult(&resp).
|
|
||||||
SetHeader("origin", "https://"+u.Host).
|
|
||||||
SetHeader("referer", iframeUrl).
|
|
||||||
SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
|
|
||||||
log.Debug(res.String())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if resp.Zt == 1 {
|
|
||||||
return resp.Dom + "/file/" + resp.Url, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("failed get link")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver *Lanzou) GetLinkWithPassword(downId string, pwd string, html string, account *model.Account) (string, error) {
|
|
||||||
shareUrl := account.SiteUrl
|
|
||||||
u, err := url.Parse(shareUrl)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if html == "" {
|
|
||||||
log.Debugln(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
|
||||||
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
html = res.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
data := regexp.MustCompile(`data : '(.+?)'\+pwd,`).FindStringSubmatch(html)[1] + pwd
|
|
||||||
var resp LanzouLinkResp
|
|
||||||
_, err = base.RestyClient.R().SetResult(&resp).SetHeaders(map[string]string{
|
|
||||||
"Referer": fmt.Sprintf("https://%s/%s", u.Host, downId),
|
|
||||||
"Origin": "https://" + u.Host,
|
|
||||||
"content-type": "application/x-www-form-urlencoded",
|
|
||||||
}).SetBody(data).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if resp.Zt == 1 {
|
|
||||||
return resp.Dom + "/file/" + resp.Url, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("failed get link with password")
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Lanzou{})
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package lanzou
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LanZouFile struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
NameAll string `json:"name_all"`
|
|
||||||
Id string `json:"id"`
|
|
||||||
FolId string `json:"fol_id"`
|
|
||||||
Size string `json:"size"`
|
|
||||||
Time string `json:"time"`
|
|
||||||
Folder bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f LanZouFile) GetSize() uint64 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f LanZouFile) GetName() string {
|
|
||||||
if f.Folder {
|
|
||||||
return f.Name
|
|
||||||
}
|
|
||||||
return f.NameAll
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f LanZouFile) GetType() int {
|
|
||||||
if f.Folder {
|
|
||||||
return conf.FOLDER
|
|
||||||
}
|
|
||||||
return utils.GetFileType(path.Ext(f.NameAll))
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownPageResp struct {
|
|
||||||
Zt int `json:"zt"`
|
|
||||||
Info struct {
|
|
||||||
Pwd string `json:"pwd"`
|
|
||||||
Onof string `json:"onof"`
|
|
||||||
FId string `json:"f_id"`
|
|
||||||
Taoc string `json:"taoc"`
|
|
||||||
IsNewd string `json:"is_newd"`
|
|
||||||
} `json:"info"`
|
|
||||||
Text interface{} `json:"text"`
|
|
||||||
}
|
|
@ -1,317 +0,0 @@
|
|||||||
package mediatrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MediaTrack struct{}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "MediaTrack",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "access_token",
|
|
||||||
Label: "Token",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Description: "Unknown expiration time",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "updated_at,title,size",
|
|
||||||
Required: true,
|
|
||||||
Description: "title",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "desc",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "true,false",
|
|
||||||
Required: true,
|
|
||||||
Default: "false",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files, err = driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pre := "https://jayce.api.mediatrack.cn/v3/assets/" + file.Id
|
|
||||||
body, err := driver.Request(pre+"/token", base.Get, nil, nil, nil, nil, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
url := pre + "/raw"
|
|
||||||
res, err := base.NoRedirectClient.R().SetQueryParam("token", jsoniter.Get(body, "data").ToString()).Get(url)
|
|
||||||
return &base.Link{Url: res.Header().Get("location")}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("MediaTrack path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver MediaTrack) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) MakeDir(path string, account *model.Account) error {
|
|
||||||
parentFile, err := driver.File(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
|
|
||||||
_, err = driver.Request(url, base.Post, nil, nil, nil, base.Json{
|
|
||||||
"type": 1,
|
|
||||||
"title": utils.Base(path),
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Move(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"parent_id": dstParentFile.Id,
|
|
||||||
"ids": []string{srcFile.Id},
|
|
||||||
}
|
|
||||||
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/move"
|
|
||||||
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
url := "https://jayce.api.mediatrack.cn/v3/assets/" + srcFile.Id
|
|
||||||
data := base.Json{
|
|
||||||
"title": utils.Base(dst),
|
|
||||||
}
|
|
||||||
_, err = driver.Request(url, base.Put, nil, nil, nil, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"parent_id": dstParentFile.Id,
|
|
||||||
"ids": []string{srcFile.Id},
|
|
||||||
}
|
|
||||||
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/clone"
|
|
||||||
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Delete(path string, account *model.Account) error {
|
|
||||||
parentFile, err := driver.File(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"origin_id": parentFile.Id,
|
|
||||||
"ids": []string{file.Id},
|
|
||||||
}
|
|
||||||
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/delete"
|
|
||||||
_, err = driver.Request(url, base.Delete, nil, nil, nil, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
src := "assets/" + uuid.New().String()
|
|
||||||
var resp UploadResp
|
|
||||||
_, err = driver.Request("https://jayce.api.mediatrack.cn/v3/storage/tokens/asset", base.Get, nil, map[string]string{
|
|
||||||
"src": src,
|
|
||||||
}, nil, nil, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
credential := resp.Data.Credentials
|
|
||||||
cfg := &aws.Config{
|
|
||||||
Credentials: credentials.NewStaticCredentials(credential.TmpSecretID, credential.TmpSecretKey, credential.Token),
|
|
||||||
Region: &resp.Data.Region,
|
|
||||||
Endpoint: aws.String("cos.accelerate.myqcloud.com"),
|
|
||||||
}
|
|
||||||
s, err := session.NewSession(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
_, err = io.Copy(tempFile, file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uploader := s3manager.NewUploader(s)
|
|
||||||
input := &s3manager.UploadInput{
|
|
||||||
Bucket: &resp.Data.Bucket,
|
|
||||||
Key: &resp.Data.Object,
|
|
||||||
Body: tempFile,
|
|
||||||
}
|
|
||||||
_, err = uploader.Upload(input)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h := md5.New()
|
|
||||||
_, err = io.Copy(h, tempFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hash := hex.EncodeToString(h.Sum(nil))
|
|
||||||
data := base.Json{
|
|
||||||
"category": 0,
|
|
||||||
"description": file.GetFileName(),
|
|
||||||
"hash": hash,
|
|
||||||
"mime": file.MIMEType,
|
|
||||||
"size": file.GetSize(),
|
|
||||||
"src": src,
|
|
||||||
"title": file.GetFileName(),
|
|
||||||
"type": 0,
|
|
||||||
}
|
|
||||||
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*MediaTrack)(nil)
|
|
@ -1,183 +0,0 @@
|
|||||||
package mediatrack
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BaseResp struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type T struct {
|
|
||||||
BaseResp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
var e BaseResp
|
|
||||||
req.SetResult(&e)
|
|
||||||
var err error
|
|
||||||
var res *resty.Response
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(url)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(url)
|
|
||||||
case base.Delete:
|
|
||||||
res, err = req.Delete(url)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(url)
|
|
||||||
case base.Put:
|
|
||||||
res, err = req.Put(url)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugln(res.String())
|
|
||||||
if e.Status != "SUCCESS" {
|
|
||||||
return nil, errors.New(e.Message)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
err = utils.Json.Unmarshal(res.Body(), resp)
|
|
||||||
}
|
|
||||||
return res.Body(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Category int `json:"category"`
|
|
||||||
ChildAssets []interface{} `json:"childAssets"`
|
|
||||||
CommentCount int `json:"comment_count"`
|
|
||||||
CoverAsset interface{} `json:"cover_asset"`
|
|
||||||
CoverAssetID string `json:"cover_asset_id"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
DeletedAt string `json:"deleted_at"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
File *struct {
|
|
||||||
Cover string `json:"cover"`
|
|
||||||
Src string `json:"src"`
|
|
||||||
} `json:"file"`
|
|
||||||
//FileID string `json:"file_id"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
|
|
||||||
Size string `json:"size"`
|
|
||||||
Thumbnails []interface{} `json:"thumbnails"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) formatFile(file *File) *model.File {
|
|
||||||
f := model.File{
|
|
||||||
Id: file.ID,
|
|
||||||
Name: file.Title,
|
|
||||||
Size: 0,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: &file.UpdatedAt,
|
|
||||||
}
|
|
||||||
if file.File == nil {
|
|
||||||
// folder
|
|
||||||
f.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
size, _ := strconv.ParseInt(file.Size, 10, 64)
|
|
||||||
f.Size = size
|
|
||||||
f.Type = utils.GetFileType(path.Ext(file.Title))
|
|
||||||
if file.File.Cover != "" {
|
|
||||||
f.Thumbnail = "https://nano.mtres.cn/" + file.File.Cover
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &f
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChildrenResp struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Data struct {
|
|
||||||
Total int `json:"total"`
|
|
||||||
Assets []File `json:"assets"`
|
|
||||||
} `json:"data"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
TraceID string `json:"trace_id"`
|
|
||||||
RequestID string `json:"requestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver MediaTrack) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v4/assets/%s/children", parentId)
|
|
||||||
sort := ""
|
|
||||||
if account.OrderBy != "" {
|
|
||||||
if account.OrderDirection == "true" {
|
|
||||||
sort = "-"
|
|
||||||
}
|
|
||||||
sort += account.OrderBy
|
|
||||||
}
|
|
||||||
page := 1
|
|
||||||
for {
|
|
||||||
var resp ChildrenResp
|
|
||||||
_, err := driver.Request(url, base.Get, nil, map[string]string{
|
|
||||||
"page": strconv.Itoa(page),
|
|
||||||
"size": "50",
|
|
||||||
"sort": sort,
|
|
||||||
}, nil, nil, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(resp.Data.Assets) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
page++
|
|
||||||
for _, file := range resp.Data.Assets {
|
|
||||||
files = append(files, *driver.formatFile(&file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadResp struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Data struct {
|
|
||||||
Credentials struct {
|
|
||||||
TmpSecretID string `json:"TmpSecretId"`
|
|
||||||
TmpSecretKey string `json:"TmpSecretKey"`
|
|
||||||
Token string `json:"Token"`
|
|
||||||
ExpiredTime int `json:"ExpiredTime"`
|
|
||||||
Expiration time.Time `json:"Expiration"`
|
|
||||||
StartTime int `json:"StartTime"`
|
|
||||||
} `json:"credentials"`
|
|
||||||
Object string `json:"object"`
|
|
||||||
Bucket string `json:"bucket"`
|
|
||||||
Region string `json:"region"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Size string `json:"size"`
|
|
||||||
} `json:"data"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
TraceID string `json:"trace_id"`
|
|
||||||
RequestID string `json:"requestId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&MediaTrack{})
|
|
||||||
}
|
|
@ -1,269 +0,0 @@
|
|||||||
package native
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Native struct{}
|
|
||||||
|
|
||||||
func (driver Native) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Native",
|
|
||||||
OnlyProxy: true,
|
|
||||||
OnlyLocal: true,
|
|
||||||
NoNeedSetLink: true,
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Debugf("save a account: [%s]", account.Name)
|
|
||||||
if !utils.Exists(account.RootFolder) {
|
|
||||||
account.Status = fmt.Sprintf("[%s] not exist", account.RootFolder)
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return fmt.Errorf("[%s] not exist", account.RootFolder)
|
|
||||||
}
|
|
||||||
account.Status = "work"
|
|
||||||
account.Proxy = true
|
|
||||||
err := model.SaveAccount(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
if utils.IsContain(strings.Split(path, "/"), "..") {
|
|
||||||
return nil, base.ErrRelativePath
|
|
||||||
}
|
|
||||||
fullPath := filepath.Join(account.RootFolder, path)
|
|
||||||
if !utils.Exists(fullPath) {
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
f, err := os.Stat(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
time := f.ModTime()
|
|
||||||
file := &model.File{
|
|
||||||
Name: f.Name(),
|
|
||||||
UpdatedAt: &time,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
file.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
|
||||||
file.Size = f.Size()
|
|
||||||
}
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
if utils.IsContain(strings.Split(path, "/"), "..") {
|
|
||||||
return nil, base.ErrRelativePath
|
|
||||||
}
|
|
||||||
fullPath := filepath.Join(account.RootFolder, path)
|
|
||||||
if !utils.Exists(fullPath) {
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
rawFiles, err := ioutil.ReadDir(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, f := range rawFiles {
|
|
||||||
if strings.HasPrefix(f.Name(), ".") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
time := f.ModTime()
|
|
||||||
file := model.File{
|
|
||||||
Name: f.Name(),
|
|
||||||
Type: 0,
|
|
||||||
UpdatedAt: &time,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
file.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
|
||||||
file.Size = f.Size()
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
_, err = base.GetCache(path, account)
|
|
||||||
if len(files) != 0 && err != nil {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
_, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fullPath := filepath.Join(account.RootFolder, args.Path)
|
|
||||||
s, err := os.Stat(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if s.IsDir() {
|
|
||||||
return nil, base.ErrNotFile
|
|
||||||
}
|
|
||||||
link := base.Link{
|
|
||||||
FilePath: fullPath,
|
|
||||||
}
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
log.Debugf("native path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
//file.Url, _ = driver.Link(path, account)
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
//model.SortFiles(files, account)
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Native) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
// // unnecessary
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) MakeDir(path string, account *model.Account) error {
|
|
||||||
if utils.IsContain(strings.Split(path, "/"), "..") {
|
|
||||||
return base.ErrRelativePath
|
|
||||||
}
|
|
||||||
fullPath := filepath.Join(account.RootFolder, path)
|
|
||||||
err := os.MkdirAll(fullPath, 0700)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Move(src string, dst string, account *model.Account) error {
|
|
||||||
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
|
|
||||||
return base.ErrRelativePath
|
|
||||||
}
|
|
||||||
fullSrc := filepath.Join(account.RootFolder, src)
|
|
||||||
fullDst := filepath.Join(account.RootFolder, dst)
|
|
||||||
return os.Rename(fullSrc, fullDst)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return driver.Move(src, dst, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
|
|
||||||
return base.ErrRelativePath
|
|
||||||
}
|
|
||||||
fullSrc := filepath.Join(account.RootFolder, src)
|
|
||||||
fullDst := filepath.Join(account.RootFolder, dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstFile, err := driver.File(dst, account)
|
|
||||||
if err == nil {
|
|
||||||
if !dstFile.IsDir() {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
return driver.CopyDir(fullSrc, fullDst)
|
|
||||||
}
|
|
||||||
return driver.CopyFile(fullSrc, fullDst)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Delete(path string, account *model.Account) error {
|
|
||||||
if utils.IsContain(strings.Split(path, "/"), "..") {
|
|
||||||
return base.ErrRelativePath
|
|
||||||
}
|
|
||||||
fullPath := filepath.Join(account.RootFolder, path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if file.IsDir() {
|
|
||||||
return os.RemoveAll(fullPath)
|
|
||||||
}
|
|
||||||
return os.Remove(fullPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Native) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
if utils.IsContain(strings.Split(file.ParentPath, "/"), "..") {
|
|
||||||
return base.ErrRelativePath
|
|
||||||
}
|
|
||||||
fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name)
|
|
||||||
_, err := driver.File(filepath.Join(file.ParentPath, file.Name), account)
|
|
||||||
if err == nil {
|
|
||||||
// TODO overwrite?
|
|
||||||
}
|
|
||||||
basePath := filepath.Dir(fullPath)
|
|
||||||
if !utils.Exists(basePath) {
|
|
||||||
err := os.MkdirAll(basePath, 0744)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out, err := os.Create(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = out.Close()
|
|
||||||
}()
|
|
||||||
//var buf bytes.Buffer
|
|
||||||
//reader := io.TeeReader(file, &buf)
|
|
||||||
//h := md5.New()
|
|
||||||
//_, err = io.Copy(h, reader)
|
|
||||||
//if err != nil {
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
//hash := hex.EncodeToString(h.Sum(nil))
|
|
||||||
//log.Debugln("md5:", hash)
|
|
||||||
//_, err = io.Copy(out, &buf)
|
|
||||||
_, err = io.Copy(out, file)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Native)(nil)
|
|
@ -1,74 +0,0 @@
|
|||||||
package native
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File copies a single file from src to dst
|
|
||||||
func (driver *Native) CopyFile(src, dst string) error {
|
|
||||||
var err error
|
|
||||||
var srcfd *os.File
|
|
||||||
var dstfd *os.File
|
|
||||||
var srcinfo os.FileInfo
|
|
||||||
|
|
||||||
if srcfd, err = os.Open(src); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer srcfd.Close()
|
|
||||||
|
|
||||||
if dstfd, err = os.Create(dst); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer dstfd.Close()
|
|
||||||
|
|
||||||
if _, err = io.Copy(dstfd, srcfd); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if srcinfo, err = os.Stat(src); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Chmod(dst, srcinfo.Mode())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dir copies a whole directory recursively
|
|
||||||
func (driver *Native) CopyDir(src string, dst string) error {
|
|
||||||
var err error
|
|
||||||
var fds []os.FileInfo
|
|
||||||
var srcinfo os.FileInfo
|
|
||||||
|
|
||||||
if srcinfo, err = os.Stat(src); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if fds, err = ioutil.ReadDir(src); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, fd := range fds {
|
|
||||||
srcfp := path.Join(src, fd.Name())
|
|
||||||
dstfp := path.Join(dst, fd.Name())
|
|
||||||
|
|
||||||
if fd.IsDir() {
|
|
||||||
if err = driver.CopyDir(srcfp, dstfp); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err = driver.CopyFile(srcfp, dstfp); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Native{})
|
|
||||||
}
|
|
@ -1,279 +0,0 @@
|
|||||||
package onedrive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Onedrive struct{}
|
|
||||||
|
|
||||||
func (driver Onedrive) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Onedrive",
|
|
||||||
NoNeedSetLink: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "zone",
|
|
||||||
Label: "zone",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Values: "global,cn,us,de",
|
|
||||||
Description: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "onedrive type",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Values: "onedrive,sharepoint",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_id",
|
|
||||||
Label: "client id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "client_secret",
|
|
||||||
Label: "client secret",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "redirect_uri",
|
|
||||||
Label: "redirect uri",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Default: "https://tool.nn.ci/onedrive/callback",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "refresh_token",
|
|
||||||
Label: "refresh token",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "site_id",
|
|
||||||
Label: "site id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "name,size,lastModifiedDateTime",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "asc,desc",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
|
|
||||||
//if old != nil {
|
|
||||||
// conf.Cron.Remove(cron.EntryID(old.CronId))
|
|
||||||
//}
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, ok := onedriveHostMap[account.Zone]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("no [%s] zone", account.Zone)
|
|
||||||
}
|
|
||||||
account.RootFolder = utils.ParsePath(account.RootFolder)
|
|
||||||
err := driver.RefreshToken(account)
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//cronId, err := conf.Cron.AddFunc("@every 1h", func() {
|
|
||||||
// name := account.Name
|
|
||||||
// log.Debugf("onedrive account name: %s", name)
|
|
||||||
// newAccount, ok := model.GetAccount(name)
|
|
||||||
// log.Debugf("onedrive account: %+v", newAccount)
|
|
||||||
// if !ok {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// err = driver.RefreshToken(&newAccount)
|
|
||||||
// _ = model.SaveAccount(&newAccount)
|
|
||||||
//})
|
|
||||||
//if err != nil {
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
//account.CronId = int(cronId)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ := cache.([]model.File)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
rawFiles, err := driver.GetFiles(account, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
for _, file := range rawFiles {
|
|
||||||
files = append(files, *driver.FormatFile(&file))
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.GetFile(account, args.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if file.File == nil {
|
|
||||||
return nil, base.ErrNotFile
|
|
||||||
}
|
|
||||||
link := base.Link{
|
|
||||||
Url: file.Url,
|
|
||||||
}
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
log.Debugf("onedrive path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Onedrive) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
// r.Header.Del("Origin")
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) MakeDir(path string, account *model.Account) error {
|
|
||||||
url := driver.GetMetaUrl(account, false, utils.Dir(path)) + "/children"
|
|
||||||
data := base.Json{
|
|
||||||
"name": utils.Base(path),
|
|
||||||
"folder": base.Json{},
|
|
||||||
"@microsoft.graph.conflictBehavior": "rename",
|
|
||||||
}
|
|
||||||
_, err := driver.Request(url, base.Post, nil, nil, nil, &data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Move(src string, dst string, account *model.Account) error {
|
|
||||||
dstParentFile, err := driver.GetFile(account, utils.Dir(dst))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"parentReference": base.Json{
|
|
||||||
"id": dstParentFile.Id,
|
|
||||||
},
|
|
||||||
"name": utils.Base(dst),
|
|
||||||
}
|
|
||||||
url := driver.GetMetaUrl(account, false, src)
|
|
||||||
_, err = driver.Request(url, base.Patch, nil, nil, nil, &data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return driver.Move(src, dst, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
dstParentFile, err := driver.GetFile(account, utils.Dir(dst))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"parentReference": base.Json{
|
|
||||||
"driveId": dstParentFile.ParentReference.DriveId,
|
|
||||||
"id": dstParentFile.Id,
|
|
||||||
},
|
|
||||||
"name": utils.Base(dst),
|
|
||||||
}
|
|
||||||
url := driver.GetMetaUrl(account, false, src) + "/copy"
|
|
||||||
_, err = driver.Request(url, base.Post, nil, nil, nil, &data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Delete(path string, account *model.Account) error {
|
|
||||||
url := driver.GetMetaUrl(account, false, path)
|
|
||||||
_, err := driver.Request(url, base.Delete, nil, nil, nil, nil, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if file.GetSize() <= 4*1024*1024 {
|
|
||||||
err = driver.UploadSmall(file, account)
|
|
||||||
} else {
|
|
||||||
err = driver.UploadBig(file, account)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Onedrive)(nil)
|
|
@ -1,320 +0,0 @@
|
|||||||
package onedrive
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Host struct {
|
|
||||||
Oauth string
|
|
||||||
Api string
|
|
||||||
}
|
|
||||||
|
|
||||||
var onedriveHostMap = map[string]Host{
|
|
||||||
"global": {
|
|
||||||
Oauth: "https://login.microsoftonline.com",
|
|
||||||
Api: "https://graph.microsoft.com",
|
|
||||||
},
|
|
||||||
"cn": {
|
|
||||||
Oauth: "https://login.chinacloudapi.cn",
|
|
||||||
Api: "https://microsoftgraph.chinacloudapi.cn",
|
|
||||||
},
|
|
||||||
"us": {
|
|
||||||
Oauth: "https://login.microsoftonline.us",
|
|
||||||
Api: "https://graph.microsoft.us",
|
|
||||||
},
|
|
||||||
"de": {
|
|
||||||
Oauth: "https://login.microsoftonline.de",
|
|
||||||
Api: "https://graph.microsoft.de",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
|
|
||||||
path = filepath.Join(account.RootFolder, path)
|
|
||||||
//log.Debugf(path)
|
|
||||||
host, _ := onedriveHostMap[account.Zone]
|
|
||||||
if auth {
|
|
||||||
return host.Oauth
|
|
||||||
}
|
|
||||||
switch account.InternalType {
|
|
||||||
case "onedrive":
|
|
||||||
{
|
|
||||||
if path == "/" || path == "\\" {
|
|
||||||
return fmt.Sprintf("%s/v1.0/me/drive/root", host.Api)
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("%s/v1.0/me/drive/root:%s:", host.Api, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "sharepoint":
|
|
||||||
{
|
|
||||||
if path == "/" || path == "\\" {
|
|
||||||
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, account.SiteId)
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, account.SiteId, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneTokenErr struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
ErrorDescription string `json:"error_description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) RefreshToken(account *model.Account) error {
|
|
||||||
err := driver.refreshToken(account)
|
|
||||||
if err != nil && err == base.ErrEmptyToken {
|
|
||||||
return driver.refreshToken(account)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) refreshToken(account *model.Account) error {
|
|
||||||
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
|
|
||||||
var resp base.TokenResp
|
|
||||||
var e OneTokenErr
|
|
||||||
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
"client_id": account.ClientId,
|
|
||||||
"client_secret": account.ClientSecret,
|
|
||||||
"redirect_uri": account.RedirectUri,
|
|
||||||
"refresh_token": account.RefreshToken,
|
|
||||||
}).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.Error != "" {
|
|
||||||
account.Status = e.ErrorDescription
|
|
||||||
return fmt.Errorf("%s", e.ErrorDescription)
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
}
|
|
||||||
if resp.RefreshToken == "" {
|
|
||||||
account.Status = base.ErrEmptyToken.Error()
|
|
||||||
return base.ErrEmptyToken
|
|
||||||
}
|
|
||||||
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneFile struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
|
|
||||||
Url string `json:"@microsoft.graph.downloadUrl"`
|
|
||||||
File *struct {
|
|
||||||
MimeType string `json:"mimeType"`
|
|
||||||
} `json:"file"`
|
|
||||||
Thumbnails []struct {
|
|
||||||
Medium struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
} `json:"medium"`
|
|
||||||
} `json:"thumbnails"`
|
|
||||||
ParentReference struct {
|
|
||||||
DriveId string `json:"driveId"`
|
|
||||||
} `json:"parentReference"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneFiles struct {
|
|
||||||
Value []OneFile `json:"value"`
|
|
||||||
NextLink string `json:"@odata.nextLink"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneRespErr struct {
|
|
||||||
Error struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
} `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) FormatFile(file *OneFile) *model.File {
|
|
||||||
f := &model.File{
|
|
||||||
Name: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
UpdatedAt: file.LastModifiedDateTime,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
Url: file.Url,
|
|
||||||
Id: file.Id,
|
|
||||||
}
|
|
||||||
if len(file.Thumbnails) > 0 {
|
|
||||||
f.Thumbnail = file.Thumbnails[0].Medium.Url
|
|
||||||
}
|
|
||||||
if file.File == nil {
|
|
||||||
f.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) {
|
|
||||||
var res []OneFile
|
|
||||||
nextLink := driver.GetMetaUrl(account, false, path) + "/children?$expand=thumbnails"
|
|
||||||
if account.OrderBy != "" {
|
|
||||||
nextLink += fmt.Sprintf("&orderby=%s", account.OrderBy)
|
|
||||||
if account.OrderDirection != "" {
|
|
||||||
nextLink += fmt.Sprintf("%%20%s", account.OrderDirection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for nextLink != "" {
|
|
||||||
var files OneFiles
|
|
||||||
_, err := driver.Request(nextLink, base.Get, nil, nil, nil, nil, &files, account)
|
|
||||||
//var e OneRespErr
|
|
||||||
//_, err := oneClient.R().SetResult(&files).SetError(&e).
|
|
||||||
// SetHeader("Authorization", "Bearer "+account.AccessToken).
|
|
||||||
// Get(nextLink)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//if e.Error.Code != "" {
|
|
||||||
// return nil, fmt.Errorf("%s", e.Error.Message)
|
|
||||||
//}
|
|
||||||
res = append(res, files.Value...)
|
|
||||||
nextLink = files.NextLink
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
|
|
||||||
var file OneFile
|
|
||||||
//var e OneRespErr
|
|
||||||
u := driver.GetMetaUrl(account, false, path)
|
|
||||||
_, err := driver.Request(u, base.Get, nil, nil, nil, nil, &file, account)
|
|
||||||
//_, err := oneClient.R().SetResult(&file).SetError(&e).
|
|
||||||
// SetHeader("Authorization", "Bearer "+account.AccessToken).
|
|
||||||
// Get(driver.GetMetaUrl(account, false, path))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//if e.Error.Code != "" {
|
|
||||||
// return nil, fmt.Errorf("%s", e.Error.Message)
|
|
||||||
//}
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
rawUrl := url
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var res *resty.Response
|
|
||||||
var err error
|
|
||||||
var e OneRespErr
|
|
||||||
req.SetError(&e)
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(url)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(url)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(url)
|
|
||||||
case base.Delete:
|
|
||||||
res, err = req.Delete(url)
|
|
||||||
case base.Put:
|
|
||||||
res, err = req.Put(url)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//log.Debug(res.String())
|
|
||||||
if e.Error.Code != "" {
|
|
||||||
if e.Error.Code == "InvalidAuthenticationToken" {
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driver.Request(rawUrl, method, headers, query, form, data, resp, account)
|
|
||||||
}
|
|
||||||
return nil, errors.New(e.Error.Message)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) UploadSmall(file *model.FileStream, account *model.Account) error {
|
|
||||||
url := driver.GetMetaUrl(account, false, utils.Join(file.ParentPath, file.Name)) + "/content"
|
|
||||||
data, err := ioutil.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = driver.Request(url, base.Put, nil, nil, nil, data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Onedrive) UploadBig(file *model.FileStream, account *model.Account) error {
|
|
||||||
url := driver.GetMetaUrl(account, false, utils.Join(file.ParentPath, file.Name)) + "/createUploadSession"
|
|
||||||
res, err := driver.Request(url, base.Post, nil, nil, nil, nil, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
|
||||||
var finish uint64 = 0
|
|
||||||
const DEFAULT = 4 * 1024 * 1024
|
|
||||||
for finish < file.GetSize() {
|
|
||||||
log.Debugf("upload: %d", finish)
|
|
||||||
var byteSize uint64 = DEFAULT
|
|
||||||
left := file.GetSize() - finish
|
|
||||||
if left < DEFAULT {
|
|
||||||
byteSize = left
|
|
||||||
}
|
|
||||||
byteData := make([]byte, byteSize)
|
|
||||||
n, err := io.ReadFull(file, byteData)
|
|
||||||
log.Debug(err, n)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("PUT", uploadUrl, bytes.NewBuffer(byteData))
|
|
||||||
req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
|
||||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, file.Size))
|
|
||||||
finish += byteSize
|
|
||||||
res, err := base.HttpClient.Do(req)
|
|
||||||
if res.StatusCode != 201 && res.StatusCode != 202 {
|
|
||||||
data, _ := ioutil.ReadAll(res.Body)
|
|
||||||
res.Body.Close()
|
|
||||||
return errors.New(string(data))
|
|
||||||
}
|
|
||||||
res.Body.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Onedrive{})
|
|
||||||
}
|
|
@ -1,111 +0,0 @@
|
|||||||
package operate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"runtime/debug"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Save(driver base.Driver, account, old *model.Account) error {
|
|
||||||
return driver.Save(account, old)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Path(driver base.Driver, account *model.Account, path string) (*model.File, []model.File, error) {
|
|
||||||
return driver.Path(path, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Files(driver base.Driver, account *model.Account, path string) ([]model.File, error) {
|
|
||||||
_, files, err := Path(driver, account, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if files == nil {
|
|
||||||
return nil, base.ErrNotFolder
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func File(driver base.Driver, account *model.Account, path string) (*model.File, error) {
|
|
||||||
return driver.File(path, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
|
|
||||||
log.Debugf("mkdir: %s", path)
|
|
||||||
_, err := Files(driver, account, path)
|
|
||||||
if err != base.ErrPathNotFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err = driver.MakeDir(path, account)
|
|
||||||
if err == nil && clearCache {
|
|
||||||
_ = base.DeleteCache(utils.Dir(path), account)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("mkdir error: %s", err.Error())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Move(driver base.Driver, account *model.Account, src, dst string, clearCache bool) error {
|
|
||||||
log.Debugf("move %s to %s", src, dst)
|
|
||||||
rename := false
|
|
||||||
if utils.Dir(src) == utils.Dir(dst) {
|
|
||||||
rename = true
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if rename {
|
|
||||||
err = driver.Rename(src, dst, account)
|
|
||||||
} else {
|
|
||||||
err = driver.Move(src, dst, account)
|
|
||||||
}
|
|
||||||
if err == nil && clearCache {
|
|
||||||
_ = base.DeleteCache(utils.Dir(src), account)
|
|
||||||
if !rename {
|
|
||||||
_ = base.DeleteCache(utils.Dir(dst), account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("move error: %s", err.Error())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Copy(driver base.Driver, account *model.Account, src, dst string, clearCache bool) error {
|
|
||||||
log.Debugf("copy %s to %s", src, dst)
|
|
||||||
err := driver.Copy(src, dst, account)
|
|
||||||
if err == nil && clearCache {
|
|
||||||
_ = base.DeleteCache(utils.Dir(dst), account)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("copy error: %s", err.Error())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Delete(driver base.Driver, account *model.Account, path string, clearCache bool) error {
|
|
||||||
log.Debugf("delete %s", path)
|
|
||||||
err := driver.Delete(path, account)
|
|
||||||
if err == nil && clearCache {
|
|
||||||
_ = base.DeleteCache(utils.Dir(path), account)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("delete error: %s", err.Error())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Upload(driver base.Driver, account *model.Account, file *model.FileStream, clearCache bool) error {
|
|
||||||
defer func() {
|
|
||||||
_ = file.Close()
|
|
||||||
}()
|
|
||||||
err := driver.Upload(file, account)
|
|
||||||
if err == nil && clearCache {
|
|
||||||
_ = base.DeleteCache(file.ParentPath, account)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("upload error: %s", err.Error())
|
|
||||||
}
|
|
||||||
debug.FreeOSMemory()
|
|
||||||
return err
|
|
||||||
}
|
|
@ -1,330 +0,0 @@
|
|||||||
package pikpak
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PikPak struct{}
|
|
||||||
|
|
||||||
func (driver PikPak) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "PikPak",
|
|
||||||
ApiProxy: true,
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "username",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
err := driver.Login(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawFiles, err := driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files = make([]model.File, 0)
|
|
||||||
for _, file := range rawFiles {
|
|
||||||
files = append(files, *driver.FormatFile(&file))
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var resp File
|
|
||||||
_, err = driver.Request(fmt.Sprintf("https://api-drive.mypikpak.com/drive/v1/files/%s?_magic=2021&thumbnail_size=SIZE_LARGE", file.Id),
|
|
||||||
base.Get, nil, nil, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
link := base.Link{
|
|
||||||
Url: resp.WebContentLink,
|
|
||||||
}
|
|
||||||
if len(resp.Medias) > 0 && resp.Medias[0].Link.Url != "" {
|
|
||||||
log.Debugln("use media link")
|
|
||||||
link.Url = resp.Medias[0].Link.Url
|
|
||||||
}
|
|
||||||
return &link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("pikpak path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver PikPak) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) MakeDir(path string, account *model.Account) error {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
parentFile, err := driver.File(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &base.Json{
|
|
||||||
"kind": "drive#folder",
|
|
||||||
"parent_id": parentFile.Id,
|
|
||||||
"name": name,
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Move(src string, dst string, account *model.Account) error {
|
|
||||||
dstDir, _ := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstDirFile, err := driver.File(dstDir, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchMove", base.Post, nil, &base.Json{
|
|
||||||
"ids": []string{srcFile.Id},
|
|
||||||
"to": base.Json{
|
|
||||||
"parent_id": dstDirFile.Id,
|
|
||||||
},
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
_, dstName := filepath.Split(dst)
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files/"+srcFile.Id, base.Patch, nil, &base.Json{
|
|
||||||
"name": dstName,
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstDirFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchCopy", base.Post, nil, &base.Json{
|
|
||||||
"ids": []string{srcFile.Id},
|
|
||||||
"to": base.Json{
|
|
||||||
"parent_id": dstDirFile.Id,
|
|
||||||
},
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Delete(path string, account *model.Account) error {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchTrash", base.Post, nil, &base.Json{
|
|
||||||
"ids": []string{file.Id},
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"kind": "drive#file",
|
|
||||||
"name": file.GetFileName(),
|
|
||||||
"size": file.GetSize(),
|
|
||||||
"hash": "1CF254FBC456E1B012CD45C546636AA62CF8350E",
|
|
||||||
"upload_type": "UPLOAD_TYPE_RESUMABLE",
|
|
||||||
"objProvider": base.Json{"provider": "UPLOAD_TYPE_UNKNOWN"},
|
|
||||||
"parent_id": parentFile.Id,
|
|
||||||
}
|
|
||||||
res, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &data, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
params := jsoniter.Get(res, "resumable").Get("params")
|
|
||||||
endpoint := params.Get("endpoint").ToString()
|
|
||||||
endpointS := strings.Split(endpoint, ".")
|
|
||||||
endpoint = strings.Join(endpointS[1:], ".")
|
|
||||||
accessKeyId := params.Get("access_key_id").ToString()
|
|
||||||
accessKeySecret := params.Get("access_key_secret").ToString()
|
|
||||||
securityToken := params.Get("security_token").ToString()
|
|
||||||
key := params.Get("key").ToString()
|
|
||||||
bucket := params.Get("bucket").ToString()
|
|
||||||
cfg := &aws.Config{
|
|
||||||
Credentials: credentials.NewStaticCredentials(accessKeyId, accessKeySecret, securityToken),
|
|
||||||
Region: aws.String("pikpak"),
|
|
||||||
Endpoint: &endpoint,
|
|
||||||
}
|
|
||||||
s, err := session.NewSession(cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uploader := s3manager.NewUploader(s)
|
|
||||||
input := &s3manager.UploadInput{
|
|
||||||
Bucket: &bucket,
|
|
||||||
Key: &key,
|
|
||||||
Body: file,
|
|
||||||
}
|
|
||||||
_, err = uploader.Upload(input)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// use aliyun-oss-sdk
|
|
||||||
//func (driver PikPak) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
// if file == nil {
|
|
||||||
// return base.ErrEmptyFile
|
|
||||||
// }
|
|
||||||
// parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// data := base.Json{
|
|
||||||
// "kind": "drive#file",
|
|
||||||
// "name": file.GetFileName(),
|
|
||||||
// "size": file.GetSize(),
|
|
||||||
// "hash": "1CF254FBC456E1B012CD45C546636AA62CF8350E",
|
|
||||||
// "upload_type": "UPLOAD_TYPE_RESUMABLE",
|
|
||||||
// "objProvider": base.Json{"provider": "UPLOAD_TYPE_UNKNOWN"},
|
|
||||||
// "parent_id": parentFile.Id,
|
|
||||||
// }
|
|
||||||
// res, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &data, nil, account)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// params := jsoniter.Get(res, "resumable").Get("params")
|
|
||||||
// endpoint := params.Get("endpoint").ToString()
|
|
||||||
// endpointS := strings.Split(endpoint, ".")
|
|
||||||
// endpoint = strings.Join(endpointS[1:], ".")
|
|
||||||
// accessKeyId := params.Get("access_key_id").ToString()
|
|
||||||
// accessKeySecret := params.Get("access_key_secret").ToString()
|
|
||||||
// securityToken := params.Get("security_token").ToString()
|
|
||||||
// client, err := oss.New("https://"+endpoint, accessKeyId,
|
|
||||||
// accessKeySecret, oss.SecurityToken(securityToken))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// bucket, err := client.Bucket(params.Get("bucket").ToString())
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// signedURL, err := bucket.SignURL(params.Get("key").ToString(), oss.HTTPPut, 60)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// err = bucket.PutObjectWithURL(signedURL, file)
|
|
||||||
// return err
|
|
||||||
//}
|
|
||||||
|
|
||||||
var _ base.Driver = (*PikPak)(nil)
|
|
@ -1,232 +0,0 @@
|
|||||||
package pikpak
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RespErr struct {
|
|
||||||
ErrorCode int `json:"error_code"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Login(account *model.Account) error {
|
|
||||||
url := "https://user.mypikpak.com/v1/auth/signin"
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
var e RespErr
|
|
||||||
res, err := base.RestyClient.R().SetError(&e).SetBody(base.Json{
|
|
||||||
"captcha_token": "",
|
|
||||||
"client_id": "YNxT9w7GMdWvEOKa",
|
|
||||||
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
|
|
||||||
"username": account.Username,
|
|
||||||
"password": account.Password,
|
|
||||||
}).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if e.ErrorCode != 0 {
|
|
||||||
account.Status = e.Error
|
|
||||||
err = errors.New(e.Error)
|
|
||||||
} else {
|
|
||||||
data := res.Body()
|
|
||||||
account.Status = "work"
|
|
||||||
account.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
|
|
||||||
account.AccessToken = jsoniter.Get(data, "access_token").ToString()
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) RefreshToken(account *model.Account) error {
|
|
||||||
url := "https://user.mypikpak.com/v1/auth/token"
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
var e RespErr
|
|
||||||
res, err := base.RestyClient.R().SetError(&e).
|
|
||||||
SetHeader("user-agent", "").SetBody(base.Json{
|
|
||||||
"client_id": "YNxT9w7GMdWvEOKa",
|
|
||||||
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
|
|
||||||
"grant_type": "refresh_token",
|
|
||||||
"refresh_token": account.RefreshToken,
|
|
||||||
}).Post(url)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if e.ErrorCode != 0 {
|
|
||||||
if e.ErrorCode == 4126 {
|
|
||||||
// refresh_token 失效,重新登陆
|
|
||||||
return driver.Login(account)
|
|
||||||
}
|
|
||||||
account.Status = e.Error
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return errors.New(e.Error)
|
|
||||||
}
|
|
||||||
data := res.Body()
|
|
||||||
account.Status = "work"
|
|
||||||
account.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
|
|
||||||
account.AccessToken = jsoniter.Get(data, "access_token").ToString()
|
|
||||||
log.Debugf("%s\n %+v", res.String(), account)
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) Request(url string, method int, query map[string]string, data *base.Json, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
rawUrl := url
|
|
||||||
if account.APIProxyUrl != "" {
|
|
||||||
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
|
|
||||||
}
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var e RespErr
|
|
||||||
req.SetError(&e)
|
|
||||||
var res *resty.Response
|
|
||||||
var err error
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(url)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(url)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(url)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if e.ErrorCode != 0 {
|
|
||||||
if e.ErrorCode == 16 {
|
|
||||||
// login / refresh token
|
|
||||||
err = driver.RefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driver.Request(rawUrl, method, query, data, resp, account)
|
|
||||||
} else {
|
|
||||||
return nil, errors.New(e.Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
ModifiedTime *time.Time `json:"modified_time"`
|
|
||||||
Size string `json:"size"`
|
|
||||||
ThumbnailLink string `json:"thumbnail_link"`
|
|
||||||
WebContentLink string `json:"web_content_link"`
|
|
||||||
Medias []Media `json:"medias"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Media struct {
|
|
||||||
MediaId string `json:"media_id"`
|
|
||||||
MediaName string `json:"media_name"`
|
|
||||||
Video struct {
|
|
||||||
Height int `json:"height"`
|
|
||||||
Width int `json:"width"`
|
|
||||||
Duration int `json:"duration"`
|
|
||||||
BitRate int `json:"bit_rate"`
|
|
||||||
FrameRate int `json:"frame_rate"`
|
|
||||||
VideoCodec string `json:"video_codec"`
|
|
||||||
AudioCodec string `json:"audio_codec"`
|
|
||||||
VideoType string `json:"video_type"`
|
|
||||||
} `json:"video"`
|
|
||||||
Link struct {
|
|
||||||
Url string `json:"url"`
|
|
||||||
Token string `json:"token"`
|
|
||||||
Expire time.Time `json:"expire"`
|
|
||||||
} `json:"link"`
|
|
||||||
NeedMoreQuota bool `json:"need_more_quota"`
|
|
||||||
VipTypes []interface{} `json:"vip_types"`
|
|
||||||
RedirectLink string `json:"redirect_link"`
|
|
||||||
IconLink string `json:"icon_link"`
|
|
||||||
IsDefault bool `json:"is_default"`
|
|
||||||
Priority int `json:"priority"`
|
|
||||||
IsOrigin bool `json:"is_origin"`
|
|
||||||
ResolutionName string `json:"resolution_name"`
|
|
||||||
IsVisible bool `json:"is_visible"`
|
|
||||||
Category string `json:"category"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) FormatFile(file *File) *model.File {
|
|
||||||
size, _ := strconv.ParseInt(file.Size, 10, 64)
|
|
||||||
f := &model.File{
|
|
||||||
Id: file.Id,
|
|
||||||
Name: file.Name,
|
|
||||||
Size: size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: file.ModifiedTime,
|
|
||||||
Thumbnail: file.ThumbnailLink,
|
|
||||||
}
|
|
||||||
if file.Kind == "drive#folder" {
|
|
||||||
f.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
f.Type = utils.GetFileType(filepath.Ext(file.Name))
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
type Files struct {
|
|
||||||
Files []File `json:"files"`
|
|
||||||
NextPageToken string `json:"next_page_token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver PikPak) GetFiles(id string, account *model.Account) ([]File, error) {
|
|
||||||
res := make([]File, 0)
|
|
||||||
pageToken := "first"
|
|
||||||
for pageToken != "" {
|
|
||||||
if pageToken == "first" {
|
|
||||||
pageToken = ""
|
|
||||||
}
|
|
||||||
query := map[string]string{
|
|
||||||
"parent_id": id,
|
|
||||||
"thumbnail_size": "SIZE_LARGE",
|
|
||||||
"with_audit": "true",
|
|
||||||
"limit": "100",
|
|
||||||
"filters": `{"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}`,
|
|
||||||
"page_token": pageToken,
|
|
||||||
}
|
|
||||||
var resp Files
|
|
||||||
_, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Get, query, nil, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("%+v", resp)
|
|
||||||
pageToken = resp.NextPageToken
|
|
||||||
res = append(res, resp.Files...)
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&PikPak{})
|
|
||||||
}
|
|
@ -1,327 +0,0 @@
|
|||||||
package quark
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Quark struct{}
|
|
||||||
|
|
||||||
func (driver Quark) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Quark",
|
|
||||||
OnlyProxy: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "access_token",
|
|
||||||
Label: "Cookie",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "Unknown expiration time",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Default: "0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "file_type,file_name,updated_at",
|
|
||||||
Required: true,
|
|
||||||
Default: "file_name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "asc,desc",
|
|
||||||
Required: true,
|
|
||||||
Default: "asc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err := driver.Get("/config", nil, nil, account)
|
|
||||||
if err == nil {
|
|
||||||
account.Status = "work"
|
|
||||||
} else {
|
|
||||||
account.Status = err.Error()
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files, err = driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
path := args.Path
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"fids": []string{file.Id},
|
|
||||||
}
|
|
||||||
var resp DownResp
|
|
||||||
_, err = driver.Post("/file/download", data, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Url: resp.Data[0].DownloadUrl,
|
|
||||||
Headers: []base.Header{
|
|
||||||
{Name: "Cookie", Value: account.AccessToken},
|
|
||||||
{Name: "Referer", Value: "https://pan.quark.cn"},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("quark path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) MakeDir(path string, account *model.Account) error {
|
|
||||||
parentFile, err := driver.File(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"dir_init_lock": false,
|
|
||||||
"dir_path": "",
|
|
||||||
"file_name": utils.Base(path),
|
|
||||||
"pdir_fid": parentFile.Id,
|
|
||||||
}
|
|
||||||
_, err = driver.Post("/file", data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Move(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"action_type": 1,
|
|
||||||
"exclude_fids": []string{},
|
|
||||||
"filelist": []string{srcFile.Id},
|
|
||||||
"to_pdir_fid": dstParentFile.Id,
|
|
||||||
}
|
|
||||||
_, err = driver.Post("/file/move", data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"fid": srcFile.Id,
|
|
||||||
"file_name": utils.Base(dst),
|
|
||||||
}
|
|
||||||
_, err = driver.Post("/file/rename", data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Delete(path string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"action_type": 1,
|
|
||||||
"exclude_fids": []string{},
|
|
||||||
"filelist": []string{srcFile.Id},
|
|
||||||
}
|
|
||||||
_, err = driver.Post("/file/delete", data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = tempFile.Close()
|
|
||||||
_ = os.Remove(tempFile.Name())
|
|
||||||
}()
|
|
||||||
_, err = io.Copy(tempFile, file)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m := md5.New()
|
|
||||||
_, err = io.Copy(m, tempFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
md5Str := hex.EncodeToString(m.Sum(nil))
|
|
||||||
s := sha1.New()
|
|
||||||
_, err = io.Copy(s, tempFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = tempFile.Seek(0, io.SeekStart)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sha1Str := hex.EncodeToString(s.Sum(nil))
|
|
||||||
// pre
|
|
||||||
pre, err := driver.UpPre(file, parentFile.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Debugln("hash: ", md5Str, sha1Str)
|
|
||||||
// hash
|
|
||||||
finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if finish {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// part up
|
|
||||||
partSize := pre.Metadata.PartSize
|
|
||||||
var bytes []byte
|
|
||||||
md5s := make([]string, 0)
|
|
||||||
defaultBytes := make([]byte, partSize)
|
|
||||||
left := int64(file.GetSize())
|
|
||||||
partNumber := 1
|
|
||||||
for left > 0 {
|
|
||||||
if left > int64(partSize) {
|
|
||||||
bytes = defaultBytes
|
|
||||||
} else {
|
|
||||||
bytes = make([]byte, left)
|
|
||||||
}
|
|
||||||
_, err := io.ReadFull(tempFile, bytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
left -= int64(partSize)
|
|
||||||
log.Debugf("left: %d", left)
|
|
||||||
m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account)
|
|
||||||
//m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account, md5Str, sha1Str)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if m == "finish" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
md5s = append(md5s, m)
|
|
||||||
partNumber++
|
|
||||||
}
|
|
||||||
err = driver.UpCommit(pre, md5s, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return driver.UpFinish(pre, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Quark)(nil)
|
|
@ -1,272 +0,0 @@
|
|||||||
package quark
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/Xhofe/alist/utils/cookie"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (driver Quark) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
u := "https://drive.quark.cn/1/clouddrive" + pathname
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeaders(map[string]string{
|
|
||||||
"Cookie": account.AccessToken,
|
|
||||||
"Accept": "application/json, text/plain, */*",
|
|
||||||
"Referer": "https://pan.quark.cn/",
|
|
||||||
})
|
|
||||||
req.SetQueryParam("pr", "ucpro")
|
|
||||||
req.SetQueryParam("fr", "pc")
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var e Resp
|
|
||||||
var err error
|
|
||||||
var res *resty.Response
|
|
||||||
req.SetError(&e)
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(u)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(u)
|
|
||||||
case base.Delete:
|
|
||||||
res, err = req.Delete(u)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(u)
|
|
||||||
case base.Put:
|
|
||||||
res, err = req.Put(u)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
__puus := cookie.GetCookie(res.Cookies(), "__puus")
|
|
||||||
if __puus != nil {
|
|
||||||
account.AccessToken = cookie.SetStr(account.AccessToken, "__puus", __puus.Value)
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
}
|
|
||||||
//log.Debugf("%s response: %s", pathname, res.String())
|
|
||||||
if e.Status >= 400 || e.Code != 0 {
|
|
||||||
return nil, errors.New(e.Message)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Get(pathname string, query map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
return driver.Request(pathname, base.Get, nil, query, nil, nil, resp, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) Post(pathname string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
return driver.Request(pathname, base.Post, nil, nil, nil, data, resp, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) GetFiles(parent string, account *model.Account) ([]model.File, error) {
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
page := 1
|
|
||||||
size := 100
|
|
||||||
query := map[string]string{
|
|
||||||
"pdir_fid": parent,
|
|
||||||
"_size": strconv.Itoa(size),
|
|
||||||
"_fetch_total": "1",
|
|
||||||
"_sort": "file_type:asc," + account.OrderBy + ":" + account.OrderDirection,
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
query["_page"] = strconv.Itoa(page)
|
|
||||||
var resp SortResp
|
|
||||||
_, err := driver.Get("/file/sort", query, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, f := range resp.Data.List {
|
|
||||||
files = append(files, *driver.formatFile(&f))
|
|
||||||
}
|
|
||||||
if page*size >= resp.Metadata.Total {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
page++
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) UpPre(file *model.FileStream, parentId string, account *model.Account) (UpPreResp, error) {
|
|
||||||
now := time.Now()
|
|
||||||
data := base.Json{
|
|
||||||
"ccp_hash_update": true,
|
|
||||||
"dir_name": "",
|
|
||||||
"file_name": file.Name,
|
|
||||||
"format_type": file.MIMEType,
|
|
||||||
"l_created_at": now.UnixMilli(),
|
|
||||||
"l_updated_at": now.UnixMilli(),
|
|
||||||
"pdir_fid": parentId,
|
|
||||||
"size": file.Size,
|
|
||||||
}
|
|
||||||
log.Debugf("uppre data: %+v", data)
|
|
||||||
var resp UpPreResp
|
|
||||||
_, err := driver.Post("/file/upload/pre", data, &resp, account)
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) UpHash(md5, sha1, taskId string, account *model.Account) (bool, error) {
|
|
||||||
data := base.Json{
|
|
||||||
"md5": md5,
|
|
||||||
"sha1": sha1,
|
|
||||||
"task_id": taskId,
|
|
||||||
}
|
|
||||||
log.Debugf("hash: %+v", data)
|
|
||||||
var resp HashResp
|
|
||||||
_, err := driver.Post("/file/update/hash", data, &resp, account)
|
|
||||||
return resp.Data.Finish, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account) (string, error) {
|
|
||||||
//func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account, md5Str, sha1Str string) (string, error) {
|
|
||||||
timeStr := time.Now().UTC().Format(http.TimeFormat)
|
|
||||||
data := base.Json{
|
|
||||||
"auth_info": pre.Data.AuthInfo,
|
|
||||||
"auth_meta": fmt.Sprintf(`PUT
|
|
||||||
|
|
||||||
%s
|
|
||||||
%s
|
|
||||||
x-oss-date:%s
|
|
||||||
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
|
|
||||||
/%s/%s?partNumber=%d&uploadId=%s`,
|
|
||||||
mineType, timeStr, timeStr, pre.Data.Bucket, pre.Data.ObjKey, partNumber, pre.Data.UploadId),
|
|
||||||
"task_id": pre.Data.TaskId,
|
|
||||||
}
|
|
||||||
var resp UpAuthResp
|
|
||||||
_, err := driver.Post("/file/upload/auth", data, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
//if partNumber == 1 {
|
|
||||||
// finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
|
|
||||||
// if err != nil {
|
|
||||||
// return "", err
|
|
||||||
// }
|
|
||||||
// if finish {
|
|
||||||
// return "finish", nil
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
|
|
||||||
res, err := base.RestyClient.R().
|
|
||||||
SetHeaders(map[string]string{
|
|
||||||
"Authorization": resp.Data.AuthKey,
|
|
||||||
"Content-Type": mineType,
|
|
||||||
"Referer": "https://pan.quark.cn/",
|
|
||||||
"x-oss-date": timeStr,
|
|
||||||
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
|
|
||||||
}).
|
|
||||||
SetQueryParams(map[string]string{
|
|
||||||
"partNumber": strconv.Itoa(partNumber),
|
|
||||||
"uploadId": pre.Data.UploadId,
|
|
||||||
}).SetBody(bytes).Put(u)
|
|
||||||
if res.StatusCode() != 200 {
|
|
||||||
return "", fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
|
|
||||||
}
|
|
||||||
return res.Header().Get("ETag"), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) UpCommit(pre UpPreResp, md5s []string, account *model.Account) error {
|
|
||||||
timeStr := time.Now().UTC().Format(http.TimeFormat)
|
|
||||||
log.Debugf("md5s: %+v", md5s)
|
|
||||||
bodyBuilder := strings.Builder{}
|
|
||||||
bodyBuilder.WriteString(`<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<CompleteMultipartUpload>
|
|
||||||
`)
|
|
||||||
for i, m := range md5s {
|
|
||||||
bodyBuilder.WriteString(fmt.Sprintf(`<Part>
|
|
||||||
<PartNumber>%d</PartNumber>
|
|
||||||
<ETag>%s</ETag>
|
|
||||||
</Part>
|
|
||||||
`, i+1, m))
|
|
||||||
}
|
|
||||||
bodyBuilder.WriteString("</CompleteMultipartUpload>")
|
|
||||||
body := bodyBuilder.String()
|
|
||||||
m := md5.New()
|
|
||||||
m.Write([]byte(body))
|
|
||||||
contentMd5 := base64.StdEncoding.EncodeToString(m.Sum(nil))
|
|
||||||
callbackBytes, err := utils.Json.Marshal(pre.Data.Callback)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
callbackBase64 := base64.StdEncoding.EncodeToString(callbackBytes)
|
|
||||||
data := base.Json{
|
|
||||||
"auth_info": pre.Data.AuthInfo,
|
|
||||||
"auth_meta": fmt.Sprintf(`POST
|
|
||||||
%s
|
|
||||||
application/xml
|
|
||||||
%s
|
|
||||||
x-oss-callback:%s
|
|
||||||
x-oss-date:%s
|
|
||||||
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
|
|
||||||
/%s/%s?uploadId=%s`,
|
|
||||||
contentMd5, timeStr, callbackBase64, timeStr,
|
|
||||||
pre.Data.Bucket, pre.Data.ObjKey, pre.Data.UploadId),
|
|
||||||
"task_id": pre.Data.TaskId,
|
|
||||||
}
|
|
||||||
log.Debugf("xml: %s", body)
|
|
||||||
log.Debugf("auth data: %+v", data)
|
|
||||||
var resp UpAuthResp
|
|
||||||
_, err = driver.Post("/file/upload/auth", data, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
|
|
||||||
res, err := base.RestyClient.R().
|
|
||||||
SetHeaders(map[string]string{
|
|
||||||
"Authorization": resp.Data.AuthKey,
|
|
||||||
"Content-MD5": contentMd5,
|
|
||||||
"Content-Type": "application/xml",
|
|
||||||
"Referer": "https://pan.quark.cn/",
|
|
||||||
"x-oss-callback": callbackBase64,
|
|
||||||
"x-oss-date": timeStr,
|
|
||||||
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
|
|
||||||
}).
|
|
||||||
SetQueryParams(map[string]string{
|
|
||||||
"uploadId": pre.Data.UploadId,
|
|
||||||
}).SetBody(body).Post(u)
|
|
||||||
if res.StatusCode() != 200 {
|
|
||||||
return fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) UpFinish(pre UpPreResp, account *model.Account) error {
|
|
||||||
data := base.Json{
|
|
||||||
"obj_key": pre.Data.ObjKey,
|
|
||||||
"task_id": pre.Data.TaskId,
|
|
||||||
}
|
|
||||||
_, err := driver.Post("/file/upload/finish", data, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Quark{})
|
|
||||||
}
|
|
@ -1,134 +0,0 @@
|
|||||||
package quark
|
|
||||||
|
|
||||||
type Resp struct {
|
|
||||||
Status int `json:"status"`
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
//ReqId string `json:"req_id"`
|
|
||||||
//Timestamp int `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Fid string `json:"fid"`
|
|
||||||
FileName string `json:"file_name"`
|
|
||||||
//PdirFid string `json:"pdir_fid"`
|
|
||||||
//Category int `json:"category"`
|
|
||||||
//FileType int `json:"file_type"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
//FormatType string `json:"format_type"`
|
|
||||||
//Status int `json:"status"`
|
|
||||||
//Tags string `json:"tags,omitempty"`
|
|
||||||
//LCreatedAt int64 `json:"l_created_at"`
|
|
||||||
LUpdatedAt int64 `json:"l_updated_at"`
|
|
||||||
//NameSpace int `json:"name_space"`
|
|
||||||
//IncludeItems int `json:"include_items,omitempty"`
|
|
||||||
//RiskType int `json:"risk_type"`
|
|
||||||
//BackupSign int `json:"backup_sign"`
|
|
||||||
//Duration int `json:"duration"`
|
|
||||||
//FileSource string `json:"file_source"`
|
|
||||||
File bool `json:"file"`
|
|
||||||
//CreatedAt int64 `json:"created_at"`
|
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
|
||||||
//PrivateExtra struct {} `json:"_private_extra"`
|
|
||||||
//ObjCategory string `json:"obj_category,omitempty"`
|
|
||||||
//Thumbnail string `json:"thumbnail,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SortResp struct {
|
|
||||||
Resp
|
|
||||||
Data struct {
|
|
||||||
List []File `json:"list"`
|
|
||||||
} `json:"data"`
|
|
||||||
Metadata struct {
|
|
||||||
Size int `json:"_size"`
|
|
||||||
Page int `json:"_page"`
|
|
||||||
Count int `json:"_count"`
|
|
||||||
Total int `json:"_total"`
|
|
||||||
Way string `json:"way"`
|
|
||||||
} `json:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DownResp struct {
|
|
||||||
Resp
|
|
||||||
Data []struct {
|
|
||||||
//Fid string `json:"fid"`
|
|
||||||
//FileName string `json:"file_name"`
|
|
||||||
//PdirFid string `json:"pdir_fid"`
|
|
||||||
//Category int `json:"category"`
|
|
||||||
//FileType int `json:"file_type"`
|
|
||||||
//Size int `json:"size"`
|
|
||||||
//FormatType string `json:"format_type"`
|
|
||||||
//Status int `json:"status"`
|
|
||||||
//Tags string `json:"tags"`
|
|
||||||
//LCreatedAt int64 `json:"l_created_at"`
|
|
||||||
//LUpdatedAt int64 `json:"l_updated_at"`
|
|
||||||
//NameSpace int `json:"name_space"`
|
|
||||||
//Thumbnail string `json:"thumbnail"`
|
|
||||||
DownloadUrl string `json:"download_url"`
|
|
||||||
//Md5 string `json:"md5"`
|
|
||||||
//RiskType int `json:"risk_type"`
|
|
||||||
//RangeSize int `json:"range_size"`
|
|
||||||
//BackupSign int `json:"backup_sign"`
|
|
||||||
//ObjCategory string `json:"obj_category"`
|
|
||||||
//Duration int `json:"duration"`
|
|
||||||
//FileSource string `json:"file_source"`
|
|
||||||
//File bool `json:"file"`
|
|
||||||
//CreatedAt int64 `json:"created_at"`
|
|
||||||
//UpdatedAt int64 `json:"updated_at"`
|
|
||||||
//PrivateExtra struct {
|
|
||||||
//} `json:"_private_extra"`
|
|
||||||
} `json:"data"`
|
|
||||||
//Metadata struct {
|
|
||||||
// Acc2 string `json:"acc2"`
|
|
||||||
// Acc1 string `json:"acc1"`
|
|
||||||
//} `json:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpPreResp struct {
|
|
||||||
Resp
|
|
||||||
Data struct {
|
|
||||||
TaskId string `json:"task_id"`
|
|
||||||
Finish bool `json:"finish"`
|
|
||||||
UploadId string `json:"upload_id"`
|
|
||||||
ObjKey string `json:"obj_key"`
|
|
||||||
UploadUrl string `json:"upload_url"`
|
|
||||||
Fid string `json:"fid"`
|
|
||||||
Bucket string `json:"bucket"`
|
|
||||||
Callback struct {
|
|
||||||
CallbackUrl string `json:"callbackUrl"`
|
|
||||||
CallbackBody string `json:"callbackBody"`
|
|
||||||
} `json:"callback"`
|
|
||||||
FormatType string `json:"format_type"`
|
|
||||||
Size int `json:"size"`
|
|
||||||
AuthInfo string `json:"auth_info"`
|
|
||||||
} `json:"data"`
|
|
||||||
Metadata struct {
|
|
||||||
PartThread int `json:"part_thread"`
|
|
||||||
Acc2 string `json:"acc2"`
|
|
||||||
Acc1 string `json:"acc1"`
|
|
||||||
PartSize int `json:"part_size"` // 分片大小
|
|
||||||
} `json:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HashResp struct {
|
|
||||||
Resp
|
|
||||||
Data struct {
|
|
||||||
Finish bool `json:"finish"`
|
|
||||||
Fid string `json:"fid"`
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
FormatType string `json:"format_type"`
|
|
||||||
} `json:"data"`
|
|
||||||
Metadata struct {
|
|
||||||
} `json:"metadata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpAuthResp struct {
|
|
||||||
Resp
|
|
||||||
Data struct {
|
|
||||||
AuthKey string `json:"auth_key"`
|
|
||||||
Speed int `json:"speed"`
|
|
||||||
Headers []interface{} `json:"headers"`
|
|
||||||
} `json:"data"`
|
|
||||||
Metadata struct {
|
|
||||||
} `json:"metadata"`
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package quark
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getTime(t int64) *time.Time {
|
|
||||||
tm := time.UnixMilli(t)
|
|
||||||
//log.Debugln(tm)
|
|
||||||
return &tm
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Quark) formatFile(f *File) *model.File {
|
|
||||||
file := model.File{
|
|
||||||
Id: f.Fid,
|
|
||||||
Name: f.FileName,
|
|
||||||
Size: f.Size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: getTime(f.UpdatedAt),
|
|
||||||
}
|
|
||||||
if f.File {
|
|
||||||
file.Type = utils.GetFileType(path.Ext(f.FileName))
|
|
||||||
} else {
|
|
||||||
file.Type = conf.FOLDER
|
|
||||||
}
|
|
||||||
return &file
|
|
||||||
}
|
|
@ -1,304 +0,0 @@
|
|||||||
package s3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type S3 struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "S3",
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "bucket",
|
|
||||||
Label: "Bucket",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "endpoint",
|
|
||||||
Label: "Endpoint",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "region",
|
|
||||||
Label: "Region",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "access_key",
|
|
||||||
Label: "Access Key",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "access_secret",
|
|
||||||
Label: "Access Secret",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "custom_host",
|
|
||||||
Label: "Custom Host",
|
|
||||||
Type: base.TypeString,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "limit",
|
|
||||||
Label: "Sign url expire time(hours)",
|
|
||||||
Type: base.TypeNumber,
|
|
||||||
Description: "default 4 hours",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "zone",
|
|
||||||
Label: "placeholder filename",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Description: "default empty string",
|
|
||||||
Default: defaultPlaceholderName,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "bool_1",
|
|
||||||
Label: "S3ForcePathStyle",
|
|
||||||
Type: base.TypeBool,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "ListObject Version",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "v1,v2",
|
|
||||||
Default: "v1",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if account.Limit == 0 {
|
|
||||||
account.Limit = 4
|
|
||||||
}
|
|
||||||
client, err := driver.NewSession(account)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
} else {
|
|
||||||
sessionsMap[account.Name] = client
|
|
||||||
account.Status = "work"
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
} else {
|
|
||||||
if account.InternalType == "v2" {
|
|
||||||
files, err = driver.ListV2(path, account)
|
|
||||||
} else {
|
|
||||||
files, err = driver.List(path, account)
|
|
||||||
}
|
|
||||||
if err == nil && len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
client, err := driver.GetClient(account, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
path := driver.GetKey(args.Path, account, false)
|
|
||||||
disposition := fmt.Sprintf(`attachment;filename="%s"`, url.QueryEscape(utils.Base(path)))
|
|
||||||
input := &s3.GetObjectInput{
|
|
||||||
Bucket: &account.Bucket,
|
|
||||||
Key: &path,
|
|
||||||
//ResponseContentDisposition: &disposition,
|
|
||||||
}
|
|
||||||
if account.CustomHost == "" {
|
|
||||||
input.ResponseContentDisposition = &disposition
|
|
||||||
}
|
|
||||||
req, _ := client.GetObjectRequest(input)
|
|
||||||
var link string
|
|
||||||
if account.CustomHost != "" {
|
|
||||||
err = req.Build()
|
|
||||||
link = req.HTTPRequest.URL.String()
|
|
||||||
} else {
|
|
||||||
link, err = req.Presign(time.Hour * time.Duration(account.Limit))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Url: link,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("s3 path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver S3) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver S3) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) MakeDir(path string, account *model.Account) error {
|
|
||||||
// not support, generate a placeholder file
|
|
||||||
_, err := driver.File(path, account)
|
|
||||||
// exist
|
|
||||||
if err != base.ErrPathNotFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return driver.Upload(&model.FileStream{
|
|
||||||
File: ioutil.NopCloser(bytes.NewReader([]byte{})),
|
|
||||||
Size: 0,
|
|
||||||
ParentPath: path,
|
|
||||||
Name: getPlaceholderName(account.Zone),
|
|
||||||
MIMEType: "application/octet-stream",
|
|
||||||
}, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Move(src string, dst string, account *model.Account) error {
|
|
||||||
err := driver.Copy(src, dst, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return driver.Delete(src, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return driver.Move(src, dst, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
client, err := driver.GetClient(account, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
srcKey := driver.GetKey(src, account, srcFile.IsDir())
|
|
||||||
dstKey := driver.GetKey(dst, account, srcFile.IsDir())
|
|
||||||
input := &s3.CopyObjectInput{
|
|
||||||
Bucket: &account.Bucket,
|
|
||||||
CopySource: &srcKey,
|
|
||||||
Key: &dstKey,
|
|
||||||
}
|
|
||||||
_, err = client.CopyObject(input)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Delete(path string, account *model.Account) error {
|
|
||||||
client, err := driver.GetClient(account, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
key := driver.GetKey(path, account, file.IsDir())
|
|
||||||
input := &s3.DeleteObjectInput{
|
|
||||||
Bucket: &account.Bucket,
|
|
||||||
Key: &key,
|
|
||||||
}
|
|
||||||
_, err = client.DeleteObject(input)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
s, ok := sessionsMap[account.Name]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("can't find [%s] session", account.Name)
|
|
||||||
}
|
|
||||||
uploader := s3manager.NewUploader(s)
|
|
||||||
key := driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false)
|
|
||||||
log.Debugln("key:", key)
|
|
||||||
input := &s3manager.UploadInput{
|
|
||||||
Bucket: &account.Bucket,
|
|
||||||
Key: &key,
|
|
||||||
Body: file,
|
|
||||||
}
|
|
||||||
_, err := uploader.Upload(input)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*S3)(nil)
|
|
195
drivers/s3/s3.go
195
drivers/s3/s3.go
@ -1,195 +0,0 @@
|
|||||||
package s3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/request"
|
|
||||||
"github.com/aws/aws-sdk-go/aws/session"
|
|
||||||
"github.com/aws/aws-sdk-go/service/s3"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sessionsMap map[string]*session.Session
|
|
||||||
|
|
||||||
func (driver S3) NewSession(account *model.Account) (*session.Session, error) {
|
|
||||||
cfg := &aws.Config{
|
|
||||||
Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""),
|
|
||||||
Region: &account.Region,
|
|
||||||
Endpoint: &account.Endpoint,
|
|
||||||
S3ForcePathStyle: aws.Bool(account.Bool1),
|
|
||||||
}
|
|
||||||
return session.NewSession(cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) GetClient(account *model.Account, link bool) (*s3.S3, error) {
|
|
||||||
s, ok := sessionsMap[account.Name]
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("can't find [%s] session", account.Name)
|
|
||||||
}
|
|
||||||
client := s3.New(s)
|
|
||||||
if link && account.CustomHost != "" {
|
|
||||||
cURL, err := url.Parse(account.CustomHost)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client.Handlers.Build.PushBack(func(r *request.Request) {
|
|
||||||
if r.HTTPRequest.Method != http.MethodGet {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.HTTPRequest.URL.Scheme = cURL.Scheme
|
|
||||||
r.HTTPRequest.URL.Host = cURL.Host
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) List(prefix string, account *model.Account) ([]model.File, error) {
|
|
||||||
prefix = driver.GetKey(prefix, account, true)
|
|
||||||
log.Debugf("list: %s", prefix)
|
|
||||||
client, err := driver.GetClient(account, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
marker := ""
|
|
||||||
for {
|
|
||||||
input := &s3.ListObjectsInput{
|
|
||||||
Bucket: &account.Bucket,
|
|
||||||
Marker: &marker,
|
|
||||||
Prefix: &prefix,
|
|
||||||
Delimiter: aws.String("/"),
|
|
||||||
}
|
|
||||||
listObjectsResult, err := client.ListObjects(input)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, object := range listObjectsResult.CommonPrefixes {
|
|
||||||
name := utils.Base(strings.Trim(*object.Prefix, "/"))
|
|
||||||
file := model.File{
|
|
||||||
//Id: *object.Key,
|
|
||||||
Name: name,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
TimeStr: "-",
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
for _, object := range listObjectsResult.Contents {
|
|
||||||
name := utils.Base(*object.Key)
|
|
||||||
if name == getPlaceholderName(account.Zone) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
file := model.File{
|
|
||||||
//Id: *object.Key,
|
|
||||||
Name: name,
|
|
||||||
Size: *object.Size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: object.LastModified,
|
|
||||||
Type: utils.GetFileType(path.Ext(*object.Key)),
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
if listObjectsResult.IsTruncated == nil {
|
|
||||||
return nil, errors.New("IsTruncated nil")
|
|
||||||
}
|
|
||||||
if *listObjectsResult.IsTruncated {
|
|
||||||
marker = *listObjectsResult.NextMarker
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) ListV2(prefix string, account *model.Account) ([]model.File, error) {
|
|
||||||
prefix = driver.GetKey(prefix, account, true)
|
|
||||||
//if prefix == "" {
|
|
||||||
// prefix = "/"
|
|
||||||
//}
|
|
||||||
log.Debugf("list: %s", prefix)
|
|
||||||
client, err := driver.GetClient(account, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
var continuationToken, startAfter *string
|
|
||||||
for {
|
|
||||||
input := &s3.ListObjectsV2Input{
|
|
||||||
Bucket: &account.Bucket,
|
|
||||||
ContinuationToken: continuationToken,
|
|
||||||
Prefix: &prefix,
|
|
||||||
Delimiter: aws.String("/"),
|
|
||||||
StartAfter: startAfter,
|
|
||||||
}
|
|
||||||
listObjectsResult, err := client.ListObjectsV2(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debugf("resp: %+v", listObjectsResult)
|
|
||||||
for _, object := range listObjectsResult.CommonPrefixes {
|
|
||||||
name := utils.Base(strings.Trim(*object.Prefix, "/"))
|
|
||||||
file := model.File{
|
|
||||||
//Id: *object.Key,
|
|
||||||
Name: name,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
TimeStr: "-",
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
for _, object := range listObjectsResult.Contents {
|
|
||||||
name := utils.Base(*object.Key)
|
|
||||||
if name == getPlaceholderName(account.Zone) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
file := model.File{
|
|
||||||
//Id: *object.Key,
|
|
||||||
Name: name,
|
|
||||||
Size: *object.Size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: object.LastModified,
|
|
||||||
Type: utils.GetFileType(path.Ext(*object.Key)),
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
if !aws.BoolValue(listObjectsResult.IsTruncated) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if listObjectsResult.NextContinuationToken != nil {
|
|
||||||
continuationToken = listObjectsResult.NextContinuationToken
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(listObjectsResult.Contents) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
startAfter = listObjectsResult.Contents[len(listObjectsResult.Contents)-1].Key
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
|
|
||||||
path = utils.Join(account.RootFolder, path)
|
|
||||||
path = strings.TrimPrefix(path, "/")
|
|
||||||
if path != "" && dir {
|
|
||||||
path += "/"
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
sessionsMap = make(map[string]*session.Session)
|
|
||||||
base.RegisterDriver(&S3{})
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package s3
|
|
||||||
|
|
||||||
var defaultPlaceholderName = ".placeholder"
|
|
||||||
|
|
||||||
func getPlaceholderName(placeholder string) string {
|
|
||||||
if placeholder == "" {
|
|
||||||
return defaultPlaceholderName
|
|
||||||
}
|
|
||||||
return placeholder
|
|
||||||
}
|
|
@ -1,220 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"io"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SFTP struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "SFTP",
|
|
||||||
OnlyProxy: true,
|
|
||||||
OnlyLocal: true,
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Items() []base.Item {
|
|
||||||
// TODO fill need info
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "site_url",
|
|
||||||
Label: "ip/host",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "limit",
|
|
||||||
Label: "port",
|
|
||||||
Type: base.TypeNumber,
|
|
||||||
Required: true,
|
|
||||||
Default: "22",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "username",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Default: "/",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if old != nil {
|
|
||||||
clientsMap.Lock()
|
|
||||||
defer clientsMap.Unlock()
|
|
||||||
delete(clientsMap.clients, old.Name)
|
|
||||||
}
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err := GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
remotePath := utils.Join(account.RootFolder, path)
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ := cache.([]model.File)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
client, err := GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
rawFiles, err := client.Files(remotePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i := 0; i < len(rawFiles); i++ {
|
|
||||||
files = append(files, driver.formatFile(rawFiles[i]))
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
client, err := GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
remoteFileName := utils.Join(account.RootFolder, args.Path)
|
|
||||||
remoteFile, err := client.Open(remoteFileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Data: remoteFile,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
//TODO preview interface if driver support
|
|
||||||
return nil, base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) MakeDir(path string, account *model.Account) error {
|
|
||||||
client, err := GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.MkdirAll(utils.Join(account.RootFolder, path))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Move(src string, dst string, account *model.Account) error {
|
|
||||||
return driver.Rename(src, dst, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
client, err := GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.Rename(utils.Join(account.RootFolder, src), utils.Join(account.RootFolder, dst))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Delete(path string, account *model.Account) error {
|
|
||||||
client, err := GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.remove(utils.Join(account.RootFolder, path))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
client, err := GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstFile, err := client.Create(path.Join(account.RootFolder, file.ParentPath, file.Name))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
_ = dstFile.Close()
|
|
||||||
}()
|
|
||||||
_, err = io.Copy(dstFile, file)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*SFTP)(nil)
|
|
@ -1,110 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/pkg/sftp"
|
|
||||||
"golang.org/x/crypto/ssh"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var clientsMap = struct {
|
|
||||||
sync.Mutex
|
|
||||||
clients map[string]*Client
|
|
||||||
}{clients: make(map[string]*Client)}
|
|
||||||
|
|
||||||
func GetClient(account *model.Account) (*Client, error) {
|
|
||||||
clientsMap.Lock()
|
|
||||||
defer clientsMap.Unlock()
|
|
||||||
if v, ok := clientsMap.clients[account.Name]; ok {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
conn, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", account.SiteUrl, account.Limit), &ssh.ClientConfig{
|
|
||||||
User: account.Username,
|
|
||||||
Auth: []ssh.AuthMethod{ssh.Password(account.Password)},
|
|
||||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
client, err := sftp.NewClient(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
c := &Client{client}
|
|
||||||
clientsMap.clients[account.Name] = c
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
*sftp.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) Files(remotePath string) ([]os.FileInfo, error) {
|
|
||||||
return client.ReadDir(remotePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) remove(remotePath string) error {
|
|
||||||
f, err := client.Stat(remotePath)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
return client.removeDirectory(remotePath)
|
|
||||||
} else {
|
|
||||||
return client.removeFile(remotePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) removeDirectory(remotePath string) error {
|
|
||||||
//打不开,说明要么文件路径错误了,要么是第一次部署
|
|
||||||
remoteFiles, err := client.ReadDir(remotePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, backupDir := range remoteFiles {
|
|
||||||
remoteFilePath := path.Join(remotePath, backupDir.Name())
|
|
||||||
if backupDir.IsDir() {
|
|
||||||
err := client.removeDirectory(remoteFilePath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err := client.Remove(path.Join(remoteFilePath))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return client.RemoveDirectory(remotePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) removeFile(remotePath string) error {
|
|
||||||
return client.Remove(utils.Join(remotePath))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver SFTP) formatFile(f os.FileInfo) model.File {
|
|
||||||
t := f.ModTime()
|
|
||||||
file := model.File{
|
|
||||||
//Id: f.Id,
|
|
||||||
Name: f.Name(),
|
|
||||||
Size: f.Size(),
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: &t,
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
file.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
file.Type = utils.GetFileType(path.Ext(f.Name()))
|
|
||||||
}
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&SFTP{})
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// write all struct here
|
|
||||||
|
|
||||||
type Resp struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
FileName string `json:"file_name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
File bool `json:"file"`
|
|
||||||
UpdatedAt *time.Time `json:"updated_at"`
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
// write util func here, such as cal sign
|
|
@ -1,278 +0,0 @@
|
|||||||
package shandian
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Shandian struct{}
|
|
||||||
|
|
||||||
func (driver Shandian) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "ShandianPan",
|
|
||||||
NoNeedSetLink: true,
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "username",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "account username/phone number",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "account password",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if account.RootFolder == "" {
|
|
||||||
account.RootFolder = "0"
|
|
||||||
}
|
|
||||||
return driver.Login(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawFiles, err := driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files = make([]model.File, 0)
|
|
||||||
for _, file := range rawFiles {
|
|
||||||
files = append(files, *driver.FormatFile(&file))
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
log.Debugf("shandian link")
|
|
||||||
file, err := driver.File(args.Path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var e Resp
|
|
||||||
res, err := base.NoRedirectClient.R().SetError(&e).SetHeader("Accept", "application/json").SetQueryParams(map[string]string{
|
|
||||||
"id": file.Id,
|
|
||||||
"token": account.AccessToken,
|
|
||||||
}).Get("https://shandianpan.com/api/pan/file-download")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Code != 0 {
|
|
||||||
if e.Code == 10 {
|
|
||||||
err = driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driver.Link(args, account)
|
|
||||||
}
|
|
||||||
return nil, errors.New(e.Msg)
|
|
||||||
}
|
|
||||||
return &base.Link{
|
|
||||||
Url: res.Header().Get("location"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("shandian path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Shandian) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) MakeDir(path string, account *model.Account) error {
|
|
||||||
parentFile, err := driver.File(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"id": parentFile.Id,
|
|
||||||
"name": utils.Base(path),
|
|
||||||
}
|
|
||||||
_, err = driver.Post("https://shandianpan.com/api/pan/mkdir", data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Move(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"id": srcFile.Id,
|
|
||||||
"to_id": dstParentFile.Id,
|
|
||||||
}
|
|
||||||
_, err = driver.Post("https://shandianpan.com/api/pan/move", data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"id": srcFile.Id,
|
|
||||||
"name": utils.Base(dst),
|
|
||||||
}
|
|
||||||
_, err = driver.Post("https://shandianpan.com/api/pan/change", data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
return base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Delete(path string, account *model.Account) error {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"id": file.Id,
|
|
||||||
}
|
|
||||||
_, err = driver.Post("https://shandianpan.com/api/pan/recycle-in", data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var resp UploadResp
|
|
||||||
parentId, err := strconv.Atoi(parentFile.Id)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"id": parentId,
|
|
||||||
"name": file.GetFileName(),
|
|
||||||
}
|
|
||||||
res, err := driver.Post("https://shandianpan.com/api/pan/upload", data, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = utils.Json.Unmarshal(res, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.Code != 0 {
|
|
||||||
if resp.Code == 10 {
|
|
||||||
err = driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return driver.Upload(file, account)
|
|
||||||
}
|
|
||||||
return errors.New(resp.Msg)
|
|
||||||
}
|
|
||||||
var r Resp
|
|
||||||
_, err = base.RestyClient.R().SetMultipartFormData(map[string]string{
|
|
||||||
"token": account.AccessToken,
|
|
||||||
"id": "0",
|
|
||||||
"key": resp.Data.Key,
|
|
||||||
"ossAccessKeyId": resp.Data.Accessid,
|
|
||||||
"policy": resp.Data.Policy,
|
|
||||||
"signature": resp.Data.Signature,
|
|
||||||
"callback": resp.Data.Callback,
|
|
||||||
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
|
|
||||||
SetResult(&r).SetError(&r).Post("https:" + resp.Data.Host + "/")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.Code == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New(r.Msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Shandian)(nil)
|
|
@ -1,150 +0,0 @@
|
|||||||
package shandian
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Resp struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Msg string `json:"msg"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoginResp struct {
|
|
||||||
Resp
|
|
||||||
Data struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Login(account *model.Account) error {
|
|
||||||
var resp LoginResp
|
|
||||||
_, err := base.RestyClient.R().SetResult(&resp).SetHeader("Accept", "application/json").SetBody(base.Json{
|
|
||||||
"mobile": account.Username,
|
|
||||||
"password": account.Password,
|
|
||||||
"smscode": "",
|
|
||||||
}).Post("https://shandianpan.com/api/login")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if resp.Code != 0 {
|
|
||||||
account.Status = resp.Msg
|
|
||||||
err = errors.New(resp.Msg)
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
account.AccessToken = resp.Data.Token
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Id int64 `json:"id"`
|
|
||||||
Type int `json:"type"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
UpdateTime int64 `json:"update_time"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
Ext string `json:"ext"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) FormatFile(file *File) *model.File {
|
|
||||||
t := time.Unix(file.UpdateTime, 0)
|
|
||||||
f := &model.File{
|
|
||||||
Id: strconv.FormatInt(file.Id, 10),
|
|
||||||
Name: file.Name,
|
|
||||||
Size: file.Size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: &t,
|
|
||||||
}
|
|
||||||
if file.Type == 1 {
|
|
||||||
f.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
f.Type = utils.GetFileType(file.Ext)
|
|
||||||
if file.Ext != "" {
|
|
||||||
f.Name += "." + file.Ext
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) Post(url string, data map[string]interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Accept", "application/json")
|
|
||||||
data["token"] = account.AccessToken
|
|
||||||
req.SetBody(data)
|
|
||||||
var e Resp
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
} else {
|
|
||||||
req.SetResult(&e)
|
|
||||||
}
|
|
||||||
req.SetError(&e)
|
|
||||||
res, err := req.Post(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.String())
|
|
||||||
if e.Code != 0 {
|
|
||||||
if e.Code == 10 {
|
|
||||||
err = driver.Login(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driver.Post(url, data, resp, account)
|
|
||||||
}
|
|
||||||
return nil, errors.New(e.Msg)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type FilesResp struct {
|
|
||||||
Resp
|
|
||||||
Data []File `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Shandian) GetFiles(id string, account *model.Account) ([]File, error) {
|
|
||||||
// TODO page not wok
|
|
||||||
//res := make([]File, 0)
|
|
||||||
page := 1
|
|
||||||
//for {
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"id": id,
|
|
||||||
"page": page,
|
|
||||||
"page_size": 100,
|
|
||||||
}
|
|
||||||
var resp FilesResp
|
|
||||||
_, err := driver.Post("https://shandianpan.com/api/pan", data, &resp, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
//res = append(res, resp.Data...)
|
|
||||||
// if len(resp.Data) == 0 {
|
|
||||||
// break
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//return res, nil
|
|
||||||
return resp.Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadResp struct {
|
|
||||||
Resp
|
|
||||||
Data struct {
|
|
||||||
Accessid string `json:"accessid"`
|
|
||||||
Policy string `json:"policy"`
|
|
||||||
Expire int `json:"expire"`
|
|
||||||
Callback string `json:"callback"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
Host string `json:"host"`
|
|
||||||
Signature string `json:"signature"`
|
|
||||||
} `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Shandian{})
|
|
||||||
}
|
|
@ -1,286 +0,0 @@
|
|||||||
package teambition
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Teambition struct{}
|
|
||||||
|
|
||||||
func (driver Teambition) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Teambition",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "Teambition type",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Values: "China,International",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "access_token",
|
|
||||||
Label: "Cookie",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
Description: "Unknown expiration time",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "zone",
|
|
||||||
Label: "Project id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder file_id",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_by",
|
|
||||||
Label: "order_by",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "fileName,fileSize,updated,created",
|
|
||||||
Required: true,
|
|
||||||
Default: "fileName",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "order_direction",
|
|
||||||
Label: "order_direction",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Values: "Asc,Desc",
|
|
||||||
Required: true,
|
|
||||||
Default: "Asc",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err := driver.Request("/api/v2/roles", base.Get, nil, nil, nil, nil, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
} else {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files, err = driver.GetFiles(file.Id, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
path := args.Path
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
url := file.Url
|
|
||||||
res, err := base.NoRedirectClient.R().Get(url)
|
|
||||||
if res.StatusCode() == 302 {
|
|
||||||
url = res.Header().Get("location")
|
|
||||||
}
|
|
||||||
return &base.Link{Url: url}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("teambition path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver Teambition) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) MakeDir(path string, account *model.Account) error {
|
|
||||||
parentFile, err := driver.File(utils.Dir(path), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data := base.Json{
|
|
||||||
"objectType": "collection",
|
|
||||||
"_projectId": account.Zone,
|
|
||||||
"_creatorId": "",
|
|
||||||
"created": "",
|
|
||||||
"updated": "",
|
|
||||||
"title": utils.Base(path),
|
|
||||||
"color": "blue",
|
|
||||||
"description": "",
|
|
||||||
"workCount": 0,
|
|
||||||
"collectionType": "",
|
|
||||||
"recentWorks": []interface{}{},
|
|
||||||
"_parentId": parentFile.Id,
|
|
||||||
"subCount": nil,
|
|
||||||
}
|
|
||||||
_, err = driver.Request("/api/collections", base.Post, nil, nil, nil, &data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Move(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pre := "/api/works/"
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
pre = "/api/collections/"
|
|
||||||
}
|
|
||||||
_, err = driver.Request(pre+srcFile.Id+"/move", base.Put, nil, nil, nil, &base.Json{
|
|
||||||
"_parentId": dstParentFile.Id,
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pre := "/api/works/"
|
|
||||||
data := base.Json{
|
|
||||||
"fileName": utils.Base(dst),
|
|
||||||
}
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
pre = "/api/collections/"
|
|
||||||
data = base.Json{
|
|
||||||
"title": utils.Base(dst),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_, err = driver.Request(pre+srcFile.Id, base.Put, nil, nil, nil, &data, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(src, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dstParentFile, err := driver.File(utils.Dir(dst), account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pre := "/api/works/"
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
pre = "/api/collections/"
|
|
||||||
}
|
|
||||||
_, err = driver.Request(pre+srcFile.Id+"/fork", base.Put, nil, nil, nil, &base.Json{
|
|
||||||
"_parentId": dstParentFile.Id,
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Delete(path string, account *model.Account) error {
|
|
||||||
srcFile, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pre := "/api/works/"
|
|
||||||
if srcFile.IsDir() {
|
|
||||||
pre = "/api/collections/"
|
|
||||||
}
|
|
||||||
_, err = driver.Request(pre+srcFile.Id+"/archive", base.Post, nil, nil, nil, nil, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
|
||||||
if !parentFile.IsDir() {
|
|
||||||
return base.ErrNotFolder
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res, err := driver.Request("/projects", base.Get, nil, nil, nil, nil, nil, account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
token := GetBetweenStr(string(res), "strikerAuth":"", "","phoneForLogin")
|
|
||||||
var newFile *FileUpload
|
|
||||||
if file.Size <= 20971520 {
|
|
||||||
// post upload
|
|
||||||
newFile, err = driver.upload(file, token, account)
|
|
||||||
} else {
|
|
||||||
// chunk upload
|
|
||||||
//err = base.ErrNotImplement
|
|
||||||
newFile, err = driver.chunkUpload(file, token, account)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return driver.finishUpload(newFile, parentFile.Id, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Teambition)(nil)
|
|
@ -1,237 +0,0 @@
|
|||||||
package teambition
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"io"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ErrResp struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
url := "https://www.teambition.com" + pathname
|
|
||||||
if account.InternalType == "International" {
|
|
||||||
url = "https://us.teambition.com" + pathname
|
|
||||||
}
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeader("Cookie", account.AccessToken)
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var e ErrResp
|
|
||||||
var err error
|
|
||||||
var res *resty.Response
|
|
||||||
req.SetError(&e)
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(url)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(url)
|
|
||||||
case base.Delete:
|
|
||||||
res, err = req.Delete(url)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(url)
|
|
||||||
case base.Put:
|
|
||||||
res, err = req.Put(url)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Name != "" {
|
|
||||||
return nil, errors.New(e.Message)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
page := 1
|
|
||||||
for {
|
|
||||||
var collections []Collection
|
|
||||||
_, err := driver.Request("/api/collections", base.Get, nil, map[string]string{
|
|
||||||
"_parentId": parentId,
|
|
||||||
"_projectId": account.Zone,
|
|
||||||
"order": account.OrderBy + account.OrderDirection,
|
|
||||||
"count": "50",
|
|
||||||
"page": strconv.Itoa(page),
|
|
||||||
}, nil, nil, &collections, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(collections) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
page++
|
|
||||||
for _, collection := range collections {
|
|
||||||
if collection.Title == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
files = append(files, model.File{
|
|
||||||
Id: collection.ID,
|
|
||||||
Name: collection.Title,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: collection.Updated,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
page = 1
|
|
||||||
for {
|
|
||||||
var works []Work
|
|
||||||
_, err := driver.Request("/api/works", base.Get, nil, map[string]string{
|
|
||||||
"_parentId": parentId,
|
|
||||||
"_projectId": account.Zone,
|
|
||||||
"order": account.OrderBy + account.OrderDirection,
|
|
||||||
"count": "50",
|
|
||||||
"page": strconv.Itoa(page),
|
|
||||||
}, nil, nil, &works, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(works) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
page++
|
|
||||||
for _, work := range works {
|
|
||||||
files = append(files, model.File{
|
|
||||||
Id: work.ID,
|
|
||||||
Name: work.FileName,
|
|
||||||
Size: work.FileSize,
|
|
||||||
Type: utils.GetFileType(path.Ext(work.FileName)),
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: work.Updated,
|
|
||||||
Thumbnail: work.Thumbnail,
|
|
||||||
Url: work.DownloadURL,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) upload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
|
|
||||||
prefix := "tcs"
|
|
||||||
if account.InternalType == "International" {
|
|
||||||
prefix = "us-tcs"
|
|
||||||
}
|
|
||||||
var newFile FileUpload
|
|
||||||
_, err := base.RestyClient.R().SetResult(&newFile).SetHeader("Authorization", token).
|
|
||||||
SetMultipartFormData(map[string]string{
|
|
||||||
"name": file.GetFileName(),
|
|
||||||
"type": file.GetMIMEType(),
|
|
||||||
"size": strconv.FormatUint(file.GetSize(), 10),
|
|
||||||
//"lastModifiedDate": "",
|
|
||||||
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
|
|
||||||
Post(fmt.Sprintf("https://%s.teambition.net/upload", prefix))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &newFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
|
|
||||||
prefix := "tcs"
|
|
||||||
referer := "https://www.teambition.com/"
|
|
||||||
if account.InternalType == "International" {
|
|
||||||
prefix = "us-tcs"
|
|
||||||
referer = "https://us.teambition.com/"
|
|
||||||
}
|
|
||||||
var newChunk ChunkUpload
|
|
||||||
_, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token).
|
|
||||||
SetBody(base.Json{
|
|
||||||
"fileName": file.GetFileName(),
|
|
||||||
"fileSize": file.GetSize(),
|
|
||||||
"lastUpdated": time.Now(),
|
|
||||||
}).Post(fmt.Sprintf("https://%s.teambition.net/upload/chunk", prefix))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i := 0; i < newChunk.Chunks; i++ {
|
|
||||||
chunkSize := newChunk.ChunkSize
|
|
||||||
if i == newChunk.Chunks-1 {
|
|
||||||
chunkSize = int(file.GetSize()) - i*chunkSize
|
|
||||||
}
|
|
||||||
log.Debugf("%d : %d", i, chunkSize)
|
|
||||||
chunkData := make([]byte, chunkSize)
|
|
||||||
_, err = io.ReadFull(file, chunkData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u := fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s?chunk=%d&chunks=%d",
|
|
||||||
prefix, newChunk.FileKey, i+1, newChunk.Chunks)
|
|
||||||
log.Debugf("url: %s", u)
|
|
||||||
res, err := base.RestyClient.R().SetHeaders(map[string]string{
|
|
||||||
"Authorization": token,
|
|
||||||
"Content-Type": "application/octet-stream",
|
|
||||||
"Referer": referer,
|
|
||||||
}).SetBody(chunkData).Post(u)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
log.Debug(res.Status(), res.String())
|
|
||||||
//req, err := http.NewRequest("POST",
|
|
||||||
// u,
|
|
||||||
// bytes.NewBuffer(chunkData))
|
|
||||||
//if err != nil {
|
|
||||||
// return nil, err
|
|
||||||
//}
|
|
||||||
//req.Header.Set("Authorization", token)
|
|
||||||
//req.Header.Set("Content-Type", "application/octet-stream")
|
|
||||||
//req.Header.Set("Referer", "https://www.teambition.com/")
|
|
||||||
//resp, err := base.HttpClient.Do(req)
|
|
||||||
//res, _ := ioutil.ReadAll(resp.Body)
|
|
||||||
//log.Debugf("chunk upload status: %s, res: %s", resp.Status, string(res))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res, err := base.RestyClient.R().SetHeader("Authorization", token).Post(
|
|
||||||
fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s",
|
|
||||||
prefix, newChunk.FileKey))
|
|
||||||
log.Debug(res.Status(), res.String())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &newChunk.FileUpload, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Teambition) finishUpload(file *FileUpload, parentId string, account *model.Account) error {
|
|
||||||
file.InvolveMembers = []interface{}{}
|
|
||||||
file.Visible = "members"
|
|
||||||
file.ParentId = parentId
|
|
||||||
_, err := driver.Request("/api/works", base.Post, nil, nil, nil, base.Json{
|
|
||||||
"works": []FileUpload{*file},
|
|
||||||
"_parentId": parentId,
|
|
||||||
}, nil, account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Teambition{})
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package teambition
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type Collection struct {
|
|
||||||
ID string `json:"_id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Updated *time.Time `json:"updated"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Work struct {
|
|
||||||
ID string `json:"_id"`
|
|
||||||
FileName string `json:"fileName"`
|
|
||||||
FileSize int64 `json:"fileSize"`
|
|
||||||
FileKey string `json:"fileKey"`
|
|
||||||
FileCategory string `json:"fileCategory"`
|
|
||||||
DownloadURL string `json:"downloadUrl"`
|
|
||||||
ThumbnailURL string `json:"thumbnailUrl"`
|
|
||||||
Thumbnail string `json:"thumbnail"`
|
|
||||||
Updated *time.Time `json:"updated"`
|
|
||||||
PreviewURL string `json:"previewUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileUpload struct {
|
|
||||||
FileKey string `json:"fileKey"`
|
|
||||||
FileName string `json:"fileName"`
|
|
||||||
FileType string `json:"fileType"`
|
|
||||||
FileSize int `json:"fileSize"`
|
|
||||||
FileCategory string `json:"fileCategory"`
|
|
||||||
ImageWidth int `json:"imageWidth"`
|
|
||||||
ImageHeight int `json:"imageHeight"`
|
|
||||||
InvolveMembers []interface{} `json:"involveMembers"`
|
|
||||||
Source string `json:"source"`
|
|
||||||
Visible string `json:"visible"`
|
|
||||||
ParentId string `json:"_parentId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ChunkUpload struct {
|
|
||||||
FileUpload
|
|
||||||
Storage string `json:"storage"`
|
|
||||||
MimeType string `json:"mimeType"`
|
|
||||||
Chunks int `json:"chunks"`
|
|
||||||
ChunkSize int `json:"chunkSize"`
|
|
||||||
Created time.Time `json:"created"`
|
|
||||||
FileMD5 string `json:"fileMD5"`
|
|
||||||
LastUpdated time.Time `json:"lastUpdated"`
|
|
||||||
UploadedChunks []interface{} `json:"uploadedChunks"`
|
|
||||||
Token struct {
|
|
||||||
AppID string `json:"AppID"`
|
|
||||||
OrganizationID string `json:"OrganizationID"`
|
|
||||||
UserID string `json:"UserID"`
|
|
||||||
Exp time.Time `json:"Exp"`
|
|
||||||
Storage string `json:"Storage"`
|
|
||||||
Resource string `json:"Resource"`
|
|
||||||
Speed int `json:"Speed"`
|
|
||||||
} `json:"token"`
|
|
||||||
DownloadUrl string `json:"downloadUrl"`
|
|
||||||
ThumbnailUrl string `json:"thumbnailUrl"`
|
|
||||||
PreviewUrl string `json:"previewUrl"`
|
|
||||||
ImmPreviewUrl string `json:"immPreviewUrl"`
|
|
||||||
PreviewExt string `json:"previewExt"`
|
|
||||||
LastUploadTime interface{} `json:"lastUploadTime"`
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package teambition
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func GetBetweenStr(str, start, end string) string {
|
|
||||||
n := strings.Index(str, start)
|
|
||||||
if n == -1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
n = n + len(start)
|
|
||||||
str = string([]byte(str)[n:])
|
|
||||||
m := strings.Index(str, end)
|
|
||||||
if m == -1 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
str = string([]byte(str)[:m])
|
|
||||||
return str
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Template struct {
|
|
||||||
base.Base
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Template) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "Template",
|
|
||||||
OnlyProxy: false,
|
|
||||||
OnlyLocal: false,
|
|
||||||
ApiProxy: false,
|
|
||||||
NoNeedSetLink: false,
|
|
||||||
NoCors: false,
|
|
||||||
LocalSort: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Template) Items() []base.Item {
|
|
||||||
// TODO fill need info
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "refresh_token",
|
|
||||||
Label: "refresh token",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Default: "/",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Template) Save(account *model.Account, old *model.Account) error {
|
|
||||||
// TODO test available or init
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Template) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Template) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ := cache.([]model.File)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
var files []model.File
|
|
||||||
// TODO get files
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Template) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
// TODO get file link
|
|
||||||
return nil, base.ErrNotImplement
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Template) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optional function
|
|
||||||
//func (driver Template) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
// //TODO preview interface if driver support
|
|
||||||
// return nil, base.ErrNotImplement
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (driver Template) MakeDir(path string, account *model.Account) error {
|
|
||||||
// //TODO make dir
|
|
||||||
// return base.ErrNotImplement
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (driver Template) Move(src string, dst string, account *model.Account) error {
|
|
||||||
// //TODO move file/dir
|
|
||||||
// return base.ErrNotImplement
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (driver Template) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
// //TODO rename file/dir
|
|
||||||
// return base.ErrNotImplement
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (driver Template) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
// //TODO copy file/dir
|
|
||||||
// return base.ErrNotImplement
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (driver Template) Delete(path string, account *model.Account) error {
|
|
||||||
// //TODO delete file/dir
|
|
||||||
// return base.ErrNotImplement
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//func (driver Template) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
// //TODO upload file
|
|
||||||
// return base.ErrNotImplement
|
|
||||||
//}
|
|
||||||
|
|
||||||
var _ base.Driver = (*Template)(nil)
|
|
@ -1,90 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/go-resty/resty/v2"
|
|
||||||
"path"
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoginOrRefreshToken(account *model.Account) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Request(u string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
|
||||||
req := base.RestyClient.R()
|
|
||||||
req.SetHeaders(map[string]string{
|
|
||||||
"Authorization": "Bearer" + account.AccessToken,
|
|
||||||
"Accept": "application/json, text/plain, */*",
|
|
||||||
})
|
|
||||||
if headers != nil {
|
|
||||||
req.SetHeaders(headers)
|
|
||||||
}
|
|
||||||
if query != nil {
|
|
||||||
req.SetQueryParams(query)
|
|
||||||
}
|
|
||||||
if form != nil {
|
|
||||||
req.SetFormData(form)
|
|
||||||
}
|
|
||||||
if data != nil {
|
|
||||||
req.SetBody(data)
|
|
||||||
}
|
|
||||||
if resp != nil {
|
|
||||||
req.SetResult(resp)
|
|
||||||
}
|
|
||||||
var e Resp
|
|
||||||
var err error
|
|
||||||
var res *resty.Response
|
|
||||||
req.SetError(&e)
|
|
||||||
switch method {
|
|
||||||
case base.Get:
|
|
||||||
res, err = req.Get(u)
|
|
||||||
case base.Post:
|
|
||||||
res, err = req.Post(u)
|
|
||||||
case base.Delete:
|
|
||||||
res, err = req.Delete(u)
|
|
||||||
case base.Patch:
|
|
||||||
res, err = req.Patch(u)
|
|
||||||
case base.Put:
|
|
||||||
res, err = req.Put(u)
|
|
||||||
default:
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e.Code >= 400 {
|
|
||||||
if e.Code == 401 {
|
|
||||||
err = LoginOrRefreshToken(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Request(u, method, headers, query, form, data, resp, account)
|
|
||||||
}
|
|
||||||
return nil, errors.New(e.Message)
|
|
||||||
}
|
|
||||||
return res.Body(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver Template) formatFile(f *File) *model.File {
|
|
||||||
file := model.File{
|
|
||||||
Id: f.Id,
|
|
||||||
Name: f.FileName,
|
|
||||||
Size: f.Size,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: f.UpdatedAt,
|
|
||||||
}
|
|
||||||
if f.File {
|
|
||||||
file.Type = utils.GetFileType(path.Ext(f.FileName))
|
|
||||||
} else {
|
|
||||||
file.Type = conf.FOLDER
|
|
||||||
}
|
|
||||||
return &file
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&Template{})
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// write all struct here
|
|
||||||
|
|
||||||
type Resp struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type File struct {
|
|
||||||
Id string `json:"id"`
|
|
||||||
FileName string `json:"file_name"`
|
|
||||||
Size int64 `json:"size"`
|
|
||||||
File bool `json:"file"`
|
|
||||||
UpdatedAt *time.Time `json:"updated_at"`
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package template
|
|
||||||
|
|
||||||
// write util func here, such as cal sign
|
|
@ -1,237 +0,0 @@
|
|||||||
package uss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/upyun/go-sdk/v3/upyun"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type USS struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "USS",
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "bucket",
|
|
||||||
Label: "Bucket",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "endpoint",
|
|
||||||
Label: "Endpoint",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "access_key",
|
|
||||||
Label: "Operator Name",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "access_secret",
|
|
||||||
Label: "Operator Password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "root_folder",
|
|
||||||
Label: "root folder path",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "custom_host",
|
|
||||||
Label: "Custom Host",
|
|
||||||
Type: base.TypeString,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "limit",
|
|
||||||
Label: "Sign url expire time(hours)",
|
|
||||||
Type: base.TypeNumber,
|
|
||||||
Default: "4",
|
|
||||||
Description: "default 4 hours",
|
|
||||||
},
|
|
||||||
//{
|
|
||||||
// Name: "zone",
|
|
||||||
// Label: "placeholder filename",
|
|
||||||
// Type: base.TypeString,
|
|
||||||
// Description: "default empty string",
|
|
||||||
//},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if account.Limit == 0 {
|
|
||||||
account.Limit = 4
|
|
||||||
}
|
|
||||||
client, err := driver.NewUpYun(account)
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
} else {
|
|
||||||
clientsMap[account.Name] = client
|
|
||||||
account.Status = "work"
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: account.RootFolder,
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
var files []model.File
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ = cache.([]model.File)
|
|
||||||
} else {
|
|
||||||
files, err = driver.List(path, account)
|
|
||||||
if err == nil && len(files) > 0 {
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
key := driver.GetKey(args.Path, account, false)
|
|
||||||
host := account.CustomHost
|
|
||||||
if host == "" {
|
|
||||||
host = account.Endpoint
|
|
||||||
}
|
|
||||||
if strings.Contains(host, "://") {
|
|
||||||
host = "https://" + host
|
|
||||||
}
|
|
||||||
u := fmt.Sprintf("%s/%s", host, key)
|
|
||||||
downExp := time.Hour * time.Duration(account.Limit)
|
|
||||||
expireAt := time.Now().Add(downExp).Unix()
|
|
||||||
upd := url.QueryEscape(utils.Base(args.Path))
|
|
||||||
signStr := strings.Join([]string{account.AccessSecret, fmt.Sprint(expireAt), fmt.Sprintf("/%s", key)}, "&")
|
|
||||||
upt := utils.GetMD5Encode(signStr)[12:20] + fmt.Sprint(expireAt)
|
|
||||||
link := fmt.Sprintf("%s?_upd=%s&_upt=%s", u, upd, upt)
|
|
||||||
return &base.Link{Url: link}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
log.Debugf("s3 path: %s", path)
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) MakeDir(path string, account *model.Account) error {
|
|
||||||
client, err := driver.GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.Mkdir(driver.GetKey(path, account, true))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Move(src string, dst string, account *model.Account) error {
|
|
||||||
client, err := driver.GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.Move(&upyun.MoveObjectConfig{
|
|
||||||
SrcPath: driver.GetKey(src, account, false),
|
|
||||||
DestPath: driver.GetKey(dst, account, false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return driver.Move(src, dst, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
client, err := driver.GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.Copy(&upyun.CopyObjectConfig{
|
|
||||||
SrcPath: driver.GetKey(src, account, false),
|
|
||||||
DestPath: driver.GetKey(dst, account, false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Delete(path string, account *model.Account) error {
|
|
||||||
client, err := driver.GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.Delete(&upyun.DeleteObjectConfig{
|
|
||||||
Path: driver.GetKey(path, account, false),
|
|
||||||
Async: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
client, err := driver.GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.Put(&upyun.PutObjectConfig{
|
|
||||||
Path: driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false),
|
|
||||||
Reader: file,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*USS)(nil)
|
|
@ -1,82 +0,0 @@
|
|||||||
package uss
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"github.com/upyun/go-sdk/v3/upyun"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var clientsMap map[string]*upyun.UpYun
|
|
||||||
|
|
||||||
func (driver USS) NewUpYun(account *model.Account) (*upyun.UpYun, error) {
|
|
||||||
return upyun.NewUpYun(&upyun.UpYunConfig{
|
|
||||||
Bucket: account.Bucket,
|
|
||||||
Operator: account.AccessKey,
|
|
||||||
Password: account.AccessToken,
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) GetClient(account *model.Account) (*upyun.UpYun, error) {
|
|
||||||
client, ok := clientsMap[account.Name]
|
|
||||||
if ok {
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("can't get client")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) List(prefix string, account *model.Account) ([]model.File, error) {
|
|
||||||
prefix = driver.GetKey(prefix, account, true)
|
|
||||||
client, err := driver.GetClient(account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
objsChan := make(chan *upyun.FileInfo, 10)
|
|
||||||
defer close(objsChan)
|
|
||||||
go func() {
|
|
||||||
err = client.List(&upyun.GetObjectsConfig{
|
|
||||||
Path: prefix,
|
|
||||||
ObjectsChan: objsChan,
|
|
||||||
MaxListObjects: 0,
|
|
||||||
MaxListLevel: 1,
|
|
||||||
})
|
|
||||||
}()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
res := make([]model.File, 0)
|
|
||||||
for obj := range objsChan {
|
|
||||||
t := obj.Time
|
|
||||||
f := model.File{
|
|
||||||
Name: obj.Name,
|
|
||||||
Size: obj.Size,
|
|
||||||
UpdatedAt: &t,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
}
|
|
||||||
if obj.IsDir {
|
|
||||||
f.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
f.Type = utils.GetFileType(path.Ext(obj.Name))
|
|
||||||
}
|
|
||||||
res = append(res, f)
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver USS) GetKey(path string, account *model.Account, dir bool) string {
|
|
||||||
path = utils.Join(account.RootFolder, path)
|
|
||||||
path = strings.TrimPrefix(path, "/")
|
|
||||||
if dir {
|
|
||||||
path += "/"
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
clientsMap = make(map[string]*upyun.UpYun)
|
|
||||||
base.RegisterDriver(&USS{})
|
|
||||||
}
|
|
@ -1,224 +0,0 @@
|
|||||||
package webdav
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/conf"
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/drivers/webdav/odrvcookie"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WebDav struct{}
|
|
||||||
|
|
||||||
func (driver WebDav) Config() base.DriverConfig {
|
|
||||||
return base.DriverConfig{
|
|
||||||
Name: "WebDav",
|
|
||||||
OnlyProxy: true,
|
|
||||||
OnlyLocal: true,
|
|
||||||
NoNeedSetLink: true,
|
|
||||||
LocalSort: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Items() []base.Item {
|
|
||||||
return []base.Item{
|
|
||||||
{
|
|
||||||
Name: "site_url",
|
|
||||||
Label: "webdav root url",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "username",
|
|
||||||
Label: "username",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "password",
|
|
||||||
Label: "password",
|
|
||||||
Type: base.TypeString,
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "internal_type",
|
|
||||||
Label: "vendor",
|
|
||||||
Type: base.TypeSelect,
|
|
||||||
Required: true,
|
|
||||||
Default: "other",
|
|
||||||
Values: "sharepoint,other",
|
|
||||||
Description: "webdav vendor",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Save(account *model.Account, old *model.Account) error {
|
|
||||||
if account == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
if isSharePoint(account) {
|
|
||||||
_, err = odrvcookie.GetCookie(account.Username, account.Password, account.SiteUrl)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
account.Status = err.Error()
|
|
||||||
} else {
|
|
||||||
account.Status = "work"
|
|
||||||
}
|
|
||||||
_ = model.SaveAccount(account)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) File(path string, account *model.Account) (*model.File, error) {
|
|
||||||
if path == "/" {
|
|
||||||
return &model.File{
|
|
||||||
Id: "/",
|
|
||||||
Name: account.Name,
|
|
||||||
Size: 0,
|
|
||||||
Type: conf.FOLDER,
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: account.UpdatedAt,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
dir, name := filepath.Split(path)
|
|
||||||
files, err := driver.Files(dir, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
if file.Name == name {
|
|
||||||
return &file, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, base.ErrPathNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Files(path string, account *model.Account) ([]model.File, error) {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
cache, err := base.GetCache(path, account)
|
|
||||||
if err == nil {
|
|
||||||
files, _ := cache.([]model.File)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
c := driver.NewClient(account)
|
|
||||||
rawFiles, err := c.ReadDir(driver.WebDavPath(path))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
files := make([]model.File, 0)
|
|
||||||
if len(rawFiles) == 0 {
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
for _, f := range rawFiles {
|
|
||||||
t := f.ModTime()
|
|
||||||
file := model.File{
|
|
||||||
Name: f.Name(),
|
|
||||||
Size: f.Size(),
|
|
||||||
Driver: driver.Config().Name,
|
|
||||||
UpdatedAt: &t,
|
|
||||||
}
|
|
||||||
if f.IsDir() {
|
|
||||||
file.Type = conf.FOLDER
|
|
||||||
} else {
|
|
||||||
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
|
|
||||||
}
|
|
||||||
files = append(files, file)
|
|
||||||
}
|
|
||||||
_ = base.SetCache(path, files, account)
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
|
||||||
path := args.Path
|
|
||||||
c := driver.NewClient(account)
|
|
||||||
callback := func(r *http.Request) {
|
|
||||||
if args.Header.Get("Range") != "" {
|
|
||||||
r.Header.Set("Range", args.Header.Get("Range"))
|
|
||||||
}
|
|
||||||
if args.Header.Get("If-Range") != "" {
|
|
||||||
r.Header.Set("If-Range", args.Header.Get("If-Range"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reader, header, err := c.ReadStream(driver.WebDavPath(path), callback)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
link := &base.Link{Data: reader}
|
|
||||||
if header.Get("Content-Range") != "" {
|
|
||||||
link.Status = 206
|
|
||||||
link.Header = http.Header{
|
|
||||||
"Content-Range": header.Values("Content-Range"),
|
|
||||||
"Content-Length": header.Values("Content-Length"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
|
||||||
file, err := driver.File(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
if !file.IsDir() {
|
|
||||||
return file, nil, nil
|
|
||||||
}
|
|
||||||
files, err := driver.Files(path, account)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return nil, files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//func (driver WebDav) Proxy(r *http.Request, account *model.Account) {
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) {
|
|
||||||
return nil, base.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) MakeDir(path string, account *model.Account) error {
|
|
||||||
c := driver.NewClient(account)
|
|
||||||
err := c.MkdirAll(driver.WebDavPath(path), 0644)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Move(src string, dst string, account *model.Account) error {
|
|
||||||
c := driver.NewClient(account)
|
|
||||||
err := c.Rename(driver.WebDavPath(src), driver.WebDavPath(dst), true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Rename(src string, dst string, account *model.Account) error {
|
|
||||||
return driver.Move(src, dst, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Copy(src string, dst string, account *model.Account) error {
|
|
||||||
c := driver.NewClient(account)
|
|
||||||
err := c.Copy(driver.WebDavPath(src), driver.WebDavPath(dst), true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Delete(path string, account *model.Account) error {
|
|
||||||
c := driver.NewClient(account)
|
|
||||||
err := c.RemoveAll(driver.WebDavPath(path))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) Upload(file *model.FileStream, account *model.Account) error {
|
|
||||||
if file == nil {
|
|
||||||
return base.ErrEmptyFile
|
|
||||||
}
|
|
||||||
c := driver.NewClient(account)
|
|
||||||
path := utils.Join(file.ParentPath, file.Name)
|
|
||||||
callback := func(r *http.Request) {
|
|
||||||
r.Header.Set("Content-Type", file.GetMIMEType())
|
|
||||||
r.ContentLength = int64(file.GetSize())
|
|
||||||
}
|
|
||||||
err := c.WriteStream(driver.WebDavPath(path), file, 0644, callback)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ base.Driver = (*WebDav)(nil)
|
|
@ -1,47 +0,0 @@
|
|||||||
package odrvcookie
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/utils/cookie"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SpCookie struct {
|
|
||||||
Cookie string
|
|
||||||
expire time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sp SpCookie) IsExpire() bool {
|
|
||||||
return time.Now().After(sp.expire)
|
|
||||||
}
|
|
||||||
|
|
||||||
var cookiesMap = struct {
|
|
||||||
sync.Mutex
|
|
||||||
m map[string]*SpCookie
|
|
||||||
}{m: make(map[string]*SpCookie)}
|
|
||||||
|
|
||||||
func GetCookie(username, password, siteUrl string) (string, error) {
|
|
||||||
cookiesMap.Lock()
|
|
||||||
defer cookiesMap.Unlock()
|
|
||||||
spCookie, ok := cookiesMap.m[username]
|
|
||||||
if ok {
|
|
||||||
if !spCookie.IsExpire() {
|
|
||||||
log.Debugln("sp use old cookie.")
|
|
||||||
return spCookie.Cookie, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Debugln("fetch new cookie")
|
|
||||||
ca := New(username, password, siteUrl)
|
|
||||||
tokenConf, err := ca.Cookies()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
spCookie = &SpCookie{
|
|
||||||
Cookie: cookie.ToString([]*http.Cookie{&tokenConf.RtFa, &tokenConf.FedAuth}),
|
|
||||||
expire: time.Now().Add(time.Hour * 12),
|
|
||||||
}
|
|
||||||
cookiesMap.m[username] = spCookie
|
|
||||||
return spCookie.Cookie, nil
|
|
||||||
}
|
|
@ -1,206 +0,0 @@
|
|||||||
// Package odrvcookie can fetch authentication cookies for a sharepoint webdav endpoint
|
|
||||||
package odrvcookie
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/xml"
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CookieAuth hold the authentication information
|
|
||||||
// These are username and password as well as the authentication endpoint
|
|
||||||
type CookieAuth struct {
|
|
||||||
user string
|
|
||||||
pass string
|
|
||||||
endpoint string
|
|
||||||
}
|
|
||||||
|
|
||||||
// CookieResponse contains the requested cookies
|
|
||||||
type CookieResponse struct {
|
|
||||||
RtFa http.Cookie
|
|
||||||
FedAuth http.Cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuccessResponse hold a response from the sharepoint webdav
|
|
||||||
type SuccessResponse struct {
|
|
||||||
XMLName xml.Name `xml:"Envelope"`
|
|
||||||
Succ SuccessResponseBody `xml:"Body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SuccessResponseBody is the body of a success response, it holds the token
|
|
||||||
type SuccessResponseBody struct {
|
|
||||||
XMLName xml.Name
|
|
||||||
Type string `xml:"RequestSecurityTokenResponse>TokenType"`
|
|
||||||
Created time.Time `xml:"RequestSecurityTokenResponse>Lifetime>Created"`
|
|
||||||
Expires time.Time `xml:"RequestSecurityTokenResponse>Lifetime>Expires"`
|
|
||||||
Token string `xml:"RequestSecurityTokenResponse>RequestedSecurityToken>BinarySecurityToken"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// reqString is a template that gets populated with the user data in order to retrieve a "BinarySecurityToken"
|
|
||||||
const reqString = `<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
|
|
||||||
xmlns:a="http://www.w3.org/2005/08/addressing"
|
|
||||||
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
|
|
||||||
<s:Header>
|
|
||||||
<a:Action s:mustUnderstand="1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
|
|
||||||
<a:ReplyTo>
|
|
||||||
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
|
|
||||||
</a:ReplyTo>
|
|
||||||
<a:To s:mustUnderstand="1">{{ .LoginUrl }}</a:To>
|
|
||||||
<o:Security s:mustUnderstand="1"
|
|
||||||
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
|
||||||
<o:UsernameToken>
|
|
||||||
<o:Username>{{ .Username }}</o:Username>
|
|
||||||
<o:Password>{{ .Password }}</o:Password>
|
|
||||||
</o:UsernameToken>
|
|
||||||
</o:Security>
|
|
||||||
</s:Header>
|
|
||||||
<s:Body>
|
|
||||||
<t:RequestSecurityToken xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust">
|
|
||||||
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
|
|
||||||
<a:EndpointReference>
|
|
||||||
<a:Address>{{ .Address }}</a:Address>
|
|
||||||
</a:EndpointReference>
|
|
||||||
</wsp:AppliesTo>
|
|
||||||
<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</t:KeyType>
|
|
||||||
<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</t:RequestType>
|
|
||||||
<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>
|
|
||||||
</t:RequestSecurityToken>
|
|
||||||
</s:Body>
|
|
||||||
</s:Envelope>`
|
|
||||||
|
|
||||||
// New creates a new CookieAuth struct
|
|
||||||
func New(pUser, pPass, pEndpoint string) CookieAuth {
|
|
||||||
retStruct := CookieAuth{
|
|
||||||
user: pUser,
|
|
||||||
pass: pPass,
|
|
||||||
endpoint: pEndpoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
return retStruct
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cookies creates a CookieResponse. It fetches the auth token and then
|
|
||||||
// retrieves the Cookies
|
|
||||||
func (ca *CookieAuth) Cookies() (CookieResponse, error) {
|
|
||||||
spToken, err := ca.getSPToken()
|
|
||||||
if err != nil {
|
|
||||||
return CookieResponse{}, err
|
|
||||||
}
|
|
||||||
return ca.getSPCookie(spToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *CookieAuth) getSPCookie(conf *SuccessResponse) (CookieResponse, error) {
|
|
||||||
spRoot, err := url.Parse(ca.endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return CookieResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse("https://" + spRoot.Host + "/_forms/default.aspx?wa=wsignin1.0")
|
|
||||||
if err != nil {
|
|
||||||
return CookieResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// To authenticate with davfs or anything else we need two cookies (rtFa and FedAuth)
|
|
||||||
// In order to get them we use the token we got earlier and a cookieJar
|
|
||||||
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
|
|
||||||
if err != nil {
|
|
||||||
return CookieResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{
|
|
||||||
Jar: jar,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send the previously aquired Token as a Post parameter
|
|
||||||
if _, err = client.Post(u.String(), "text/xml", strings.NewReader(conf.Succ.Token)); err != nil {
|
|
||||||
return CookieResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cookieResponse := CookieResponse{}
|
|
||||||
for _, cookie := range jar.Cookies(u) {
|
|
||||||
if (cookie.Name == "rtFa") || (cookie.Name == "FedAuth") {
|
|
||||||
switch cookie.Name {
|
|
||||||
case "rtFa":
|
|
||||||
cookieResponse.RtFa = *cookie
|
|
||||||
case "FedAuth":
|
|
||||||
cookieResponse.FedAuth = *cookie
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cookieResponse, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var loginUrlsMap = map[string]string{
|
|
||||||
"com": "https://login.microsoftonline.com",
|
|
||||||
"cn": "https://login.chinacloudapi.cn",
|
|
||||||
"us": "https://login.microsoftonline.us",
|
|
||||||
"de": "https://login.microsoftonline.de",
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLoginUrl(endpoint string) (string, error) {
|
|
||||||
spRoot, err := url.Parse(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
domains := strings.Split(spRoot.Host, ".")
|
|
||||||
tld := domains[len(domains)-1]
|
|
||||||
loginUrl, ok := loginUrlsMap[tld]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("tld %s is not supported", tld)
|
|
||||||
}
|
|
||||||
return loginUrl + "/extSTS.srf", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ca *CookieAuth) getSPToken() (*SuccessResponse, error) {
|
|
||||||
loginUrl, err := getLoginUrl(ca.endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
reqData := map[string]string{
|
|
||||||
"Username": ca.user,
|
|
||||||
"Password": ca.pass,
|
|
||||||
"Address": ca.endpoint,
|
|
||||||
"LoginUrl": loginUrl,
|
|
||||||
}
|
|
||||||
|
|
||||||
t := template.Must(template.New("authXML").Parse(reqString))
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
if err := t.Execute(buf, reqData); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the first request which gives us an auth token for the sharepoint service
|
|
||||||
// With this token we can authenticate on the login page and save the returned cookies
|
|
||||||
req, err := http.NewRequest("POST", loginUrl, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
respBuf := bytes.Buffer{}
|
|
||||||
respBuf.ReadFrom(resp.Body)
|
|
||||||
s := respBuf.Bytes()
|
|
||||||
|
|
||||||
var conf SuccessResponse
|
|
||||||
err = xml.Unmarshal(s, &conf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &conf, err
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package webdav
|
|
||||||
|
|
||||||
import "github.com/Xhofe/alist/model"
|
|
||||||
|
|
||||||
func isSharePoint(account *model.Account) bool {
|
|
||||||
return account.InternalType == "sharepoint"
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
package webdav
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
|
||||||
"github.com/Xhofe/alist/drivers/webdav/odrvcookie"
|
|
||||||
"github.com/Xhofe/alist/model"
|
|
||||||
"github.com/Xhofe/alist/pkg/gowebdav"
|
|
||||||
"github.com/Xhofe/alist/utils"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (driver WebDav) NewClient(account *model.Account) *gowebdav.Client {
|
|
||||||
c := gowebdav.NewClient(account.SiteUrl, account.Username, account.Password)
|
|
||||||
if isSharePoint(account) {
|
|
||||||
cookie, err := odrvcookie.GetCookie(account.Username, account.Password, account.SiteUrl)
|
|
||||||
if err == nil {
|
|
||||||
c.SetInterceptor(func(method string, rq *http.Request) {
|
|
||||||
rq.Header.Del("Authorization")
|
|
||||||
rq.Header.Set("Cookie", cookie)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (driver WebDav) WebDavPath(path string) string {
|
|
||||||
path = utils.ParsePath(path)
|
|
||||||
path = strings.TrimPrefix(path, "/")
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
base.RegisterDriver(&WebDav{})
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user