Compare commits
153 Commits
refactor/o
...
v3.33.0
Author | SHA1 | Date | |
---|---|---|---|
cf08aa3668 | |||
9c84b6596f | |||
022e0ca292 | |||
88947f6676 | |||
b07ddfbc13 | |||
9a0a63d34c | |||
195c869272 | |||
bdfc1591bd | |||
82222840fe | |||
45e009a22c | |||
ac68079a76 | |||
2a17d0c2cd | |||
6f6a8e6dfc | |||
7d9ecba99c | |||
ae6984714d | |||
d0f88bd1cb | |||
f8b1f87a5f | |||
71e4e1ab6e | |||
7e6522c81e | |||
94a80bccfe | |||
e66abb3f58 | |||
742335f80e | |||
f1979a8bbc | |||
1f835502ba | |||
424ab2d0c0 | |||
858ba19670 | |||
0c7e47a76c | |||
53926d5cd0 | |||
47f4b05517 | |||
6d85f1b0c0 | |||
e49fda3e2a | |||
da5e35578a | |||
812f58ae6d | |||
9bd3c87bcc | |||
c82866975e | |||
aef952ae68 | |||
9222510d8d | |||
d88b54d98a | |||
85a28d9822 | |||
4f7761fe2c | |||
a8c900d09e | |||
8bccb69e8d | |||
0f29a811bf | |||
442c2f77ea | |||
ce06f394f1 | |||
e3e790f461 | |||
f0e8c0e886 | |||
86b35ae5cf | |||
4930f85b90 | |||
85fe65951d | |||
1381e8fb27 | |||
292bbe94ee | |||
bb6747de4e | |||
555ef0eb1a | |||
bff56ffd0f | |||
34b73b94f7 | |||
434892f135 | |||
e6e2d03ba1 | |||
28bb3f6310 | |||
fb729c1846 | |||
4448e08f5b | |||
8020d42b10 | |||
9d5fb7f595 | |||
126cfe9f93 | |||
fd96a7ccf4 | |||
03b9b9a119 | |||
03dbdfc0dd | |||
2683621ed7 | |||
be537aa49b | |||
6f742a68cf | |||
97a4b8321d | |||
8c432d3339 | |||
ff25e51f80 | |||
88831b5d5a | |||
b97c9173af | |||
207c7e05fe | |||
7db27e6da8 | |||
b5cc90cb5a | |||
8a427ddc49 | |||
c36644a172 | |||
45b1ff4a24 | |||
a4a9675616 | |||
8531b23382 | |||
2c15349ce4 | |||
5afd65b65c | |||
e2434029f9 | |||
bdf7abe717 | |||
2c8d003c2e | |||
a006f57637 | |||
be5d94cd11 | |||
977b3cf9ab | |||
182aacd309 | |||
57bac9e0d2 | |||
478470f609 | |||
6b8f35e7fa | |||
697a0ed2d3 | |||
299bfb4d7b | |||
3eca38e599 | |||
ab216ed170 | |||
e91c42c9dc | |||
54f7b21a73 | |||
de56f926cf | |||
6d4ab57a0e | |||
734d4b0354 | |||
74b20dedc3 | |||
83c2269330 | |||
296be88b5f | |||
026e944cbb | |||
8bdfc7ac8e | |||
e4a6b758dc | |||
66b7fe1e1b | |||
f475eb4401 | |||
b99e709bdb | |||
f4dcf4599c | |||
54e75d7287 | |||
d142fc3449 | |||
f23567199b | |||
1420492d81 | |||
b88067ea2f | |||
d5f381ef6f | |||
68af284dad | |||
d26887d211 | |||
3f405de6a9 | |||
6100647310 | |||
34746e951c | |||
b6134dc515 | |||
d455a232ef | |||
fe34d30d17 | |||
0fbb986ba9 | |||
1280070438 | |||
d7f66138eb | |||
b2890f05ab | |||
7583c4d734 | |||
11a30c5044 | |||
de9647a5fa | |||
8d5283604c | |||
867accafd1 | |||
6fc6751463 | |||
f904596cbc | |||
3d51845f57 | |||
a7421d8fc2 | |||
55a14bc271 | |||
91f51f17d0 | |||
4355dae491 | |||
da1c7a4c23 | |||
769281bd40 | |||
3bbdd4fa89 | |||
68f440abdb | |||
65c5ec0c34 | |||
a6325967d0 | |||
4dff49470a | |||
cc86d6f3d1 | |||
c0f9c8ebaf |
44
.air.toml
Normal file
44
.air.toml
Normal file
@ -0,0 +1,44 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = ["server"]
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ."
|
||||
delay = 0
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
8
.github/workflows/auto_lang.yml
vendored
8
.github/workflows/auto_lang.yml
vendored
@ -20,22 +20,22 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: auto generate lang.json
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout alist
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: alist
|
||||
|
||||
- name: Checkout alist-web
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'alist-org/alist-web'
|
||||
ref: main
|
||||
|
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@ -15,17 +15,20 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Build
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: benjlevesque/short-sha@v2.2
|
||||
id: short-sha
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@ -39,7 +42,7 @@ jobs:
|
||||
bash build.sh dev
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: alist
|
||||
name: alist_${{ env.SHA }}
|
||||
path: dist
|
65
.github/workflows/build_docker.yml
vendored
65
.github/workflows/build_docker.yml
vendored
@ -3,6 +3,8 @@ name: build_docker
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
@ -10,45 +12,80 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build_docker:
|
||||
name: Build docker
|
||||
name: Build Docker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: xhofe/alist
|
||||
- name: Replace release with dev
|
||||
run: |
|
||||
sed -i 's/release/dev/g' Dockerfile
|
||||
|
||||
- name: Docker meta with ffmpeg
|
||||
id: meta-ffmpeg
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: xhofe/alist
|
||||
flavor: |
|
||||
suffix=-ffmpeg,onlatest=true
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 'stable'
|
||||
|
||||
- name: Build go binary
|
||||
run: bash build.sh dev docker-multiplatform
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
if: github.event_name == 'push'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: xhofe
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
file: Dockerfile.ci
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||
|
||||
- name: Replace dockerfile tag
|
||||
run: |
|
||||
sed -i -e "s/latest/main/g" Dockerfile.ffmpeg
|
||||
|
||||
- name: Build and push with ffmpeg
|
||||
id: docker_build_ffmpeg
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.ffmpeg
|
||||
push: ${{ github.event_name == 'push' }}
|
||||
tags: ${{ steps.meta-ffmpeg.outputs.tags }}
|
||||
labels: ${{ steps.meta-ffmpeg.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||
|
||||
build_docker_with_aria2:
|
||||
needs: build_docker
|
||||
name: Build docker with aria2
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push'
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: alist-org/with_aria2
|
||||
ref: main
|
||||
@ -66,4 +103,4 @@ jobs:
|
||||
with:
|
||||
github_token: ${{ secrets.MY_TOKEN }}
|
||||
branch: main
|
||||
repository: alist-org/with_aria2
|
||||
repository: alist-org/with_aria2
|
||||
|
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: npx changelogithub # or changelogithub@0.12 if ensure the stable result
|
||||
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
@ -21,12 +21,12 @@ jobs:
|
||||
prerelease: true
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
@ -53,7 +53,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: alist-org/desktop-release
|
||||
ref: main
|
||||
|
34
.github/workflows/release_android.yml
vendored
Normal file
34
.github/workflows/release_android.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: release_android
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
jobs:
|
||||
release_android:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release android
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: build/compress/*
|
42
.github/workflows/release_docker.yml
vendored
42
.github/workflows/release_docker.yml
vendored
@ -11,43 +11,71 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 'stable'
|
||||
|
||||
- name: Build go binary
|
||||
run: bash build.sh release docker-multiplatform
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: xhofe/alist
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: xhofe
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.ci
|
||||
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
|
||||
|
||||
- name: Docker meta with ffmpeg
|
||||
id: meta-ffmpeg
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: xhofe/alist
|
||||
flavor: |
|
||||
latest=true
|
||||
suffix=-ffmpeg,onlatest=true
|
||||
|
||||
- name: Build and push with ffmpeg
|
||||
id: docker_build_ffmpeg
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: Dockerfile.ffmpeg
|
||||
push: true
|
||||
tags: ${{ steps.meta-ffmpeg.outputs.tags }}
|
||||
labels: ${{ steps.meta-ffmpeg.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x
|
||||
|
||||
release_docker_with_aria2:
|
||||
needs: release_docker
|
||||
name: Release docker with aria2
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: alist-org/with_aria2
|
||||
ref: main
|
||||
|
6
.github/workflows/release_linux_musl.yml
vendored
6
.github/workflows/release_linux_musl.yml
vendored
@ -9,18 +9,18 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
6
.github/workflows/release_linux_musl_arm.yml
vendored
6
.github/workflows/release_linux_musl_arm.yml
vendored
@ -9,18 +9,18 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.20' ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -24,6 +24,7 @@ output/
|
||||
*.json
|
||||
/build
|
||||
/data/
|
||||
/tmp/
|
||||
/log/
|
||||
/lang/
|
||||
/daemon/
|
||||
|
19
Dockerfile
19
Dockerfile
@ -1,18 +1,23 @@
|
||||
FROM alpine:3.18 as builder
|
||||
FROM alpine:edge as builder
|
||||
LABEL stage=go-builder
|
||||
WORKDIR /app/
|
||||
RUN apk add --no-cache bash curl gcc git go musl-dev
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
COPY ./ ./
|
||||
RUN apk add --no-cache bash curl gcc git go musl-dev; \
|
||||
bash build.sh release docker
|
||||
RUN bash build.sh release docker
|
||||
|
||||
FROM alpine:3.18
|
||||
FROM alpine:edge
|
||||
LABEL MAINTAINER="i@nn.ci"
|
||||
VOLUME /opt/alist/data/
|
||||
WORKDIR /opt/alist/
|
||||
COPY --from=builder /app/bin/alist ./
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN apk add --no-cache bash ca-certificates su-exec tzdata; \
|
||||
chmod +x /entrypoint.sh
|
||||
RUN apk update && \
|
||||
apk upgrade --no-cache && \
|
||||
apk add --no-cache bash ca-certificates su-exec tzdata; \
|
||||
chmod +x /entrypoint.sh && \
|
||||
rm -rf /var/cache/apk/*
|
||||
ENV PUID=0 PGID=0 UMASK=022
|
||||
EXPOSE 5244 5245
|
||||
CMD [ "/entrypoint.sh" ]
|
||||
CMD [ "/entrypoint.sh" ]
|
16
Dockerfile.ci
Normal file
16
Dockerfile.ci
Normal file
@ -0,0 +1,16 @@
|
||||
FROM alpine:edge
|
||||
ARG TARGETPLATFORM
|
||||
LABEL MAINTAINER="i@nn.ci"
|
||||
VOLUME /opt/alist/data/
|
||||
WORKDIR /opt/alist/
|
||||
COPY /${TARGETPLATFORM}/alist ./
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN apk update && \
|
||||
apk upgrade --no-cache && \
|
||||
apk add --no-cache bash ca-certificates su-exec tzdata; \
|
||||
chmod +x /entrypoint.sh && \
|
||||
rm -rf /var/cache/apk/* && \
|
||||
/entrypoint.sh version
|
||||
ENV PUID=0 PGID=0 UMASK=022
|
||||
EXPOSE 5244 5245
|
||||
CMD [ "/entrypoint.sh" ]
|
4
Dockerfile.ffmpeg
Normal file
4
Dockerfile.ffmpeg
Normal file
@ -0,0 +1,4 @@
|
||||
FROM xhofe/alist:latest
|
||||
RUN apk update && \
|
||||
apk add --no-cache ffmpeg \
|
||||
rm -rf /var/cache/apk/*
|
13
README.md
13
README.md
@ -45,7 +45,7 @@ English | [中文](./README_cn.md)| [日本語](./README_ja.md) | [Contributing]
|
||||
|
||||
- [x] Multiple storages
|
||||
- [x] Local storage
|
||||
- [x] [Aliyundrive](https://www.aliyundrive.com/)
|
||||
- [x] [Aliyundrive](https://www.alipan.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/)
|
||||
@ -66,7 +66,8 @@ English | [中文](./README_cn.md)| [日本語](./README_ja.md) | [Contributing]
|
||||
- [x] [Quark](https://pan.quark.cn)
|
||||
- [x] [Thunder](https://pan.xunlei.com)
|
||||
- [x] [Lanzou](https://www.lanzou.com/)
|
||||
- [x] [Aliyundrive share](https://www.aliyundrive.com/)
|
||||
- [x] [ILanzou](https://www.ilanzou.com/)
|
||||
- [x] [Aliyundrive share](https://www.alipan.com/)
|
||||
- [x] [Google photo](https://photos.google.com/)
|
||||
- [x] [Mega.nz](https://mega.nz)
|
||||
- [x] [Baidu photo](https://photo.baidu.com/)
|
||||
@ -74,6 +75,8 @@ English | [中文](./README_cn.md)| [日本語](./README_ja.md) | [Contributing]
|
||||
- [x] [115](https://115.com/)
|
||||
- [X] Cloudreve
|
||||
- [x] [Dropbox](https://www.dropbox.com/)
|
||||
- [x] [FeijiPan](https://www.feijipan.com/)
|
||||
- [x] [dogecloud](https://www.dogecloud.com/product/oss)
|
||||
- [x] Easy to deploy and out-of-the-box
|
||||
- [x] File preview (PDF, markdown, code, plain text, ...)
|
||||
- [x] Image preview in gallery mode
|
||||
@ -112,9 +115,9 @@ https://alist.nn.ci/guide/sponsor.html
|
||||
|
||||
### Special sponsors
|
||||
|
||||
- [亚洲云 - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商](https://www.asiayun.com/aff/QQCOOQKZ) (sponsored Chinese API server)
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.pw/)
|
||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||
- [VidHub](https://okaapps.com/product/1659622164?ref=alist) - An elegant cloud video player within the Apple ecosystem. Support for iPhone, iPad, Mac, and Apple TV.
|
||||
- [亚洲云](https://www.asiayun.com/aff/QQCOOQKZ) - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商 (sponsored Chinese API server)
|
||||
- [找资源](https://zhaoziyuan.pw/) - 阿里云盘资源搜索引擎
|
||||
|
||||
## Contributors
|
||||
|
||||
|
13
README_cn.md
13
README_cn.md
@ -45,7 +45,7 @@
|
||||
|
||||
- [x] 多种存储
|
||||
- [x] 本地存储
|
||||
- [x] [阿里云盘](https://www.aliyundrive.com/)
|
||||
- [x] [阿里云盘](https://www.alipan.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/)
|
||||
@ -65,7 +65,8 @@
|
||||
- [x] [夸克网盘](https://pan.quark.cn)
|
||||
- [x] [迅雷网盘](https://pan.xunlei.com)
|
||||
- [x] [蓝奏云](https://www.lanzou.com/)
|
||||
- [x] [阿里云盘分享](https://www.aliyundrive.com/)
|
||||
- [x] [蓝奏云优享版](https://www.ilanzou.com/)
|
||||
- [x] [阿里云盘分享](https://www.alipan.com/)
|
||||
- [x] [谷歌相册](https://photos.google.com/)
|
||||
- [x] [Mega.nz](https://mega.nz)
|
||||
- [x] [一刻相册](https://photo.baidu.com/)
|
||||
@ -73,6 +74,8 @@
|
||||
- [x] [115](https://115.com/)
|
||||
- [X] Cloudreve
|
||||
- [x] [Dropbox](https://www.dropbox.com/)
|
||||
- [x] [飞机盘](https://www.feijipan.com/)
|
||||
- [x] [多吉云](https://www.dogecloud.com/product/oss)
|
||||
- [x] 部署方便,开箱即用
|
||||
- [x] 文件预览(PDF、markdown、代码、纯文本……)
|
||||
- [x] 画廊模式下的图像预览
|
||||
@ -110,9 +113,9 @@ AList 是一个开源软件,如果你碰巧喜欢这个项目,并希望我
|
||||
|
||||
### 特别赞助
|
||||
|
||||
- [亚洲云 - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商](https://www.asiayun.com/aff/QQCOOQKZ) (国内API服务器赞助)
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.pw/)
|
||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||
- [VidHub](https://zh.okaapps.com/product/1659622164?ref=alist) - 苹果生态下优雅的网盘视频播放器,iPhone,iPad,Mac,Apple TV全平台支持。
|
||||
- [亚洲云](https://www.asiayun.com/aff/QQCOOQKZ) - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商 (国内API服务器赞助)
|
||||
- [找资源](https://zhaoziyuan.pw/) - 阿里云盘资源搜索引擎
|
||||
|
||||
## 贡献者
|
||||
|
||||
|
13
README_ja.md
13
README_ja.md
@ -45,7 +45,7 @@
|
||||
|
||||
- [x] マルチストレージ
|
||||
- [x] ローカルストレージ
|
||||
- [x] [Aliyundrive](https://www.aliyundrive.com/)
|
||||
- [x] [Aliyundrive](https://www.alipan.com/)
|
||||
- [x] OneDrive / Sharepoint ([グローバル](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/)
|
||||
@ -66,7 +66,8 @@
|
||||
- [x] [Quark](https://pan.quark.cn)
|
||||
- [x] [Thunder](https://pan.xunlei.com)
|
||||
- [x] [Lanzou](https://www.lanzou.com/)
|
||||
- [x] [Aliyundrive share](https://www.aliyundrive.com/)
|
||||
- [x] [ILanzou](https://www.ilanzou.com/)
|
||||
- [x] [Aliyundrive share](https://www.alipan.com/)
|
||||
- [x] [Google photo](https://photos.google.com/)
|
||||
- [x] [Mega.nz](https://mega.nz)
|
||||
- [x] [Baidu photo](https://photo.baidu.com/)
|
||||
@ -74,6 +75,8 @@
|
||||
- [x] [115](https://115.com/)
|
||||
- [X] Cloudreve
|
||||
- [x] [Dropbox](https://www.dropbox.com/)
|
||||
- [x] [FeijiPan](https://www.feijipan.com/)
|
||||
- [x] [dogecloud](https://www.dogecloud.com/product/oss)
|
||||
- [x] デプロイが簡単で、すぐに使える
|
||||
- [x] ファイルプレビュー (PDF, マークダウン, コード, プレーンテキスト, ...)
|
||||
- [x] ギャラリーモードでの画像プレビュー
|
||||
@ -112,9 +115,9 @@ https://alist.nn.ci/guide/sponsor.html
|
||||
|
||||
### スペシャルスポンサー
|
||||
|
||||
- [亚洲云 - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商](https://www.asiayun.com/aff/QQCOOQKZ) (sponsored Chinese API server)
|
||||
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.pw/)
|
||||
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
|
||||
- [VidHub](https://okaapps.com/product/1659622164?ref=alist) - An elegant cloud video player within the Apple ecosystem. Support for iPhone, iPad, Mac, and Apple TV.
|
||||
- [亚洲云](https://www.asiayun.com/aff/QQCOOQKZ) - 高防服务器|服务器租用|福州高防|广东电信|香港服务器|美国服务器|海外服务器 - 国内靠谱的企业级云计算服务提供商 (sponsored Chinese API server)
|
||||
- [找资源](https://zhaoziyuan.pw/) - 阿里云盘资源搜索引擎
|
||||
|
||||
## コントリビューター
|
||||
|
||||
|
87
build.sh
87
build.sh
@ -49,6 +49,7 @@ BuildWinArm64() {
|
||||
export GOARCH=arm64
|
||||
export CC=$(pwd)/wrapper/zcc-arm64
|
||||
export CXX=$(pwd)/wrapper/zcxx-arm64
|
||||
export CGO_ENABLED=1
|
||||
go build -o "$1" -ldflags="$ldflags" -tags=jsoniter .
|
||||
}
|
||||
|
||||
@ -75,7 +76,7 @@ BuildDev() {
|
||||
export CGO_ENABLED=1
|
||||
go build -o ./dist/$appName-$os_arch -ldflags="$muslflags" -tags=jsoniter .
|
||||
done
|
||||
xgo -targets=windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
xgo -targets=windows/amd64,darwin/amd64,darwin/arm64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
mv alist-* dist
|
||||
cd dist
|
||||
cp ./alist-windows-amd64.exe ./alist-windows-amd64-upx.exe
|
||||
@ -84,10 +85,61 @@ BuildDev() {
|
||||
cat md5.txt
|
||||
}
|
||||
|
||||
PrepareBuildDocker() {
|
||||
echo "replace github.com/mattn/go-sqlite3 => github.com/leso-kn/go-sqlite3 v0.0.0-20230710125852-03158dc838ed" >>go.mod
|
||||
go get gorm.io/driver/sqlite@v1.4.4
|
||||
go mod download
|
||||
}
|
||||
|
||||
BuildDocker() {
|
||||
PrepareBuildDocker
|
||||
go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter .
|
||||
}
|
||||
|
||||
BuildDockerMultiplatform() {
|
||||
PrepareBuildDocker
|
||||
|
||||
BASE="https://musl.cc/"
|
||||
FILES=(x86_64-linux-musl-cross aarch64-linux-musl-cross i486-linux-musl-cross s390x-linux-musl-cross armv6-linux-musleabihf-cross armv7l-linux-musleabihf-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
|
||||
rm -f "${i}.tgz"
|
||||
done
|
||||
|
||||
docker_lflags="--extldflags '-static -fpic' $ldflags"
|
||||
export CGO_ENABLED=1
|
||||
|
||||
OS_ARCHES=(linux-amd64 linux-arm64 linux-386 linux-s390x)
|
||||
CGO_ARGS=(x86_64-linux-musl-gcc aarch64-linux-musl-gcc i486-linux-musl-gcc s390x-linux-musl-gcc)
|
||||
for i in "${!OS_ARCHES[@]}"; do
|
||||
os_arch=${OS_ARCHES[$i]}
|
||||
cgo_cc=${CGO_ARGS[$i]}
|
||||
os=${os_arch%%-*}
|
||||
arch=${os_arch##*-}
|
||||
export GOOS=$os
|
||||
export GOARCH=$arch
|
||||
export CC=${cgo_cc}
|
||||
echo "building for $os_arch"
|
||||
go build -o ./$os/$arch/alist -ldflags="$docker_lflags" -tags=jsoniter .
|
||||
done
|
||||
|
||||
DOCKER_ARM_ARCHES=(linux-arm/v6 linux-arm/v7)
|
||||
CGO_ARGS=(armv6-linux-musleabihf-gcc armv7l-linux-musleabihf-gcc)
|
||||
GO_ARM=(6 7)
|
||||
export GOOS=linux
|
||||
export GOARCH=arm
|
||||
for i in "${!DOCKER_ARM_ARCHES[@]}"; do
|
||||
docker_arch=${DOCKER_ARM_ARCHES[$i]}
|
||||
cgo_cc=${CGO_ARGS[$i]}
|
||||
export GOARM=${GO_ARM[$i]}
|
||||
export CC=${cgo_cc}
|
||||
echo "building for $docker_arch"
|
||||
go build -o ./${docker_arch%%-*}/${docker_arch##*-}/alist -ldflags="$docker_lflags" -tags=jsoniter .
|
||||
done
|
||||
}
|
||||
|
||||
BuildRelease() {
|
||||
rm -rf .git/
|
||||
mkdir -p "build"
|
||||
@ -159,6 +211,27 @@ BuildReleaseLinuxMuslArm() {
|
||||
done
|
||||
}
|
||||
|
||||
BuildReleaseAndroid() {
|
||||
rm -rf .git/
|
||||
mkdir -p "build"
|
||||
wget https://dl.google.com/android/repository/android-ndk-r26b-linux.zip
|
||||
unzip android-ndk-r26b-linux.zip
|
||||
rm android-ndk-r26b-linux.zip
|
||||
OS_ARCHES=(amd64 arm64 386 arm)
|
||||
CGO_ARGS=(x86_64-linux-android24-clang aarch64-linux-android24-clang i686-linux-android24-clang armv7a-linux-androideabi24-clang)
|
||||
for i in "${!OS_ARCHES[@]}"; do
|
||||
os_arch=${OS_ARCHES[$i]}
|
||||
cgo_cc=$(realpath android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/${CGO_ARGS[$i]})
|
||||
echo building for android-${os_arch}
|
||||
export GOOS=android
|
||||
export GOARCH=${os_arch##*-}
|
||||
export CC=${cgo_cc}
|
||||
export CGO_ENABLED=1
|
||||
go build -o ./build/$appName-android-$os_arch -ldflags="$ldflags" -tags=jsoniter .
|
||||
android-ndk-r26b/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip ./build/$appName-android-$os_arch
|
||||
done
|
||||
}
|
||||
|
||||
MakeRelease() {
|
||||
cd build
|
||||
mkdir compress
|
||||
@ -166,6 +239,11 @@ MakeRelease() {
|
||||
cp "$i" alist
|
||||
tar -czvf compress/"$i".tar.gz alist
|
||||
rm -f alist
|
||||
done
|
||||
for i in $(find . -type f -name "$appName-android-*"); do
|
||||
cp "$i" alist
|
||||
tar -czvf compress/"$i".tar.gz alist
|
||||
rm -f alist
|
||||
done
|
||||
for i in $(find . -type f -name "$appName-darwin-*"); do
|
||||
cp "$i" alist
|
||||
@ -187,6 +265,8 @@ if [ "$1" = "dev" ]; then
|
||||
FetchWebDev
|
||||
if [ "$2" = "docker" ]; then
|
||||
BuildDocker
|
||||
elif [ "$2" = "docker-multiplatform" ]; then
|
||||
BuildDockerMultiplatform
|
||||
else
|
||||
BuildDev
|
||||
fi
|
||||
@ -194,12 +274,17 @@ elif [ "$1" = "release" ]; then
|
||||
FetchWebRelease
|
||||
if [ "$2" = "docker" ]; then
|
||||
BuildDocker
|
||||
elif [ "$2" = "docker-multiplatform" ]; then
|
||||
BuildDockerMultiplatform
|
||||
elif [ "$2" = "linux_musl_arm" ]; then
|
||||
BuildReleaseLinuxMuslArm
|
||||
MakeRelease "md5-linux-musl-arm.txt"
|
||||
elif [ "$2" = "linux_musl" ]; then
|
||||
BuildReleaseLinuxMusl
|
||||
MakeRelease "md5-linux-musl.txt"
|
||||
elif [ "$2" = "android" ]; then
|
||||
BuildReleaseAndroid
|
||||
MakeRelease "md5-android.txt"
|
||||
else
|
||||
BuildRelease
|
||||
MakeRelease "md5.txt"
|
||||
|
@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -36,6 +37,7 @@ the address is defined in config file`,
|
||||
}
|
||||
bootstrap.InitOfflineDownloadTools()
|
||||
bootstrap.LoadStorages()
|
||||
bootstrap.InitTaskManager()
|
||||
if !flags.Debug && !flags.Dev {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
@ -49,7 +51,7 @@ the address is defined in config file`,
|
||||
httpSrv = &http.Server{Addr: httpBase, Handler: r}
|
||||
go func() {
|
||||
err := httpSrv.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
utils.Log.Fatalf("failed to start http: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
@ -60,7 +62,7 @@ the address is defined in config file`,
|
||||
httpsSrv = &http.Server{Addr: httpsBase, Handler: r}
|
||||
go func() {
|
||||
err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
utils.Log.Fatalf("failed to start https: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
@ -84,11 +86,32 @@ the address is defined in config file`,
|
||||
}
|
||||
}
|
||||
err = unixSrv.Serve(listener)
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
utils.Log.Fatalf("failed to start unix: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
s3r := gin.New()
|
||||
s3r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out))
|
||||
server.InitS3(s3r)
|
||||
if conf.Conf.S3.Port != -1 {
|
||||
s3Base := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.S3.Port)
|
||||
utils.Log.Infof("start S3 server @ %s", s3Base)
|
||||
go func() {
|
||||
var err error
|
||||
if conf.Conf.S3.SSL {
|
||||
httpsSrv = &http.Server{Addr: s3Base, Handler: s3r}
|
||||
err = httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
||||
}
|
||||
if !conf.Conf.S3.SSL {
|
||||
httpSrv = &http.Server{Addr: s3Base, Handler: s3r}
|
||||
err = httpSrv.ListenAndServe()
|
||||
}
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
utils.Log.Fatalf("failed to start s3 server: %s", err.Error())
|
||||
}
|
||||
}()
|
||||
}
|
||||
// Wait for interrupt signal to gracefully shutdown the server with
|
||||
// a timeout of 1 second.
|
||||
quit := make(chan os.Signal, 1)
|
||||
|
@ -63,8 +63,9 @@ func (d *Pan115) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
||||
if err := d.WaitLimit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
downloadInfo, err := d.client.
|
||||
DownloadWithUA(file.(*FileObj).PickCode, driver115.UA115Browser)
|
||||
var userAgent = args.Header.Get("User-Agent")
|
||||
downloadInfo, err := d.
|
||||
DownloadWithUA(file.(*FileObj).PickCode, userAgent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -6,17 +6,18 @@ import (
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
||||
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
||||
PageSize int64 `json:"page_size" type:"number" default:"56" help:"list api per page size of 115 driver"`
|
||||
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
|
||||
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
||||
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
||||
QRCodeSource string `json:"qrcode_source" type:"select" options:"web,android,ios,linux,mac,windows,tv" default:"linux" help:"select the QR code device, default linux"`
|
||||
PageSize int64 `json:"page_size" type:"number" default:"56" help:"list api per page size of 115 driver"`
|
||||
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
|
||||
driver.RootID
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "115 Cloud",
|
||||
DefaultRoot: "0",
|
||||
OnlyProxy: true,
|
||||
//OnlyProxy: true,
|
||||
//OnlyLocal: true,
|
||||
NoOverwriteUpload: true,
|
||||
}
|
||||
|
@ -5,12 +5,8 @@ import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/orzogc/fake115uploader/cipher"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@ -18,29 +14,35 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/SheltonZhu/115driver/pkg/driver"
|
||||
driver115 "github.com/SheltonZhu/115driver/pkg/driver"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
|
||||
driver115 "github.com/SheltonZhu/115driver/pkg/driver"
|
||||
crypto "github.com/gaoyb7/115drive-webdav/115"
|
||||
"github.com/orzogc/fake115uploader/cipher"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var UserAgent = driver.UA115Desktop
|
||||
var UserAgent = driver115.UA115Desktop
|
||||
|
||||
func (d *Pan115) login() error {
|
||||
var err error
|
||||
opts := []driver.Option{
|
||||
driver.UA(UserAgent),
|
||||
func(c *driver.Pan115Client) {
|
||||
opts := []driver115.Option{
|
||||
driver115.UA(UserAgent),
|
||||
func(c *driver115.Pan115Client) {
|
||||
c.Client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify})
|
||||
},
|
||||
}
|
||||
d.client = driver.New(opts...)
|
||||
cr := &driver.Credential{}
|
||||
d.client = driver115.New(opts...)
|
||||
cr := &driver115.Credential{}
|
||||
if d.Addition.QRCodeToken != "" {
|
||||
s := &driver.QRCodeSession{
|
||||
s := &driver115.QRCodeSession{
|
||||
UID: d.Addition.QRCodeToken,
|
||||
}
|
||||
if cr, err = d.client.QRCodeLogin(s); err != nil {
|
||||
if cr, err = d.client.QRCodeLoginWithApp(s, driver115.LoginApp(d.QRCodeSource)); err != nil {
|
||||
return errors.Wrap(err, "failed to login by qrcode")
|
||||
}
|
||||
d.Addition.Cookie = fmt.Sprintf("UID=%s;CID=%s;SEID=%s", cr.UID, cr.CID, cr.SEID)
|
||||
@ -59,7 +61,7 @@ func (d *Pan115) login() error {
|
||||
func (d *Pan115) getFiles(fileId string) ([]FileObj, error) {
|
||||
res := make([]FileObj, 0)
|
||||
if d.PageSize <= 0 {
|
||||
d.PageSize = driver.FileListLimit
|
||||
d.PageSize = driver115.FileListLimit
|
||||
}
|
||||
files, err := d.client.ListWithLimit(fileId, d.PageSize)
|
||||
if err != nil {
|
||||
@ -75,6 +77,61 @@ const (
|
||||
appVer = "2.0.3.6"
|
||||
)
|
||||
|
||||
func (c *Pan115) DownloadWithUA(pickCode, ua string) (*driver115.DownloadInfo, error) {
|
||||
key := crypto.GenerateKey()
|
||||
result := driver115.DownloadResp{}
|
||||
params, err := utils.Json.Marshal(map[string]string{"pickcode": pickCode})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := crypto.Encode(params, key)
|
||||
|
||||
bodyReader := strings.NewReader(url.Values{"data": []string{data}}.Encode())
|
||||
reqUrl := fmt.Sprintf("%s?t=%s", driver115.ApiDownloadGetUrl, driver115.Now().String())
|
||||
req, _ := http.NewRequest(http.MethodPost, reqUrl, bodyReader)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("Cookie", c.Cookie)
|
||||
req.Header.Set("User-Agent", ua)
|
||||
|
||||
resp, err := c.client.Client.GetClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := utils.Json.Unmarshal(body, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = result.Err(string(body)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bytes, err := crypto.Decode(string(result.EncodedData), key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
downloadInfo := driver115.DownloadData{}
|
||||
if err := utils.Json.Unmarshal(bytes, &downloadInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, info := range downloadInfo {
|
||||
if info.FileSize < 0 {
|
||||
return nil, driver115.ErrDownloadEmpty
|
||||
}
|
||||
info.Header = resp.Request.Header
|
||||
return info, nil
|
||||
}
|
||||
return nil, driver115.ErrUnexpected
|
||||
}
|
||||
|
||||
func (d *Pan115) rapidUpload(fileSize int64, fileName, dirID, preID, fileID string, stream model.FileStreamer) (*driver115.UploadInitResp, error) {
|
||||
var (
|
||||
ecdhCipher *cipher.EcdhCipher
|
||||
@ -249,7 +306,7 @@ func (d *Pan115) UploadByMultipart(params *driver115.UploadOSSParams, fileSize i
|
||||
go func(threadId int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errCh <- fmt.Errorf("Recovered in %v", r)
|
||||
errCh <- fmt.Errorf("recovered in %v", r)
|
||||
}
|
||||
}()
|
||||
for chunk := range chunksCh {
|
||||
|
112
drivers/115_share/driver.go
Normal file
112
drivers/115_share/driver.go
Normal file
@ -0,0 +1,112 @@
|
||||
package _115_share
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
driver115 "github.com/SheltonZhu/115driver/pkg/driver"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type Pan115Share struct {
|
||||
model.Storage
|
||||
Addition
|
||||
client *driver115.Pan115Client
|
||||
limiter *rate.Limiter
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Pan115Share) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Init(ctx context.Context) error {
|
||||
if d.LimitRate > 0 {
|
||||
d.limiter = rate.NewLimiter(rate.Limit(d.LimitRate), 1)
|
||||
}
|
||||
|
||||
return d.login()
|
||||
}
|
||||
|
||||
func (d *Pan115Share) WaitLimit(ctx context.Context) error {
|
||||
if d.limiter != nil {
|
||||
return d.limiter.Wait(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Pan115Share) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
if err := d.WaitLimit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]driver115.ShareFile, 0)
|
||||
fileResp, err := d.client.GetShareSnap(d.ShareCode, d.ReceiveCode, dir.GetID(), driver115.QueryLimit(int(d.PageSize)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, fileResp.Data.List...)
|
||||
total := fileResp.Data.Count
|
||||
count := len(fileResp.Data.List)
|
||||
for total > count {
|
||||
fileResp, err := d.client.GetShareSnap(
|
||||
d.ShareCode, d.ReceiveCode, dir.GetID(),
|
||||
driver115.QueryLimit(int(d.PageSize)), driver115.QueryOffset(count),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, fileResp.Data.List...)
|
||||
count += len(fileResp.Data.List)
|
||||
}
|
||||
|
||||
return utils.SliceConvert(files, transFunc)
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
if err := d.WaitLimit(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
downloadInfo, err := d.client.DownloadByShareCode(d.ShareCode, d.ReceiveCode, file.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Link{URL: downloadInfo.URL.URL}, nil
|
||||
}
|
||||
|
||||
func (d *Pan115Share) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Remove(ctx context.Context, obj model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Pan115Share) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Pan115Share)(nil)
|
34
drivers/115_share/meta.go
Normal file
34
drivers/115_share/meta.go
Normal file
@ -0,0 +1,34 @@
|
||||
package _115_share
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"`
|
||||
QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"`
|
||||
QRCodeSource string `json:"qrcode_source" type:"select" options:"web,android,ios,linux,mac,windows,tv" default:"linux" help:"select the QR code device, default linux"`
|
||||
PageSize int64 `json:"page_size" type:"number" default:"20" help:"list api per page size of 115 driver"`
|
||||
LimitRate float64 `json:"limit_rate" type:"number" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"`
|
||||
ShareCode string `json:"share_code" type:"text" required:"true" help:"share code of 115 share link"`
|
||||
ReceiveCode string `json:"receive_code" type:"text" required:"true" help:"receive code of 115 share link"`
|
||||
driver.RootID
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "115 Share",
|
||||
DefaultRoot: "",
|
||||
// OnlyProxy: true,
|
||||
// OnlyLocal: true,
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: true,
|
||||
NoUpload: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Pan115Share{}
|
||||
})
|
||||
}
|
111
drivers/115_share/utils.go
Normal file
111
drivers/115_share/utils.go
Normal file
@ -0,0 +1,111 @@
|
||||
package _115_share
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
driver115 "github.com/SheltonZhu/115driver/pkg/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ model.Obj = (*FileObj)(nil)
|
||||
|
||||
type FileObj struct {
|
||||
Size int64
|
||||
Sha1 string
|
||||
Utm time.Time
|
||||
FileName string
|
||||
isDir bool
|
||||
FileID string
|
||||
}
|
||||
|
||||
func (f *FileObj) CreateTime() time.Time {
|
||||
return f.Utm
|
||||
}
|
||||
|
||||
func (f *FileObj) GetHash() utils.HashInfo {
|
||||
return utils.NewHashInfo(utils.SHA1, f.Sha1)
|
||||
}
|
||||
|
||||
func (f *FileObj) GetSize() int64 {
|
||||
return f.Size
|
||||
}
|
||||
|
||||
func (f *FileObj) GetName() string {
|
||||
return f.FileName
|
||||
}
|
||||
|
||||
func (f *FileObj) ModTime() time.Time {
|
||||
return f.Utm
|
||||
}
|
||||
|
||||
func (f *FileObj) IsDir() bool {
|
||||
return f.isDir
|
||||
}
|
||||
|
||||
func (f *FileObj) GetID() string {
|
||||
return f.FileID
|
||||
}
|
||||
|
||||
func (f *FileObj) GetPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func transFunc(sf driver115.ShareFile) (model.Obj, error) {
|
||||
timeInt, err := strconv.ParseInt(sf.UpdateTime, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
utm = time.Unix(timeInt, 0)
|
||||
isDir = (sf.IsFile == 0)
|
||||
fileID = string(sf.FileID)
|
||||
)
|
||||
if isDir {
|
||||
fileID = string(sf.CategoryID)
|
||||
}
|
||||
return &FileObj{
|
||||
Size: int64(sf.Size),
|
||||
Sha1: sf.Sha1,
|
||||
Utm: utm,
|
||||
FileName: string(sf.FileName),
|
||||
isDir: isDir,
|
||||
FileID: fileID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var UserAgent = driver115.UA115Browser
|
||||
|
||||
func (d *Pan115Share) login() error {
|
||||
var err error
|
||||
opts := []driver115.Option{
|
||||
driver115.UA(UserAgent),
|
||||
}
|
||||
d.client = driver115.New(opts...)
|
||||
if _, err := d.client.GetShareSnap(d.ShareCode, d.ReceiveCode, ""); err != nil {
|
||||
return errors.Wrap(err, "failed to get share snap")
|
||||
}
|
||||
cr := &driver115.Credential{}
|
||||
if d.QRCodeToken != "" {
|
||||
s := &driver115.QRCodeSession{
|
||||
UID: d.QRCodeToken,
|
||||
}
|
||||
if cr, err = d.client.QRCodeLoginWithApp(s, driver115.LoginApp(d.QRCodeSource)); err != nil {
|
||||
return errors.Wrap(err, "failed to login by qrcode")
|
||||
}
|
||||
d.Cookie = fmt.Sprintf("UID=%s;CID=%s;SEID=%s", cr.UID, cr.CID, cr.SEID)
|
||||
d.QRCodeToken = ""
|
||||
} else if d.Cookie != "" {
|
||||
if err = cr.FromCookie(d.Cookie); err != nil {
|
||||
return errors.Wrap(err, "failed to login by cookies")
|
||||
}
|
||||
d.client.ImportCredential(cr)
|
||||
} else {
|
||||
return errors.New("missing cookie or qrcode account")
|
||||
}
|
||||
|
||||
return d.client.LoginCheck()
|
||||
}
|
@ -6,6 +6,13 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"golang.org/x/time/rate"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
@ -17,14 +24,12 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Pan123 struct {
|
||||
model.Storage
|
||||
Addition
|
||||
apiRateLimit sync.Map
|
||||
}
|
||||
|
||||
func (d *Pan123) Config() driver.Config {
|
||||
@ -232,6 +237,9 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
||||
return err
|
||||
}
|
||||
uploader := s3manager.NewUploader(s)
|
||||
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &resp.Data.Bucket,
|
||||
Key: &resp.Data.Key,
|
||||
@ -250,4 +258,11 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Pan123) APIRateLimit(api string) bool {
|
||||
limiter, _ := d.apiRateLimit.LoadOrStore(api,
|
||||
rate.NewLimiter(rate.Every(time.Millisecond*700), 1))
|
||||
ins := limiter.(*rate.Limiter)
|
||||
return ins.Allow()
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Pan123)(nil)
|
||||
|
@ -3,12 +3,18 @@ package _123
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
resty "github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
@ -18,7 +24,7 @@ const (
|
||||
Api = "https://www.123pan.com/api"
|
||||
AApi = "https://www.123pan.com/a/api"
|
||||
BApi = "https://www.123pan.com/b/api"
|
||||
MainApi = Api
|
||||
MainApi = BApi
|
||||
SignIn = MainApi + "/user/sign_in"
|
||||
Logout = MainApi + "/user/logout"
|
||||
UserInfo = MainApi + "/user/info"
|
||||
@ -37,6 +43,104 @@ const (
|
||||
//AuthKeySalt = "8-8D$sL8gPjom7bk#cY"
|
||||
)
|
||||
|
||||
func signPath(path string, os string, version string) (k string, v string) {
|
||||
table := []byte{'a', 'd', 'e', 'f', 'g', 'h', 'l', 'm', 'y', 'i', 'j', 'n', 'o', 'p', 'k', 'q', 'r', 's', 't', 'u', 'b', 'c', 'v', 'w', 's', 'z'}
|
||||
random := fmt.Sprintf("%.f", math.Round(1e7*rand.Float64()))
|
||||
now := time.Now().In(time.FixedZone("CST", 8*3600))
|
||||
timestamp := fmt.Sprint(now.Unix())
|
||||
nowStr := []byte(now.Format("200601021504"))
|
||||
for i := 0; i < len(nowStr); i++ {
|
||||
nowStr[i] = table[nowStr[i]-48]
|
||||
}
|
||||
timeSign := fmt.Sprint(crc32.ChecksumIEEE(nowStr))
|
||||
data := strings.Join([]string{timestamp, random, path, os, version, timeSign}, "|")
|
||||
dataSign := fmt.Sprint(crc32.ChecksumIEEE([]byte(data)))
|
||||
return timeSign, strings.Join([]string{timestamp, random, dataSign}, "-")
|
||||
}
|
||||
|
||||
func GetApi(rawUrl string) string {
|
||||
u, _ := url.Parse(rawUrl)
|
||||
query := u.Query()
|
||||
query.Add(signPath(u.Path, "web", "3"))
|
||||
u.RawQuery = query.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
||||
//func GetApi(url string) string {
|
||||
// vm := js.New()
|
||||
// vm.Set("url", url[22:])
|
||||
// r, err := vm.RunString(`
|
||||
// (function(e){
|
||||
// function A(t, e) {
|
||||
// e = 1 < arguments.length && void 0 !== e ? e : 10;
|
||||
// for (var n = function() {
|
||||
// for (var t = [], e = 0; e < 256; e++) {
|
||||
// for (var n = e, r = 0; r < 8; r++)
|
||||
// n = 1 & n ? 3988292384 ^ n >>> 1 : n >>> 1;
|
||||
// t[e] = n
|
||||
// }
|
||||
// return t
|
||||
// }(), r = function(t) {
|
||||
// t = t.replace(/\\r\\n/g, "\\n");
|
||||
// for (var e = "", n = 0; n < t.length; n++) {
|
||||
// var r = t.charCodeAt(n);
|
||||
// r < 128 ? e += String.fromCharCode(r) : e = 127 < r && r < 2048 ? (e += String.fromCharCode(r >> 6 | 192)) + String.fromCharCode(63 & r | 128) : (e = (e += String.fromCharCode(r >> 12 | 224)) + String.fromCharCode(r >> 6 & 63 | 128)) + String.fromCharCode(63 & r | 128)
|
||||
// }
|
||||
// return e
|
||||
// }(t), a = -1, i = 0; i < r.length; i++)
|
||||
// a = a >>> 8 ^ n[255 & (a ^ r.charCodeAt(i))];
|
||||
// return (a = (-1 ^ a) >>> 0).toString(e)
|
||||
// }
|
||||
//
|
||||
// function v(t) {
|
||||
// return (v = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(t) {
|
||||
// return typeof t
|
||||
// }
|
||||
// : function(t) {
|
||||
// return t && "function" == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype ? "symbol" : typeof t
|
||||
// }
|
||||
// )(t)
|
||||
// }
|
||||
//
|
||||
// for (p in a = Math.round(1e7 * Math.random()),
|
||||
// o = Math.round(((new Date).getTime() + 60 * (new Date).getTimezoneOffset() * 1e3 + 288e5) / 1e3).toString(),
|
||||
// m = ["a", "d", "e", "f", "g", "h", "l", "m", "y", "i", "j", "n", "o", "p", "k", "q", "r", "s", "t", "u", "b", "c", "v", "w", "s", "z"],
|
||||
// u = function(t, e, n) {
|
||||
// var r;
|
||||
// n = 2 < arguments.length && void 0 !== n ? n : 8;
|
||||
// return 0 === arguments.length ? null : (r = "object" === v(t) ? t : (10 === "".concat(t).length && (t = 1e3 * Number.parseInt(t)),
|
||||
// new Date(t)),
|
||||
// t += 6e4 * new Date(t).getTimezoneOffset(),
|
||||
// {
|
||||
// y: (r = new Date(t + 36e5 * n)).getFullYear(),
|
||||
// m: r.getMonth() + 1 < 10 ? "0".concat(r.getMonth() + 1) : r.getMonth() + 1,
|
||||
// d: r.getDate() < 10 ? "0".concat(r.getDate()) : r.getDate(),
|
||||
// h: r.getHours() < 10 ? "0".concat(r.getHours()) : r.getHours(),
|
||||
// f: r.getMinutes() < 10 ? "0".concat(r.getMinutes()) : r.getMinutes()
|
||||
// })
|
||||
// }(o),
|
||||
// h = u.y,
|
||||
// g = u.m,
|
||||
// l = u.d,
|
||||
// c = u.h,
|
||||
// u = u.f,
|
||||
// d = [h, g, l, c, u].join(""),
|
||||
// f = [],
|
||||
// d)
|
||||
// f.push(m[Number(d[p])]);
|
||||
// return h = A(f.join("")),
|
||||
// g = A("".concat(o, "|").concat(a, "|").concat(e, "|").concat("web", "|").concat("3", "|").concat(h)),
|
||||
// "".concat(h, "=").concat(o, "-").concat(a, "-").concat(g);
|
||||
// })(url)
|
||||
// `)
|
||||
// if err != nil {
|
||||
// fmt.Println(err)
|
||||
// return url
|
||||
// }
|
||||
// v, _ := r.Export().(string)
|
||||
// return url + "?" + v
|
||||
//}
|
||||
|
||||
func (d *Pan123) login() error {
|
||||
var body base.Json
|
||||
if utils.IsEmailFormat(d.Username) {
|
||||
@ -56,9 +160,9 @@ func (d *Pan123) login() error {
|
||||
SetHeaders(map[string]string{
|
||||
"origin": "https://www.123pan.com",
|
||||
"referer": "https://www.123pan.com/",
|
||||
"user-agent": "Dart/2.19(dart:io)",
|
||||
"platform": "android",
|
||||
"app-version": "36",
|
||||
"user-agent": "Dart/2.19(dart:io)-alist",
|
||||
"platform": "web",
|
||||
"app-version": "3",
|
||||
//"user-agent": base.UserAgent,
|
||||
}).
|
||||
SetBody(body).Post(SignIn)
|
||||
@ -93,9 +197,9 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r
|
||||
"origin": "https://www.123pan.com",
|
||||
"referer": "https://www.123pan.com/",
|
||||
"authorization": "Bearer " + d.AccessToken,
|
||||
"user-agent": "Dart/2.19(dart:io)",
|
||||
"platform": "android",
|
||||
"app-version": "36",
|
||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) alist-client",
|
||||
"platform": "web",
|
||||
"app-version": "3",
|
||||
//"user-agent": base.UserAgent,
|
||||
})
|
||||
if callback != nil {
|
||||
@ -109,7 +213,7 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r
|
||||
// return nil, err
|
||||
//}
|
||||
//req.SetQueryParam("auth-key", *authKey)
|
||||
res, err := req.Execute(method, url)
|
||||
res, err := req.Execute(method, GetApi(url))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -131,17 +235,27 @@ func (d *Pan123) request(url string, method string, callback base.ReqCallback, r
|
||||
func (d *Pan123) getFiles(parentId string) ([]File, error) {
|
||||
page := 1
|
||||
res := make([]File, 0)
|
||||
// 2024-02-06 fix concurrency by 123pan
|
||||
for {
|
||||
if !d.APIRateLimit(FileList) {
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
continue
|
||||
}
|
||||
var resp Files
|
||||
query := map[string]string{
|
||||
"driveId": "0",
|
||||
"limit": "100",
|
||||
"next": "0",
|
||||
"orderBy": d.OrderBy,
|
||||
"orderDirection": d.OrderDirection,
|
||||
"parentFileId": parentId,
|
||||
"trashed": "false",
|
||||
"Page": strconv.Itoa(page),
|
||||
"driveId": "0",
|
||||
"limit": "100",
|
||||
"next": "0",
|
||||
"orderBy": d.OrderBy,
|
||||
"orderDirection": d.OrderDirection,
|
||||
"parentFileId": parentId,
|
||||
"trashed": "false",
|
||||
"SearchData": "",
|
||||
"Page": strconv.Itoa(page),
|
||||
"OnlyLookAbnormalFile": "0",
|
||||
"event": "homeListFile",
|
||||
"operateType": "4",
|
||||
"inDirectSpace": "false",
|
||||
}
|
||||
_, err := d.request(FileList, http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
|
@ -8,18 +8,21 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/pkg/cron"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Yun139 struct {
|
||||
model.Storage
|
||||
Addition
|
||||
cron *cron.Cron
|
||||
Account string
|
||||
}
|
||||
|
||||
@ -35,61 +38,116 @@ func (d *Yun139) Init(ctx context.Context) error {
|
||||
if d.Authorization == "" {
|
||||
return fmt.Errorf("authorization is empty")
|
||||
}
|
||||
decode, err := base64.StdEncoding.DecodeString(d.Authorization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decodeStr := string(decode)
|
||||
splits := strings.Split(decodeStr, ":")
|
||||
if len(splits) < 2 {
|
||||
return fmt.Errorf("authorization is invalid, splits < 2")
|
||||
}
|
||||
d.Account = splits[1]
|
||||
_, err = d.post("/orchestration/personalCloud/user/v1.0/qryUserExternInfo", base.Json{
|
||||
"qryUserExternInfoReq": base.Json{
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
d.cron = cron.NewCron(time.Hour * 24 * 7)
|
||||
d.cron.Do(func() {
|
||||
err := d.refreshToken()
|
||||
if err != nil {
|
||||
log.Errorf("%+v", err)
|
||||
}
|
||||
})
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
if len(d.Addition.RootFolderID) == 0 {
|
||||
d.RootFolderID = "/"
|
||||
}
|
||||
return nil
|
||||
case MetaPersonal:
|
||||
if len(d.Addition.RootFolderID) == 0 {
|
||||
d.RootFolderID = "root"
|
||||
}
|
||||
fallthrough
|
||||
case MetaFamily:
|
||||
decode, err := base64.StdEncoding.DecodeString(d.Authorization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decodeStr := string(decode)
|
||||
splits := strings.Split(decodeStr, ":")
|
||||
if len(splits) < 2 {
|
||||
return fmt.Errorf("authorization is invalid, splits < 2")
|
||||
}
|
||||
d.Account = splits[1]
|
||||
_, err = d.post("/orchestration/personalCloud/user/v1.0/qryUserExternInfo", base.Json{
|
||||
"qryUserExternInfoReq": base.Json{
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
return err
|
||||
}, nil)
|
||||
return err
|
||||
default:
|
||||
return errs.NotImplement
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Yun139) Drop(ctx context.Context) error {
|
||||
if d.cron != nil {
|
||||
d.cron.Stop()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Yun139) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
if d.isFamily() {
|
||||
return d.familyGetFiles(dir.GetID())
|
||||
} else {
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
return d.personalGetFiles(dir.GetID())
|
||||
case MetaPersonal:
|
||||
return d.getFiles(dir.GetID())
|
||||
case MetaFamily:
|
||||
return d.familyGetFiles(dir.GetID())
|
||||
default:
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Yun139) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
u, err := d.getLink(file.GetID())
|
||||
var url string
|
||||
var err error
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
url, err = d.personalGetLink(file.GetID())
|
||||
case MetaPersonal:
|
||||
fallthrough
|
||||
case MetaFamily:
|
||||
url, err = d.getLink(file.GetID())
|
||||
default:
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.Link{URL: u}, nil
|
||||
return &model.Link{URL: url}, nil
|
||||
}
|
||||
|
||||
func (d *Yun139) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
data := base.Json{
|
||||
"createCatalogExtReq": base.Json{
|
||||
"parentCatalogID": parentDir.GetID(),
|
||||
"newCatalogName": dirName,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
var err error
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
data := base.Json{
|
||||
"parentFileId": parentDir.GetID(),
|
||||
"name": dirName,
|
||||
"description": "",
|
||||
"type": "folder",
|
||||
"fileRenameMode": "force_rename",
|
||||
}
|
||||
pathname := "/hcy/file/create"
|
||||
_, err = d.personalPost(pathname, data, nil)
|
||||
case MetaPersonal:
|
||||
data := base.Json{
|
||||
"createCatalogExtReq": base.Json{
|
||||
"parentCatalogID": parentDir.GetID(),
|
||||
"newCatalogName": dirName,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/catalog/v1.0/createCatalogExt"
|
||||
if d.isFamily() {
|
||||
data = base.Json{
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/catalog/v1.0/createCatalogExt"
|
||||
_, err = d.post(pathname, data, nil)
|
||||
case MetaFamily:
|
||||
data := base.Json{
|
||||
"cloudID": d.CloudID,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
@ -97,147 +155,198 @@ func (d *Yun139) MakeDir(ctx context.Context, parentDir model.Obj, dirName strin
|
||||
},
|
||||
"docLibName": dirName,
|
||||
}
|
||||
pathname = "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
|
||||
pathname := "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
|
||||
_, err = d.post(pathname, data, nil)
|
||||
default:
|
||||
err = errs.NotImplement
|
||||
}
|
||||
_, err := d.post(pathname, data, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Yun139) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||
if d.isFamily() {
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
data := base.Json{
|
||||
"fileIds": []string{srcObj.GetID()},
|
||||
"toParentFileId": dstDir.GetID(),
|
||||
}
|
||||
pathname := "/hcy/file/batchMove"
|
||||
_, err := d.personalPost(pathname, data, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srcObj, nil
|
||||
case MetaPersonal:
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if srcObj.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, srcObj.GetID())
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, srcObj.GetID())
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 3,
|
||||
"actionType": "304",
|
||||
"taskInfo": base.Json{
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
"newCatalogID": dstDir.GetID(),
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
_, err := d.post(pathname, data, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srcObj, nil
|
||||
default:
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if srcObj.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, srcObj.GetID())
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, srcObj.GetID())
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 3,
|
||||
"actionType": "304",
|
||||
"taskInfo": base.Json{
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
"newCatalogID": dstDir.GetID(),
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
_, err := d.post(pathname, data, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srcObj, nil
|
||||
}
|
||||
|
||||
func (d *Yun139) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
if d.isFamily() {
|
||||
return errs.NotImplement
|
||||
}
|
||||
var data base.Json
|
||||
var pathname string
|
||||
if srcObj.IsDir() {
|
||||
data = base.Json{
|
||||
"catalogID": srcObj.GetID(),
|
||||
"catalogName": newName,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
var err error
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
data := base.Json{
|
||||
"fileId": srcObj.GetID(),
|
||||
"name": newName,
|
||||
"description": "",
|
||||
}
|
||||
pathname = "/orchestration/personalCloud/catalog/v1.0/updateCatalogInfo"
|
||||
} else {
|
||||
data = base.Json{
|
||||
"contentID": srcObj.GetID(),
|
||||
"contentName": newName,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
pathname := "/hcy/file/update"
|
||||
_, err = d.personalPost(pathname, data, nil)
|
||||
case MetaPersonal:
|
||||
var data base.Json
|
||||
var pathname string
|
||||
if srcObj.IsDir() {
|
||||
data = base.Json{
|
||||
"catalogID": srcObj.GetID(),
|
||||
"catalogName": newName,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
pathname = "/orchestration/personalCloud/catalog/v1.0/updateCatalogInfo"
|
||||
} else {
|
||||
data = base.Json{
|
||||
"contentID": srcObj.GetID(),
|
||||
"contentName": newName,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
pathname = "/orchestration/personalCloud/content/v1.0/updateContentInfo"
|
||||
}
|
||||
pathname = "/orchestration/personalCloud/content/v1.0/updateContentInfo"
|
||||
_, err = d.post(pathname, data, nil)
|
||||
default:
|
||||
err = errs.NotImplement
|
||||
}
|
||||
_, err := d.post(pathname, data, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Yun139) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
if d.isFamily() {
|
||||
return errs.NotImplement
|
||||
}
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if srcObj.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, srcObj.GetID())
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, srcObj.GetID())
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 3,
|
||||
"actionType": 309,
|
||||
"taskInfo": base.Json{
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
"newCatalogID": dstDir.GetID(),
|
||||
var err error
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
data := base.Json{
|
||||
"fileIds": []string{srcObj.GetID()},
|
||||
"toParentFileId": dstDir.GetID(),
|
||||
}
|
||||
pathname := "/hcy/file/batchCopy"
|
||||
_, err := d.personalPost(pathname, data, nil)
|
||||
return err
|
||||
case MetaPersonal:
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if srcObj.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, srcObj.GetID())
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, srcObj.GetID())
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 3,
|
||||
"actionType": 309,
|
||||
"taskInfo": base.Json{
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
"newCatalogID": dstDir.GetID(),
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
_, err = d.post(pathname, data, nil)
|
||||
default:
|
||||
err = errs.NotImplement
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
_, err := d.post(pathname, data, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Yun139) Remove(ctx context.Context, obj model.Obj) error {
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if obj.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, obj.GetID())
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, obj.GetID())
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 2,
|
||||
"actionType": 201,
|
||||
"taskInfo": base.Json{
|
||||
"newCatalogID": "",
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
if d.isFamily() {
|
||||
data = base.Json{
|
||||
"catalogList": catalogInfoList,
|
||||
"contentList": contentInfoList,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
"sourceCatalogType": 1002,
|
||||
"taskType": 2,
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
data := base.Json{
|
||||
"fileIds": []string{obj.GetID()},
|
||||
}
|
||||
pathname = "/orchestration/familyCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
pathname := "/hcy/recyclebin/batchTrash"
|
||||
_, err := d.personalPost(pathname, data, nil)
|
||||
return err
|
||||
case MetaPersonal:
|
||||
fallthrough
|
||||
case MetaFamily:
|
||||
var contentInfoList []string
|
||||
var catalogInfoList []string
|
||||
if obj.IsDir() {
|
||||
catalogInfoList = append(catalogInfoList, obj.GetID())
|
||||
} else {
|
||||
contentInfoList = append(contentInfoList, obj.GetID())
|
||||
}
|
||||
data := base.Json{
|
||||
"createBatchOprTaskReq": base.Json{
|
||||
"taskType": 2,
|
||||
"actionType": 201,
|
||||
"taskInfo": base.Json{
|
||||
"newCatalogID": "",
|
||||
"contentInfoList": contentInfoList,
|
||||
"catalogInfoList": catalogInfoList,
|
||||
},
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
if d.isFamily() {
|
||||
data = base.Json{
|
||||
"catalogList": catalogInfoList,
|
||||
"contentList": contentInfoList,
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
"sourceCatalogType": 1002,
|
||||
"taskType": 2,
|
||||
}
|
||||
pathname = "/orchestration/familyCloud/batchOprTask/v1.0/createBatchOprTask"
|
||||
}
|
||||
_, err := d.post(pathname, data, nil)
|
||||
return err
|
||||
default:
|
||||
return errs.NotImplement
|
||||
}
|
||||
_, err := d.post(pathname, data, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
@ -257,94 +366,208 @@ func getPartSize(size int64) int64 {
|
||||
}
|
||||
|
||||
func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
data := base.Json{
|
||||
"manualRename": 2,
|
||||
"operation": 0,
|
||||
"fileCount": 1,
|
||||
"totalSize": 0, // 去除上传大小限制
|
||||
"uploadContentList": []base.Json{{
|
||||
"contentName": stream.GetName(),
|
||||
"contentSize": 0, // 去除上传大小限制
|
||||
// "digest": "5a3231986ce7a6b46e408612d385bafa"
|
||||
}},
|
||||
"parentCatalogID": dstDir.GetID(),
|
||||
"newCatalogName": "",
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/uploadAndDownload/v1.0/pcUploadFileRequest"
|
||||
if d.isFamily() {
|
||||
data = d.newJson(base.Json{
|
||||
"fileCount": 1,
|
||||
"manualRename": 2,
|
||||
"operation": 0,
|
||||
"path": "",
|
||||
"seqNo": "",
|
||||
"totalSize": 0,
|
||||
"uploadContentList": []base.Json{{
|
||||
"contentName": stream.GetName(),
|
||||
"contentSize": 0,
|
||||
// "digest": "5a3231986ce7a6b46e408612d385bafa"
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
var err error
|
||||
fullHash := stream.GetHash().GetHash(utils.SHA256)
|
||||
if len(fullHash) <= 0 {
|
||||
tmpF, err := stream.CacheFullInTempFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fullHash, err = utils.HashFile(utils.SHA256, tmpF)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// return errs.NotImplement
|
||||
data := base.Json{
|
||||
"contentHash": fullHash,
|
||||
"contentHashAlgorithm": "SHA256",
|
||||
"contentType": "application/octet-stream",
|
||||
"parallelUpload": false,
|
||||
"partInfos": []base.Json{{
|
||||
"parallelHashCtx": base.Json{
|
||||
"partOffset": 0,
|
||||
},
|
||||
"partNumber": 1,
|
||||
"partSize": stream.GetSize(),
|
||||
}},
|
||||
})
|
||||
pathname = "/orchestration/familyCloud/content/v1.0/getFileUploadURL"
|
||||
return errs.NotImplement
|
||||
}
|
||||
var resp UploadResp
|
||||
_, err := d.post(pathname, data, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Progress
|
||||
p := driver.NewProgress(stream.GetSize(), up)
|
||||
|
||||
var partSize = getPartSize(stream.GetSize())
|
||||
part := (stream.GetSize() + partSize - 1) / partSize
|
||||
if part == 0 {
|
||||
part = 1
|
||||
}
|
||||
for i := int64(0); i < part; i++ {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
"size": stream.GetSize(),
|
||||
"parentFileId": dstDir.GetID(),
|
||||
"name": stream.GetName(),
|
||||
"type": "file",
|
||||
"fileRenameMode": "auto_rename",
|
||||
}
|
||||
|
||||
start := i * partSize
|
||||
byteSize := stream.GetSize() - start
|
||||
if byteSize > partSize {
|
||||
byteSize = partSize
|
||||
}
|
||||
|
||||
limitReader := io.LimitReader(stream, byteSize)
|
||||
// Update Progress
|
||||
r := io.TeeReader(limitReader, p)
|
||||
req, err := http.NewRequest("POST", resp.Data.UploadResult.RedirectionURL, r)
|
||||
pathname := "/hcy/file/create"
|
||||
var resp PersonalUploadResp
|
||||
_, err = d.personalPost(pathname, data, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Data.Exist || resp.Data.RapidUpload {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Progress
|
||||
p := driver.NewProgress(stream.GetSize(), up)
|
||||
|
||||
// Update Progress
|
||||
r := io.TeeReader(stream, p)
|
||||
|
||||
req, err := http.NewRequest("PUT", resp.Data.PartInfos[0].UploadUrl, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.Header.Set("Content-Type", "text/plain;name="+unicode(stream.GetName()))
|
||||
req.Header.Set("contentSize", strconv.FormatInt(stream.GetSize(), 10))
|
||||
req.Header.Set("range", fmt.Sprintf("bytes=%d-%d", start, start+byteSize-1))
|
||||
req.Header.Set("uploadtaskID", resp.Data.UploadResult.UploadTaskID)
|
||||
req.Header.Set("rangeType", "0")
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("Content-Type", "application/octet-stream")
|
||||
req.Header.Set("Content-Length", fmt.Sprint(stream.GetSize()))
|
||||
req.Header.Set("Origin", "https://yun.139.com")
|
||||
req.Header.Set("Referer", "https://yun.139.com/")
|
||||
req.ContentLength = stream.GetSize()
|
||||
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_ = res.Body.Close()
|
||||
log.Debugf("%+v", res)
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
data = base.Json{
|
||||
"contentHash": fullHash,
|
||||
"contentHashAlgorithm": "SHA256",
|
||||
"fileId": resp.Data.FileId,
|
||||
"uploadId": resp.Data.UploadId,
|
||||
}
|
||||
_, err = d.personalPost("/hcy/file/complete", data, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case MetaPersonal:
|
||||
fallthrough
|
||||
case MetaFamily:
|
||||
data := base.Json{
|
||||
"manualRename": 2,
|
||||
"operation": 0,
|
||||
"fileCount": 1,
|
||||
"totalSize": 0, // 去除上传大小限制
|
||||
"uploadContentList": []base.Json{{
|
||||
"contentName": stream.GetName(),
|
||||
"contentSize": 0, // 去除上传大小限制
|
||||
// "digest": "5a3231986ce7a6b46e408612d385bafa"
|
||||
}},
|
||||
"parentCatalogID": dstDir.GetID(),
|
||||
"newCatalogName": "",
|
||||
"commonAccountInfo": base.Json{
|
||||
"account": d.Account,
|
||||
"accountType": 1,
|
||||
},
|
||||
}
|
||||
pathname := "/orchestration/personalCloud/uploadAndDownload/v1.0/pcUploadFileRequest"
|
||||
if d.isFamily() {
|
||||
// data = d.newJson(base.Json{
|
||||
// "fileCount": 1,
|
||||
// "manualRename": 2,
|
||||
// "operation": 0,
|
||||
// "path": "",
|
||||
// "seqNo": "",
|
||||
// "totalSize": 0,
|
||||
// "uploadContentList": []base.Json{{
|
||||
// "contentName": stream.GetName(),
|
||||
// "contentSize": 0,
|
||||
// // "digest": "5a3231986ce7a6b46e408612d385bafa"
|
||||
// }},
|
||||
// })
|
||||
// pathname = "/orchestration/familyCloud/content/v1.0/getFileUploadURL"
|
||||
return errs.NotImplement
|
||||
}
|
||||
var resp UploadResp
|
||||
_, err := d.post(pathname, data, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Progress
|
||||
p := driver.NewProgress(stream.GetSize(), up)
|
||||
|
||||
var partSize = getPartSize(stream.GetSize())
|
||||
part := (stream.GetSize() + partSize - 1) / partSize
|
||||
if part == 0 {
|
||||
part = 1
|
||||
}
|
||||
for i := int64(0); i < part; i++ {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
start := i * partSize
|
||||
byteSize := stream.GetSize() - start
|
||||
if byteSize > partSize {
|
||||
byteSize = partSize
|
||||
}
|
||||
|
||||
limitReader := io.LimitReader(stream, byteSize)
|
||||
// Update Progress
|
||||
r := io.TeeReader(limitReader, p)
|
||||
req, err := http.NewRequest("POST", resp.Data.UploadResult.RedirectionURL, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
req.Header.Set("Content-Type", "text/plain;name="+unicode(stream.GetName()))
|
||||
req.Header.Set("contentSize", strconv.FormatInt(stream.GetSize(), 10))
|
||||
req.Header.Set("range", fmt.Sprintf("bytes=%d-%d", start, start+byteSize-1))
|
||||
req.Header.Set("uploadtaskID", resp.Data.UploadResult.UploadTaskID)
|
||||
req.Header.Set("rangeType", "0")
|
||||
req.ContentLength = byteSize
|
||||
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = res.Body.Close()
|
||||
log.Debugf("%+v", res)
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return errs.NotImplement
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Yun139) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
switch d.Addition.Type {
|
||||
case MetaPersonalNew:
|
||||
var resp base.Json
|
||||
var uri string
|
||||
data := base.Json{
|
||||
"category": "video",
|
||||
"fileId": args.Obj.GetID(),
|
||||
}
|
||||
switch args.Method {
|
||||
case "video_preview":
|
||||
uri = "/hcy/videoPreview/getPreviewInfo"
|
||||
default:
|
||||
return nil, errs.NotSupport
|
||||
}
|
||||
_, err := d.personalPost(uri, data, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp["data"], nil
|
||||
default:
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Yun139)(nil)
|
||||
|
@ -9,7 +9,7 @@ type Addition struct {
|
||||
//Account string `json:"account" required:"true"`
|
||||
Authorization string `json:"authorization" type:"text" required:"true"`
|
||||
driver.RootID
|
||||
Type string `json:"type" type:"select" options:"personal,family" default:"personal"`
|
||||
Type string `json:"type" type:"select" options:"personal,family,personal_new" default:"personal"`
|
||||
CloudID string `json:"cloud_id"`
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,15 @@
|
||||
package _139
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
const (
|
||||
MetaPersonal string = "personal"
|
||||
MetaFamily string = "family"
|
||||
MetaPersonalNew string = "personal_new"
|
||||
)
|
||||
|
||||
type BaseResp struct {
|
||||
Success bool `json:"success"`
|
||||
Code string `json:"code"`
|
||||
@ -185,3 +195,51 @@ type QueryContentListResp struct {
|
||||
RecallContent interface{} `json:"recallContent"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type PersonalThumbnail struct {
|
||||
Style string `json:"style"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type PersonalFileItem struct {
|
||||
FileId string `json:"fileId"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
CreatedAt string `json:"createdAt"`
|
||||
UpdatedAt string `json:"updatedAt"`
|
||||
Thumbnails []PersonalThumbnail `json:"thumbnailUrls"`
|
||||
}
|
||||
|
||||
type PersonalListResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
Items []PersonalFileItem `json:"items"`
|
||||
NextPageCursor string `json:"nextPageCursor"`
|
||||
}
|
||||
}
|
||||
|
||||
type PersonalPartInfo struct {
|
||||
PartNumber int `json:"partNumber"`
|
||||
UploadUrl string `json:"uploadUrl"`
|
||||
}
|
||||
|
||||
type PersonalUploadResp struct {
|
||||
BaseResp
|
||||
Data struct {
|
||||
FileId string `json:"fileId"`
|
||||
PartInfos []PersonalPartInfo `json:"partInfos"`
|
||||
Exist bool `json:"exist"`
|
||||
RapidUpload bool `json:"rapidUpload"`
|
||||
UploadId string `json:"uploadId"`
|
||||
}
|
||||
}
|
||||
|
||||
type RefreshTokenResp struct {
|
||||
XMLName xml.Name `xml:"root"`
|
||||
Return string `xml:"return"`
|
||||
Token string `xml:"token"`
|
||||
Expiretime int32 `xml:"expiretime"`
|
||||
AccessToken string `xml:"accessToken"`
|
||||
Desc string `xml:"desc"`
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/pkg/utils/random"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -52,6 +53,32 @@ func getTime(t string) time.Time {
|
||||
return stamp
|
||||
}
|
||||
|
||||
func (d *Yun139) refreshToken() error {
|
||||
url := "https://aas.caiyun.feixin.10086.cn:443/tellin/authTokenRefresh.do"
|
||||
var resp RefreshTokenResp
|
||||
decode, err := base64.StdEncoding.DecodeString(d.Authorization)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
decodeStr := string(decode)
|
||||
splits := strings.Split(decodeStr, ":")
|
||||
reqBody := "<root><token>" + splits[2] + "</token><account>" + splits[1] + "</account><clienttype>656</clienttype></root>"
|
||||
_, err = base.RestyClient.R().
|
||||
ForceContentType("application/xml").
|
||||
SetBody(reqBody).
|
||||
SetResult(&resp).
|
||||
Post(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Return != "0" {
|
||||
return fmt.Errorf("failed to refresh token: %s", resp.Desc)
|
||||
}
|
||||
d.Authorization = base64.StdEncoding.EncodeToString([]byte(splits[0] + ":" + splits[1] + ":" + resp.Token))
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Yun139) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
url := "https://yun.139.com" + pathname
|
||||
req := base.RestyClient.R()
|
||||
@ -252,3 +279,154 @@ func unicode(str string) string {
|
||||
textUnquoted := textQuoted[1 : len(textQuoted)-1]
|
||||
return textUnquoted
|
||||
}
|
||||
|
||||
func (d *Yun139) personalRequest(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
url := "https://personal-kd-njs.yun.139.com" + pathname
|
||||
req := base.RestyClient.R()
|
||||
randStr := random.String(16)
|
||||
ts := time.Now().Format("2006-01-02 15:04:05")
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
body, err := utils.Json.Marshal(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sign := calSign(string(body), ts, randStr)
|
||||
svcType := "1"
|
||||
if d.isFamily() {
|
||||
svcType = "2"
|
||||
}
|
||||
req.SetHeaders(map[string]string{
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Authorization": "Basic " + d.Authorization,
|
||||
"Caller": "web",
|
||||
"Cms-Device": "default",
|
||||
"Mcloud-Channel": "1000101",
|
||||
"Mcloud-Client": "10701",
|
||||
"Mcloud-Route": "001",
|
||||
"Mcloud-Sign": fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
|
||||
"Mcloud-Version": "7.13.0",
|
||||
"Origin": "https://yun.139.com",
|
||||
"Referer": "https://yun.139.com/w/",
|
||||
"x-DeviceInfo": "||9|7.13.0|chrome|120.0.0.0|||windows 10||zh-CN|||",
|
||||
"x-huawei-channelSrc": "10000034",
|
||||
"x-inner-ntwk": "2",
|
||||
"x-m4c-caller": "PC",
|
||||
"x-m4c-src": "10002",
|
||||
"x-SvcType": svcType,
|
||||
"X-Yun-Api-Version": "v1",
|
||||
"X-Yun-App-Channel": "10000034",
|
||||
"X-Yun-Channel-Source": "10000034",
|
||||
"X-Yun-Client-Info": "||9|7.13.0|chrome|120.0.0.0|||windows 10||zh-CN|||dW5kZWZpbmVk||",
|
||||
"X-Yun-Module-Type": "100",
|
||||
"X-Yun-Svc-Type": "1",
|
||||
})
|
||||
|
||||
var e BaseResp
|
||||
req.SetResult(&e)
|
||||
res, err := req.Execute(method, url)
|
||||
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 (d *Yun139) personalPost(pathname string, data interface{}, resp interface{}) ([]byte, error) {
|
||||
return d.personalRequest(pathname, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(data)
|
||||
}, resp)
|
||||
}
|
||||
|
||||
func getPersonalTime(t string) time.Time {
|
||||
stamp, err := time.ParseInLocation("2006-01-02T15:04:05.999-07:00", t, utils.CNLoc)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return stamp
|
||||
}
|
||||
|
||||
func (d *Yun139) personalGetFiles(fileId string) ([]model.Obj, error) {
|
||||
files := make([]model.Obj, 0)
|
||||
nextPageCursor := ""
|
||||
for {
|
||||
data := base.Json{
|
||||
"imageThumbnailStyleList": []string{"Small", "Large"},
|
||||
"orderBy": "updated_at",
|
||||
"orderDirection": "DESC",
|
||||
"pageInfo": base.Json{
|
||||
"pageCursor": nextPageCursor,
|
||||
"pageSize": 100,
|
||||
},
|
||||
"parentFileId": fileId,
|
||||
}
|
||||
var resp PersonalListResp
|
||||
_, err := d.personalPost("/hcy/file/list", data, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nextPageCursor = resp.Data.NextPageCursor
|
||||
for _, item := range resp.Data.Items {
|
||||
var isFolder = (item.Type == "folder")
|
||||
var f model.Obj
|
||||
if isFolder {
|
||||
f = &model.Object{
|
||||
ID: item.FileId,
|
||||
Name: item.Name,
|
||||
Size: 0,
|
||||
Modified: getPersonalTime(item.UpdatedAt),
|
||||
Ctime: getPersonalTime(item.CreatedAt),
|
||||
IsFolder: isFolder,
|
||||
}
|
||||
} else {
|
||||
var Thumbnails = item.Thumbnails
|
||||
var ThumbnailUrl string
|
||||
if len(Thumbnails) > 0 {
|
||||
ThumbnailUrl = Thumbnails[len(Thumbnails)-1].Url
|
||||
}
|
||||
f = &model.ObjThumb{
|
||||
Object: model.Object{
|
||||
ID: item.FileId,
|
||||
Name: item.Name,
|
||||
Size: item.Size,
|
||||
Modified: getPersonalTime(item.UpdatedAt),
|
||||
Ctime: getPersonalTime(item.CreatedAt),
|
||||
IsFolder: isFolder,
|
||||
},
|
||||
Thumbnail: model.Thumbnail{Thumbnail: ThumbnailUrl},
|
||||
}
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
if len(nextPageCursor) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (d *Yun139) personalGetLink(fileId string) (string, error) {
|
||||
data := base.Json{
|
||||
"fileId": fileId,
|
||||
}
|
||||
res, err := d.personalPost("/hcy/file/getDownloadUrl",
|
||||
data, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var cdnUrl = jsoniter.Get(res, "data", "cdnUrl").ToString()
|
||||
if cdnUrl != "" {
|
||||
return cdnUrl, nil
|
||||
} else {
|
||||
return jsoniter.Get(res, "data", "url").ToString(), nil
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
@ -174,13 +175,13 @@ func (d *AListV3) Remove(ctx context.Context, obj model.Obj) error {
|
||||
}
|
||||
|
||||
func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
_, err := d.request("/fs/put", http.MethodPut, func(req *resty.Request) {
|
||||
_, err := d.requestWithTimeout("/fs/put", http.MethodPut, func(req *resty.Request) {
|
||||
req.SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())).
|
||||
SetHeader("Password", d.MetaPassword).
|
||||
SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).
|
||||
SetContentLength(true).
|
||||
SetBody(io.ReadCloser(stream))
|
||||
})
|
||||
}, time.Hour*6)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package alist_v3
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
@ -56,3 +57,33 @@ func (d *AListV3) request(api, method string, callback base.ReqCallback, retry .
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *AListV3) requestWithTimeout(api, method string, callback base.ReqCallback, timeout time.Duration, retry ...bool) ([]byte, error) {
|
||||
url := d.Address + "/api" + api
|
||||
client := base.NewRestyClient().SetTimeout(timeout)
|
||||
req := client.R()
|
||||
req.SetHeader("Authorization", d.Token)
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("[alist_v3] response body: %s", res.String())
|
||||
if res.StatusCode() >= 400 {
|
||||
return nil, fmt.Errorf("request failed, status: %s", res.Status())
|
||||
}
|
||||
code := utils.Json.Get(res.Body(), "code").ToInt()
|
||||
if code != 200 {
|
||||
if (code == 401 || code == 403) && !utils.IsBool(retry...) {
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.requestWithTimeout(api, method, callback, timeout, true)
|
||||
}
|
||||
return nil, fmt.Errorf("request failed,code: %d, message: %s", code, utils.Json.Get(res.Body(), "message").ToString())
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ func (d *AliDrive) Init(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
// get driver id
|
||||
res, err, _ := d.request("https://api.aliyundrive.com/v2/user/get", http.MethodPost, nil, nil)
|
||||
res, err, _ := d.request("https://api.alipan.com/v2/user/get", http.MethodPost, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -106,7 +106,7 @@ func (d *AliDrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs
|
||||
"file_id": file.GetID(),
|
||||
"expire_sec": 14400,
|
||||
}
|
||||
res, err, _ := d.request("https://api.aliyundrive.com/v2/file/get_download_url", http.MethodPost, func(req *resty.Request) {
|
||||
res, err, _ := d.request("https://api.alipan.com/v2/file/get_download_url", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(data)
|
||||
}, nil)
|
||||
if err != nil {
|
||||
@ -114,14 +114,14 @@ func (d *AliDrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs
|
||||
}
|
||||
return &model.Link{
|
||||
Header: http.Header{
|
||||
"Referer": []string{"https://www.aliyundrive.com/"},
|
||||
"Referer": []string{"https://www.alipan.com/"},
|
||||
},
|
||||
URL: utils.Json.Get(res, "url").ToString(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *AliDrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
_, err, _ := d.request("https://api.aliyundrive.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
||||
_, err, _ := d.request("https://api.alipan.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"check_name_mode": "refuse",
|
||||
"drive_id": d.DriveId,
|
||||
@ -139,7 +139,7 @@ func (d *AliDrive) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
}
|
||||
|
||||
func (d *AliDrive) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
_, err, _ := d.request("https://api.aliyundrive.com/v3/file/update", http.MethodPost, func(req *resty.Request) {
|
||||
_, err, _ := d.request("https://api.alipan.com/v3/file/update", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"check_name_mode": "refuse",
|
||||
"drive_id": d.DriveId,
|
||||
@ -156,7 +156,7 @@ func (d *AliDrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
}
|
||||
|
||||
func (d *AliDrive) Remove(ctx context.Context, obj model.Obj) error {
|
||||
_, err, _ := d.request("https://api.aliyundrive.com/v2/recyclebin/trash", http.MethodPost, func(req *resty.Request) {
|
||||
_, err, _ := d.request("https://api.alipan.com/v2/recyclebin/trash", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"drive_id": d.DriveId,
|
||||
"file_id": obj.GetID(),
|
||||
@ -216,7 +216,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, streamer model.Fil
|
||||
}
|
||||
|
||||
var resp UploadResp
|
||||
_, err, e := d.request("https://api.aliyundrive.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
||||
_, err, e := d.request("https://api.alipan.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(reqBody)
|
||||
}, &resp)
|
||||
|
||||
@ -270,7 +270,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, streamer model.Fil
|
||||
n, _ := io.NewSectionReader(localFile, o.Int64(), 8).Read(buf[:8])
|
||||
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
||||
|
||||
_, err, e := d.request("https://api.aliyundrive.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
||||
_, err, e := d.request("https://api.alipan.com/adrive/v2/file/createWithFolders", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(reqBody)
|
||||
}, &resp)
|
||||
if err != nil && e.Code != "PreHashMatched" {
|
||||
@ -309,7 +309,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, streamer model.Fil
|
||||
}
|
||||
}
|
||||
var resp2 base.Json
|
||||
_, err, e = d.request("https://api.aliyundrive.com/v2/file/complete", http.MethodPost, func(req *resty.Request) {
|
||||
_, err, e = d.request("https://api.alipan.com/v2/file/complete", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"drive_id": d.DriveId,
|
||||
"file_id": resp.FileId,
|
||||
@ -334,10 +334,10 @@ func (d *AliDrive) Other(ctx context.Context, args model.OtherArgs) (interface{}
|
||||
}
|
||||
switch args.Method {
|
||||
case "doc_preview":
|
||||
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
|
||||
url = "https://api.alipan.com/v2/file/get_office_preview_url"
|
||||
data["access_token"] = d.AccessToken
|
||||
case "video_preview":
|
||||
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
|
||||
url = "https://api.alipan.com/v2/file/get_video_preview_play_info"
|
||||
data["category"] = "live_transcoding"
|
||||
data["url_expire_sec"] = 14400
|
||||
default:
|
||||
|
@ -26,7 +26,7 @@ func (d *AliDrive) createSession() error {
|
||||
state.retry = 0
|
||||
return fmt.Errorf("createSession failed after three retries")
|
||||
}
|
||||
_, err, _ := d.request("https://api.aliyundrive.com/users/v1/users/device/create_session", http.MethodPost, func(req *resty.Request) {
|
||||
_, err, _ := d.request("https://api.alipan.com/users/v1/users/device/create_session", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"deviceName": "samsung",
|
||||
"modelName": "SM-G9810",
|
||||
@ -42,7 +42,7 @@ func (d *AliDrive) createSession() error {
|
||||
}
|
||||
|
||||
// func (d *AliDrive) renewSession() error {
|
||||
// _, err, _ := d.request("https://api.aliyundrive.com/users/v1/users/device/renew_session", http.MethodPost, nil, nil)
|
||||
// _, err, _ := d.request("https://api.alipan.com/users/v1/users/device/renew_session", http.MethodPost, nil, nil)
|
||||
// return err
|
||||
// }
|
||||
|
||||
@ -58,7 +58,7 @@ func (d *AliDrive) sign() {
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
func (d *AliDrive) refreshToken() error {
|
||||
url := "https://auth.aliyundrive.com/v2/account/token"
|
||||
url := "https://auth.alipan.com/v2/account/token"
|
||||
var resp base.TokenResp
|
||||
var e RespErr
|
||||
_, err := base.RestyClient.R().
|
||||
@ -85,7 +85,7 @@ func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp i
|
||||
req := base.RestyClient.R()
|
||||
state, ok := global.Load(d.UserID)
|
||||
if !ok {
|
||||
if url == "https://api.aliyundrive.com/v2/user/get" {
|
||||
if url == "https://api.alipan.com/v2/user/get" {
|
||||
state = &State{}
|
||||
} else {
|
||||
return nil, fmt.Errorf("can't load user state, user_id: %s", d.UserID), RespErr{}
|
||||
@ -94,8 +94,8 @@ func (d *AliDrive) request(url, method string, callback base.ReqCallback, resp i
|
||||
req.SetHeaders(map[string]string{
|
||||
"Authorization": "Bearer\t" + d.AccessToken,
|
||||
"content-type": "application/json",
|
||||
"origin": "https://www.aliyundrive.com",
|
||||
"Referer": "https://aliyundrive.com/",
|
||||
"origin": "https://www.alipan.com",
|
||||
"Referer": "https://alipan.com/",
|
||||
"X-Signature": state.signature,
|
||||
"x-request-id": uuid.NewString(),
|
||||
"X-Canary": "client=Android,app=adrive,version=v4.1.0",
|
||||
@ -158,7 +158,7 @@ func (d *AliDrive) getFiles(fileId string) ([]File, error) {
|
||||
"video_thumbnail_process": "video/snapshot,t_0,f_jpg,ar_auto,w_300",
|
||||
"url_expire_sec": 14400,
|
||||
}
|
||||
_, err, _ := d.request("https://api.aliyundrive.com/v2/file/list", http.MethodPost, func(req *resty.Request) {
|
||||
_, err, _ := d.request("https://api.alipan.com/v2/file/list", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(data)
|
||||
}, &resp)
|
||||
|
||||
@ -172,7 +172,7 @@ func (d *AliDrive) getFiles(fileId string) ([]File, error) {
|
||||
}
|
||||
|
||||
func (d *AliDrive) batch(srcId, dstId string, url string) error {
|
||||
res, err, _ := d.request("https://api.aliyundrive.com/v3/batch", http.MethodPost, func(req *resty.Request) {
|
||||
res, err, _ := d.request("https://api.alipan.com/v3/batch", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"requests": []base.Json{
|
||||
{
|
||||
|
@ -93,7 +93,7 @@ func (d *AliyundriveOpen) link(ctx context.Context, file model.Obj) (*model.Link
|
||||
}
|
||||
url = utils.Json.Get(res, "streamsUrl", d.LIVPDownloadFormat).ToString()
|
||||
}
|
||||
exp := time.Hour
|
||||
exp := time.Minute
|
||||
return &model.Link{
|
||||
URL: url,
|
||||
Expiration: &exp,
|
||||
|
@ -36,7 +36,7 @@ var config = driver.Config{
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &AliyundriveOpen{
|
||||
base: "https://openapi.aliyundrive.com",
|
||||
base: "https://openapi.alipan.com",
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ func (d *AliyundriveShare) link(ctx context.Context, file model.Obj) (*model.Lin
|
||||
"share_id": d.ShareId,
|
||||
}
|
||||
var resp ShareLinkResp
|
||||
_, err := d.request("https://api.aliyundrive.com/v2/file/get_share_link_download_url", http.MethodPost, func(req *resty.Request) {
|
||||
_, err := d.request("https://api.alipan.com/v2/file/get_share_link_download_url", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetHeader(CanaryHeaderKey, CanaryHeaderValue).SetBody(data).SetResult(&resp)
|
||||
})
|
||||
if err != nil {
|
||||
@ -113,7 +113,7 @@ func (d *AliyundriveShare) link(ctx context.Context, file model.Obj) (*model.Lin
|
||||
}
|
||||
return &model.Link{
|
||||
Header: http.Header{
|
||||
"Referer": []string{"https://www.aliyundrive.com/"},
|
||||
"Referer": []string{"https://www.alipan.com/"},
|
||||
},
|
||||
URL: resp.DownloadUrl,
|
||||
}, nil
|
||||
@ -128,9 +128,9 @@ func (d *AliyundriveShare) Other(ctx context.Context, args model.OtherArgs) (int
|
||||
}
|
||||
switch args.Method {
|
||||
case "doc_preview":
|
||||
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
|
||||
url = "https://api.alipan.com/v2/file/get_office_preview_url"
|
||||
case "video_preview":
|
||||
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
|
||||
url = "https://api.alipan.com/v2/file/get_video_preview_play_info"
|
||||
data["category"] = "live_transcoding"
|
||||
default:
|
||||
return nil, errs.NotSupport
|
||||
|
@ -16,7 +16,7 @@ const (
|
||||
)
|
||||
|
||||
func (d *AliyundriveShare) refreshToken() error {
|
||||
url := "https://auth.aliyundrive.com/v2/account/token"
|
||||
url := "https://auth.alipan.com/v2/account/token"
|
||||
var resp base.TokenResp
|
||||
var e ErrorResp
|
||||
_, err := base.RestyClient.R().
|
||||
@ -47,7 +47,7 @@ func (d *AliyundriveShare) getShareToken() error {
|
||||
var resp ShareTokenResp
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).SetError(&e).SetBody(data).
|
||||
Post("https://api.aliyundrive.com/v2/share_link/get_share_token")
|
||||
Post("https://api.alipan.com/v2/share_link/get_share_token")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -116,7 +116,7 @@ func (d *AliyundriveShare) getFiles(fileId string) ([]File, error) {
|
||||
SetHeader("x-share-token", d.ShareToken).
|
||||
SetHeader(CanaryHeaderKey, CanaryHeaderValue).
|
||||
SetResult(&resp).SetError(&e).SetBody(data).
|
||||
Post("https://api.aliyundrive.com/adrive/v3/file/list")
|
||||
Post("https://api.alipan.com/adrive/v3/file/list")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package drivers
|
||||
|
||||
import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/115"
|
||||
_ "github.com/alist-org/alist/v3/drivers/115_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/123"
|
||||
_ "github.com/alist-org/alist/v3/drivers/123_link"
|
||||
_ "github.com/alist-org/alist/v3/drivers/123_share"
|
||||
@ -17,12 +18,14 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_netdisk"
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_photo"
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/chaoxing"
|
||||
_ "github.com/alist-org/alist/v3/drivers/cloudreve"
|
||||
_ "github.com/alist-org/alist/v3/drivers/crypt"
|
||||
_ "github.com/alist-org/alist/v3/drivers/dropbox"
|
||||
_ "github.com/alist-org/alist/v3/drivers/ftp"
|
||||
_ "github.com/alist-org/alist/v3/drivers/google_drive"
|
||||
_ "github.com/alist-org/alist/v3/drivers/google_photo"
|
||||
_ "github.com/alist-org/alist/v3/drivers/ilanzou"
|
||||
_ "github.com/alist-org/alist/v3/drivers/ipfs_api"
|
||||
_ "github.com/alist-org/alist/v3/drivers/lanzou"
|
||||
_ "github.com/alist-org/alist/v3/drivers/local"
|
||||
@ -34,6 +37,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
||||
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/quark_uc"
|
||||
_ "github.com/alist-org/alist/v3/drivers/quqi"
|
||||
_ "github.com/alist-org/alist/v3/drivers/s3"
|
||||
_ "github.com/alist-org/alist/v3/drivers/seafile"
|
||||
_ "github.com/alist-org/alist/v3/drivers/sftp"
|
||||
@ -45,6 +49,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/url_tree"
|
||||
_ "github.com/alist-org/alist/v3/drivers/uss"
|
||||
_ "github.com/alist-org/alist/v3/drivers/virtual"
|
||||
_ "github.com/alist-org/alist/v3/drivers/vtencent"
|
||||
_ "github.com/alist-org/alist/v3/drivers/webdav"
|
||||
_ "github.com/alist-org/alist/v3/drivers/weiyun"
|
||||
_ "github.com/alist-org/alist/v3/drivers/wopan"
|
||||
|
@ -33,6 +33,7 @@ func NewRestyClient() *resty.Client {
|
||||
client := resty.New().
|
||||
SetHeader("user-agent", UserAgent).
|
||||
SetRetryCount(3).
|
||||
SetRetryResetReaders(true).
|
||||
SetTimeout(DefaultTimeout).
|
||||
SetTLSClientConfig(&tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify})
|
||||
return client
|
||||
|
297
drivers/chaoxing/driver.go
Normal file
297
drivers/chaoxing/driver.go
Normal file
@ -0,0 +1,297 @@
|
||||
package chaoxing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/cron"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"google.golang.org/appengine/log"
|
||||
)
|
||||
|
||||
type ChaoXing struct {
|
||||
model.Storage
|
||||
Addition
|
||||
cron *cron.Cron
|
||||
config driver.Config
|
||||
conf Conf
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Config() driver.Config {
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *ChaoXing) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *ChaoXing) refreshCookie() error {
|
||||
cookie, err := d.Login()
|
||||
if err != nil {
|
||||
d.Status = err.Error()
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
d.Addition.Cookie = cookie
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Init(ctx context.Context) error {
|
||||
err := d.refreshCookie()
|
||||
if err != nil {
|
||||
log.Errorf(ctx, err.Error())
|
||||
}
|
||||
d.cron = cron.NewCron(time.Hour * 12)
|
||||
d.cron.Do(func() {
|
||||
err = d.refreshCookie()
|
||||
if err != nil {
|
||||
log.Errorf(ctx, err.Error())
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Drop(ctx context.Context) error {
|
||||
d.cron.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.GetFiles(dir.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||
return fileToObj(src), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
var resp DownResp
|
||||
ua := d.conf.ua
|
||||
fileId := strings.Split(file.GetID(), "$")[1]
|
||||
_, err := d.requestDownload("/screen/note_note/files/status/"+fileId, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetHeader("User-Agent", ua)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u := resp.Download
|
||||
return &model.Link{
|
||||
URL: u,
|
||||
Header: http.Header{
|
||||
"Cookie": []string{d.Cookie},
|
||||
"Referer": []string{d.conf.referer},
|
||||
"User-Agent": []string{ua},
|
||||
},
|
||||
Concurrency: 2,
|
||||
PartSize: 10 * utils.MB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"name": dirName,
|
||||
"pid": parentDir.GetID(),
|
||||
}
|
||||
var resp ListFileResp
|
||||
_, err := d.request("/pc/resource/addResourceFolder", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
msg := fmt.Sprintf("error:%s", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderIds": srcObj.GetID(),
|
||||
"targetId": dstDir.GetID(),
|
||||
}
|
||||
if !srcObj.IsDir() {
|
||||
query = map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"recIds": strings.Split(srcObj.GetID(), "$")[0],
|
||||
"targetId": dstDir.GetID(),
|
||||
}
|
||||
}
|
||||
var resp ListFileResp
|
||||
_, err := d.request("/pc/resource/moveResource", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !resp.Status {
|
||||
msg := fmt.Sprintf("error:%s", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderId": srcObj.GetID(),
|
||||
"name": newName,
|
||||
}
|
||||
path := "/pc/resource/updateResourceFolderName"
|
||||
if !srcObj.IsDir() {
|
||||
// path = "/pc/resource/updateResourceFileName"
|
||||
// query = map[string]string{
|
||||
// "bbsid": d.Addition.Bbsid,
|
||||
// "recIds": strings.Split(srcObj.GetID(), "$")[0],
|
||||
// "name": newName,
|
||||
// }
|
||||
return errors.New("此网盘不支持修改文件名")
|
||||
}
|
||||
var resp ListFileResp
|
||||
_, err := d.request(path, http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
msg := fmt.Sprintf("error:%s", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Remove(ctx context.Context, obj model.Obj) error {
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderIds": obj.GetID(),
|
||||
}
|
||||
path := "/pc/resource/deleteResourceFolder"
|
||||
var resp ListFileResp
|
||||
if !obj.IsDir() {
|
||||
path = "/pc/resource/deleteResourceFile"
|
||||
query = map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"recIds": strings.Split(obj.GetID(), "$")[0],
|
||||
}
|
||||
}
|
||||
_, err := d.request(path, http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
msg := fmt.Sprintf("error:%s", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
var resp UploadDataRsp
|
||||
_, err := d.request("https://noteyd.chaoxing.com/pc/files/getUploadConfig", http.MethodGet, func(req *resty.Request) {
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
return errors.New("get upload data error")
|
||||
}
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
filePart, err := writer.CreateFormFile("file", stream.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(filePart, stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.WriteField("_token", resp.Msg.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = writer.WriteField("puid", fmt.Sprintf("%d", resp.Msg.Puid))
|
||||
if err != nil {
|
||||
fmt.Println("Error writing param2 to request body:", err)
|
||||
return err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("POST", "https://pan-yz.chaoxing.com/upload", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
req.Header.Set("Content-Length", fmt.Sprintf("%d", body.Len()))
|
||||
resps, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resps.Body.Close()
|
||||
bodys, err := io.ReadAll(resps.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var fileRsp UploadFileDataRsp
|
||||
err = json.Unmarshal(bodys, &fileRsp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fileRsp.Msg != "success" {
|
||||
return errors.New(fileRsp.Msg)
|
||||
}
|
||||
uploadDoneParam := UploadDoneParam{Key: fileRsp.ObjectID, Cataid: "100000019", Param: fileRsp.Data}
|
||||
params, err := json.Marshal(uploadDoneParam)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"pid": dstDir.GetID(),
|
||||
"type": "yunpan",
|
||||
"params": url.QueryEscape("[" + string(params) + "]"),
|
||||
}
|
||||
var respd ListFileResp
|
||||
_, err = d.request("/pc/resource/addResource", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &respd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if respd.Result != 1 {
|
||||
msg := fmt.Sprintf("error:%v", resp.Msg)
|
||||
return errors.New(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*ChaoXing)(nil)
|
47
drivers/chaoxing/meta.go
Normal file
47
drivers/chaoxing/meta.go
Normal file
@ -0,0 +1,47 @@
|
||||
package chaoxing
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
// 此程序挂载的是超星小组网盘,需要代理才能使用;
|
||||
// 登录超星后进入个人空间,进入小组,新建小组,点击进去。
|
||||
// url中就有bbsid的参数,系统限制单文件大小2G,没有总容量限制
|
||||
type Addition struct {
|
||||
// 超星用户名及密码
|
||||
UserName string `json:"user_name" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
// 从自己新建的小组url里获取
|
||||
Bbsid string `json:"bbsid" required:"true"`
|
||||
driver.RootID
|
||||
// 可不填,程序会自动登录获取
|
||||
Cookie string `json:"cookie"`
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
ua string
|
||||
referer string
|
||||
api string
|
||||
DowloadApi string
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &ChaoXing{
|
||||
config: driver.Config{
|
||||
Name: "ChaoXingGroupDrive",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "-1",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
conf: Conf{
|
||||
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
|
||||
referer: "https://chaoxing.com/",
|
||||
api: "https://groupweb.chaoxing.com",
|
||||
DowloadApi: "https://noteyd.chaoxing.com",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
276
drivers/chaoxing/types.go
Normal file
276
drivers/chaoxing/types.go
Normal file
@ -0,0 +1,276 @@
|
||||
package chaoxing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type Resp struct {
|
||||
Result int `json:"result"`
|
||||
}
|
||||
|
||||
type UserAuth struct {
|
||||
GroupAuth struct {
|
||||
AddData int `json:"addData"`
|
||||
AddDataFolder int `json:"addDataFolder"`
|
||||
AddLebel int `json:"addLebel"`
|
||||
AddManager int `json:"addManager"`
|
||||
AddMem int `json:"addMem"`
|
||||
AddTopicFolder int `json:"addTopicFolder"`
|
||||
AnonymousAddReply int `json:"anonymousAddReply"`
|
||||
AnonymousAddTopic int `json:"anonymousAddTopic"`
|
||||
BatchOperation int `json:"batchOperation"`
|
||||
DelData int `json:"delData"`
|
||||
DelDataFolder int `json:"delDataFolder"`
|
||||
DelMem int `json:"delMem"`
|
||||
DelTopicFolder int `json:"delTopicFolder"`
|
||||
Dismiss int `json:"dismiss"`
|
||||
ExamEnc string `json:"examEnc"`
|
||||
GroupChat int `json:"groupChat"`
|
||||
IsShowCircleChatButton int `json:"isShowCircleChatButton"`
|
||||
IsShowCircleCloudButton int `json:"isShowCircleCloudButton"`
|
||||
IsShowCompanyButton int `json:"isShowCompanyButton"`
|
||||
Join int `json:"join"`
|
||||
MemberShowRankSet int `json:"memberShowRankSet"`
|
||||
ModifyDataFolder int `json:"modifyDataFolder"`
|
||||
ModifyExpose int `json:"modifyExpose"`
|
||||
ModifyName int `json:"modifyName"`
|
||||
ModifyShowPic int `json:"modifyShowPic"`
|
||||
ModifyTopicFolder int `json:"modifyTopicFolder"`
|
||||
ModifyVisibleState int `json:"modifyVisibleState"`
|
||||
OnlyMgrScoreSet int `json:"onlyMgrScoreSet"`
|
||||
Quit int `json:"quit"`
|
||||
SendNotice int `json:"sendNotice"`
|
||||
ShowActivityManage int `json:"showActivityManage"`
|
||||
ShowActivitySet int `json:"showActivitySet"`
|
||||
ShowAttentionSet int `json:"showAttentionSet"`
|
||||
ShowAutoClearStatus int `json:"showAutoClearStatus"`
|
||||
ShowBarcode int `json:"showBarcode"`
|
||||
ShowChatRoomSet int `json:"showChatRoomSet"`
|
||||
ShowCircleActivitySet int `json:"showCircleActivitySet"`
|
||||
ShowCircleSet int `json:"showCircleSet"`
|
||||
ShowCmem int `json:"showCmem"`
|
||||
ShowDataFolder int `json:"showDataFolder"`
|
||||
ShowDelReason int `json:"showDelReason"`
|
||||
ShowForward int `json:"showForward"`
|
||||
ShowGroupChat int `json:"showGroupChat"`
|
||||
ShowGroupChatSet int `json:"showGroupChatSet"`
|
||||
ShowGroupSquareSet int `json:"showGroupSquareSet"`
|
||||
ShowLockAddSet int `json:"showLockAddSet"`
|
||||
ShowManager int `json:"showManager"`
|
||||
ShowManagerIdentitySet int `json:"showManagerIdentitySet"`
|
||||
ShowNeedDelReasonSet int `json:"showNeedDelReasonSet"`
|
||||
ShowNotice int `json:"showNotice"`
|
||||
ShowOnlyManagerReplySet int `json:"showOnlyManagerReplySet"`
|
||||
ShowRank int `json:"showRank"`
|
||||
ShowRank2 int `json:"showRank2"`
|
||||
ShowRecycleBin int `json:"showRecycleBin"`
|
||||
ShowReplyByClass int `json:"showReplyByClass"`
|
||||
ShowReplyNeedCheck int `json:"showReplyNeedCheck"`
|
||||
ShowSignbanSet int `json:"showSignbanSet"`
|
||||
ShowSpeechSet int `json:"showSpeechSet"`
|
||||
ShowTopicCheck int `json:"showTopicCheck"`
|
||||
ShowTopicNeedCheck int `json:"showTopicNeedCheck"`
|
||||
ShowTransferSet int `json:"showTransferSet"`
|
||||
} `json:"groupAuth"`
|
||||
OperationAuth struct {
|
||||
Add int `json:"add"`
|
||||
AddTopicToFolder int `json:"addTopicToFolder"`
|
||||
ChoiceSet int `json:"choiceSet"`
|
||||
DelTopicFromFolder int `json:"delTopicFromFolder"`
|
||||
Delete int `json:"delete"`
|
||||
Reply int `json:"reply"`
|
||||
ScoreSet int `json:"scoreSet"`
|
||||
TopSet int `json:"topSet"`
|
||||
Update int `json:"update"`
|
||||
} `json:"operationAuth"`
|
||||
}
|
||||
|
||||
// 手机端学习通上传的文件的json内容(content字段)与网页端上传的有所不同
|
||||
// 网页端json `"puid": 54321, "size": 12345`
|
||||
// 手机端json `"puid": "54321". "size": "12345"`
|
||||
type int_str int
|
||||
|
||||
// json 字符串数字和纯数字解析
|
||||
func (ios *int_str) UnmarshalJSON(data []byte) error {
|
||||
intValue, err := strconv.Atoi(string(bytes.Trim(data, "\"")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*ios = int_str(intValue)
|
||||
return nil
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Cataid int `json:"cataid"`
|
||||
Cfid int `json:"cfid"`
|
||||
Content struct {
|
||||
Cfid int `json:"cfid"`
|
||||
Pid int `json:"pid"`
|
||||
FolderName string `json:"folderName"`
|
||||
ShareType int `json:"shareType"`
|
||||
Preview string `json:"preview"`
|
||||
Filetype string `json:"filetype"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
IsImg bool `json:"isImg"`
|
||||
ParentPath string `json:"parentPath"`
|
||||
Icon string `json:"icon"`
|
||||
Suffix string `json:"suffix"`
|
||||
Duration int `json:"duration"`
|
||||
Pantype string `json:"pantype"`
|
||||
Puid int_str `json:"puid"`
|
||||
Filepath string `json:"filepath"`
|
||||
Crc string `json:"crc"`
|
||||
Isfile bool `json:"isfile"`
|
||||
Residstr string `json:"residstr"`
|
||||
ObjectID string `json:"objectId"`
|
||||
Extinfo string `json:"extinfo"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Creator int `json:"creator"`
|
||||
ResTypeValue int `json:"resTypeValue"`
|
||||
UploadDateFormat string `json:"uploadDateFormat"`
|
||||
DisableOpt bool `json:"disableOpt"`
|
||||
DownPath string `json:"downPath"`
|
||||
Sort int `json:"sort"`
|
||||
Topsort int `json:"topsort"`
|
||||
Restype string `json:"restype"`
|
||||
Size int_str `json:"size"`
|
||||
UploadDate int64 `json:"uploadDate"`
|
||||
FileSize string `json:"fileSize"`
|
||||
Name string `json:"name"`
|
||||
FileID string `json:"fileId"`
|
||||
} `json:"content"`
|
||||
CreatorID int `json:"creatorId"`
|
||||
DesID string `json:"des_id"`
|
||||
ID int `json:"id"`
|
||||
Inserttime int64 `json:"inserttime"`
|
||||
Key string `json:"key"`
|
||||
Norder int `json:"norder"`
|
||||
OwnerID int `json:"ownerId"`
|
||||
OwnerType int `json:"ownerType"`
|
||||
Path string `json:"path"`
|
||||
Rid int `json:"rid"`
|
||||
Status int `json:"status"`
|
||||
Topsign int `json:"topsign"`
|
||||
}
|
||||
|
||||
type ListFileResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Result int `json:"result"`
|
||||
Status bool `json:"status"`
|
||||
UserAuth UserAuth `json:"userAuth"`
|
||||
List []File `json:"list"`
|
||||
}
|
||||
|
||||
type DownResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Duration int `json:"duration"`
|
||||
Download string `json:"download"`
|
||||
FileStatus string `json:"fileStatus"`
|
||||
URL string `json:"url"`
|
||||
Status bool `json:"status"`
|
||||
}
|
||||
|
||||
type UploadDataRsp struct {
|
||||
Result int `json:"result"`
|
||||
Msg struct {
|
||||
Puid int `json:"puid"`
|
||||
Token string `json:"token"`
|
||||
} `json:"msg"`
|
||||
}
|
||||
|
||||
type UploadFileDataRsp struct {
|
||||
Result bool `json:"result"`
|
||||
Msg string `json:"msg"`
|
||||
Crc string `json:"crc"`
|
||||
ObjectID string `json:"objectId"`
|
||||
Resid int64 `json:"resid"`
|
||||
Puid int `json:"puid"`
|
||||
Data struct {
|
||||
DisableOpt bool `json:"disableOpt"`
|
||||
Resid int64 `json:"resid"`
|
||||
Crc string `json:"crc"`
|
||||
Puid int `json:"puid"`
|
||||
Isfile bool `json:"isfile"`
|
||||
Pantype string `json:"pantype"`
|
||||
Size int `json:"size"`
|
||||
Name string `json:"name"`
|
||||
ObjectID string `json:"objectId"`
|
||||
Restype string `json:"restype"`
|
||||
UploadDate time.Time `json:"uploadDate"`
|
||||
ModifyDate time.Time `json:"modifyDate"`
|
||||
UploadDateFormat string `json:"uploadDateFormat"`
|
||||
Residstr string `json:"residstr"`
|
||||
Suffix string `json:"suffix"`
|
||||
Preview string `json:"preview"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Creator int `json:"creator"`
|
||||
Duration int `json:"duration"`
|
||||
IsImg bool `json:"isImg"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
Filetype string `json:"filetype"`
|
||||
Filepath string `json:"filepath"`
|
||||
Sort int `json:"sort"`
|
||||
Topsort int `json:"topsort"`
|
||||
ResTypeValue int `json:"resTypeValue"`
|
||||
Extinfo string `json:"extinfo"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type UploadDoneParam struct {
|
||||
Cataid string `json:"cataid"`
|
||||
Key string `json:"key"`
|
||||
Param struct {
|
||||
DisableOpt bool `json:"disableOpt"`
|
||||
Resid int64 `json:"resid"`
|
||||
Crc string `json:"crc"`
|
||||
Puid int `json:"puid"`
|
||||
Isfile bool `json:"isfile"`
|
||||
Pantype string `json:"pantype"`
|
||||
Size int `json:"size"`
|
||||
Name string `json:"name"`
|
||||
ObjectID string `json:"objectId"`
|
||||
Restype string `json:"restype"`
|
||||
UploadDate time.Time `json:"uploadDate"`
|
||||
ModifyDate time.Time `json:"modifyDate"`
|
||||
UploadDateFormat string `json:"uploadDateFormat"`
|
||||
Residstr string `json:"residstr"`
|
||||
Suffix string `json:"suffix"`
|
||||
Preview string `json:"preview"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Creator int `json:"creator"`
|
||||
Duration int `json:"duration"`
|
||||
IsImg bool `json:"isImg"`
|
||||
PreviewURL string `json:"previewUrl"`
|
||||
Filetype string `json:"filetype"`
|
||||
Filepath string `json:"filepath"`
|
||||
Sort int `json:"sort"`
|
||||
Topsort int `json:"topsort"`
|
||||
ResTypeValue int `json:"resTypeValue"`
|
||||
Extinfo string `json:"extinfo"`
|
||||
} `json:"param"`
|
||||
}
|
||||
|
||||
func fileToObj(f File) *model.Object {
|
||||
if len(f.Content.FolderName) > 0 {
|
||||
return &model.Object{
|
||||
ID: fmt.Sprintf("%d", f.ID),
|
||||
Name: f.Content.FolderName,
|
||||
Size: 0,
|
||||
Modified: time.UnixMilli(f.Inserttime),
|
||||
IsFolder: true,
|
||||
}
|
||||
}
|
||||
paserTime := time.UnixMilli(f.Content.UploadDate)
|
||||
return &model.Object{
|
||||
ID: fmt.Sprintf("%d$%s", f.ID, f.Content.FileID),
|
||||
Name: f.Content.Name,
|
||||
Size: int64(f.Content.Size),
|
||||
Modified: paserTime,
|
||||
IsFolder: false,
|
||||
}
|
||||
}
|
183
drivers/chaoxing/util.go
Normal file
183
drivers/chaoxing/util.go
Normal file
@ -0,0 +1,183 @@
|
||||
package chaoxing
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
func (d *ChaoXing) requestDownload(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
u := d.conf.DowloadApi + pathname
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"Cookie": d.Cookie,
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
var e Resp
|
||||
req.SetError(&e)
|
||||
res, err := req.Execute(method, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
u := d.conf.api + pathname
|
||||
if strings.Contains(pathname, "getUploadConfig") {
|
||||
u = pathname
|
||||
}
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"Cookie": d.Cookie,
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
var e Resp
|
||||
req.SetError(&e)
|
||||
res, err := req.Execute(method, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *ChaoXing) GetFiles(parent string) ([]File, error) {
|
||||
files := make([]File, 0)
|
||||
query := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderId": parent,
|
||||
"recType": "1",
|
||||
}
|
||||
var resp ListFileResp
|
||||
_, err := d.request("/pc/resource/getResourceList", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Result != 1 {
|
||||
msg := fmt.Sprintf("error code is:%d", resp.Result)
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
if len(resp.List) > 0 {
|
||||
files = append(files, resp.List...)
|
||||
}
|
||||
querys := map[string]string{
|
||||
"bbsid": d.Addition.Bbsid,
|
||||
"folderId": parent,
|
||||
"recType": "2",
|
||||
}
|
||||
var resps ListFileResp
|
||||
_, err = d.request("/pc/resource/getResourceList", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(querys)
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range resps.List {
|
||||
// 手机端超星上传的文件没有fileID字段,但ObjectID与fileID相同,可代替
|
||||
if file.Content.FileID == "" {
|
||||
file.Content.FileID = file.Content.ObjectID
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func EncryptByAES(message, key string) (string, error) {
|
||||
aesKey := []byte(key)
|
||||
plainText := []byte(message)
|
||||
block, err := aes.NewCipher(aesKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
iv := aesKey[:aes.BlockSize]
|
||||
mode := cipher.NewCBCEncrypter(block, iv)
|
||||
padding := aes.BlockSize - len(plainText)%aes.BlockSize
|
||||
paddedText := append(plainText, byte(padding))
|
||||
for i := 0; i < padding-1; i++ {
|
||||
paddedText = append(paddedText, byte(padding))
|
||||
}
|
||||
ciphertext := make([]byte, len(paddedText))
|
||||
mode.CryptBlocks(ciphertext, paddedText)
|
||||
encrypted := base64.StdEncoding.EncodeToString(ciphertext)
|
||||
return encrypted, nil
|
||||
}
|
||||
|
||||
func CookiesToString(cookies []*http.Cookie) string {
|
||||
var cookieStr string
|
||||
for _, cookie := range cookies {
|
||||
cookieStr += cookie.Name + "=" + cookie.Value + "; "
|
||||
}
|
||||
if len(cookieStr) > 2 {
|
||||
cookieStr = cookieStr[:len(cookieStr)-2]
|
||||
}
|
||||
return cookieStr
|
||||
}
|
||||
|
||||
func (d *ChaoXing) Login() (string, error) {
|
||||
transferKey := "u2oh6Vu^HWe4_AES"
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
uname, err := EncryptByAES(d.Addition.UserName, transferKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
password, err := EncryptByAES(d.Addition.Password, transferKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = writer.WriteField("uname", uname)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = writer.WriteField("password", password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = writer.WriteField("t", "true")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Create the request
|
||||
req, err := http.NewRequest("POST", "https://passport2.chaoxing.com/fanyalogin", body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
req.Header.Set("Content-Length", fmt.Sprintf("%d", body.Len()))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return CookiesToString(resp.Cookies()), nil
|
||||
|
||||
}
|
@ -124,6 +124,9 @@ func (d *Crypt) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([
|
||||
//filter illegal files
|
||||
continue
|
||||
}
|
||||
if !d.ShowHidden && strings.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
objRes := model.Object{
|
||||
Name: name,
|
||||
Size: 0,
|
||||
@ -145,6 +148,9 @@ func (d *Crypt) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([
|
||||
//filter illegal files
|
||||
continue
|
||||
}
|
||||
if !d.ShowHidden && strings.HasPrefix(name, ".") {
|
||||
continue
|
||||
}
|
||||
objRes := model.Object{
|
||||
Name: name,
|
||||
Size: size,
|
||||
|
@ -21,6 +21,8 @@ type Addition struct {
|
||||
FileNameEncoding string `json:"filename_encoding" type:"select" required:"true" options:"base64,base32,base32768" default:"base64" help:"for advanced user only!"`
|
||||
|
||||
Thumbnail bool `json:"thumbnail" required:"true" default:"false" help:"enable thumbnail which pre-generated under .thumbnails folder"`
|
||||
|
||||
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -45,7 +45,25 @@ func (d *Dropbox) Init(ctx context.Context) error {
|
||||
if result != query {
|
||||
return fmt.Errorf("failed to check user: %s", string(res))
|
||||
}
|
||||
return nil
|
||||
d.RootNamespaceId, err = d.GetRootNamespaceId(ctx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Dropbox) GetRootNamespaceId(ctx context.Context) (string, error) {
|
||||
res, err := d.request("/2/users/get_current_account", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(nil)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var currentAccountResp CurrentAccountResp
|
||||
err = utils.Json.Unmarshal(res, ¤tAccountResp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rootNamespaceId := currentAccountResp.RootInfo.RootNamespaceId
|
||||
return rootNamespaceId, nil
|
||||
}
|
||||
|
||||
func (d *Dropbox) Drop(ctx context.Context) error {
|
||||
|
@ -17,7 +17,8 @@ type Addition struct {
|
||||
ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"`
|
||||
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
||||
|
||||
AccessToken string
|
||||
AccessToken string
|
||||
RootNamespaceId string
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -23,6 +23,13 @@ type RefreshTokenErrorResp struct {
|
||||
ErrorDescription string `json:"error_description"`
|
||||
}
|
||||
|
||||
type CurrentAccountResp struct {
|
||||
RootInfo struct {
|
||||
RootNamespaceId string `json:"root_namespace_id"`
|
||||
HomeNamespaceId string `json:"home_namespace_id"`
|
||||
} `json:"root_info"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Tag string `json:".tag"`
|
||||
Name string `json:"name"`
|
||||
|
@ -46,12 +46,22 @@ func (d *Dropbox) refreshToken() error {
|
||||
func (d *Dropbox) request(uri, method string, callback base.ReqCallback, retry ...bool) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||||
if method == http.MethodPost {
|
||||
req.SetHeader("Content-Type", "application/json")
|
||||
if d.RootNamespaceId != "" {
|
||||
apiPathRootJson, err := utils.Json.MarshalToString(map[string]interface{}{
|
||||
".tag": "root",
|
||||
"root": d.RootNamespaceId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.SetHeader("Dropbox-API-Path-Root", apiPathRootJson)
|
||||
}
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
if method == http.MethodPost && req.Body != nil {
|
||||
req.SetHeader("Content-Type", "application/json")
|
||||
}
|
||||
var e ErrorResp
|
||||
req.SetError(&e)
|
||||
res, err := req.Execute(method, d.base+uri)
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
@ -44,7 +43,7 @@ func (d *GoogleDrive) refreshToken() error {
|
||||
gdsaFileThis := d.RefreshToken
|
||||
if gdsaFile.IsDir() {
|
||||
if len(d.ServiceAccountFileList) <= 0 {
|
||||
gdsaReadDir, gdsaDirErr := ioutil.ReadDir(d.RefreshToken)
|
||||
gdsaReadDir, gdsaDirErr := os.ReadDir(d.RefreshToken)
|
||||
if gdsaDirErr != nil {
|
||||
log.Error("read dir fail")
|
||||
return gdsaDirErr
|
||||
@ -76,7 +75,7 @@ func (d *GoogleDrive) refreshToken() error {
|
||||
}
|
||||
}
|
||||
|
||||
gdsaFileThisContent, err := ioutil.ReadFile(gdsaFileThis)
|
||||
gdsaFileThisContent, err := os.ReadFile(gdsaFileThis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -58,9 +58,33 @@ func (d *GooglePhoto) Link(ctx context.Context, file model.Obj, args model.LinkA
|
||||
URL: f.BaseURL + "=d",
|
||||
}, nil
|
||||
} else if strings.Contains(f.MimeType, "video/") {
|
||||
return &model.Link{
|
||||
URL: f.BaseURL + "=dv",
|
||||
}, nil
|
||||
var width, height int
|
||||
|
||||
fmt.Sscanf(f.MediaMetadata.Width, "%d", &width)
|
||||
fmt.Sscanf(f.MediaMetadata.Height, "%d", &height)
|
||||
|
||||
switch {
|
||||
// 1080P
|
||||
case width == 1920 && height == 1080:
|
||||
return &model.Link{
|
||||
URL: f.BaseURL + "=m37",
|
||||
}, nil
|
||||
// 720P
|
||||
case width == 1280 && height == 720:
|
||||
return &model.Link{
|
||||
URL: f.BaseURL + "=m22",
|
||||
}, nil
|
||||
// 360P
|
||||
case width == 640 && height == 360:
|
||||
return &model.Link{
|
||||
URL: f.BaseURL + "=m18",
|
||||
}, nil
|
||||
default:
|
||||
return &model.Link{
|
||||
URL: f.BaseURL + "=dv",
|
||||
}, nil
|
||||
}
|
||||
|
||||
}
|
||||
return &model.Link{}, nil
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ func (d *GooglePhoto) getMedia(id string) (MediaItem, error) {
|
||||
var resp MediaItem
|
||||
|
||||
query := map[string]string{
|
||||
"fields": "baseUrl,mimeType",
|
||||
"fields": "mediaMetadata,baseUrl,mimeType",
|
||||
}
|
||||
_, err := d.request(fmt.Sprintf("https://photoslibrary.googleapis.com/v1/mediaItems/%s", id), http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
|
381
drivers/ilanzou/driver.go
Normal file
381
drivers/ilanzou/driver.go
Normal file
@ -0,0 +1,381 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/foxxorcat/mopan-sdk-go"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ILanZou struct {
|
||||
model.Storage
|
||||
Addition
|
||||
|
||||
userID string
|
||||
account string
|
||||
upClient *resty.Client
|
||||
conf Conf
|
||||
config driver.Config
|
||||
}
|
||||
|
||||
func (d *ILanZou) Config() driver.Config {
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *ILanZou) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *ILanZou) Init(ctx context.Context) error {
|
||||
d.upClient = base.NewRestyClient().SetTimeout(time.Minute * 10)
|
||||
if d.UUID == "" {
|
||||
res, err := d.unproved("/getUuid", http.MethodGet, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.UUID = utils.Json.Get(res, "uuid").ToString()
|
||||
}
|
||||
res, err := d.proved("/user/account/map", http.MethodGet, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.userID = utils.Json.Get(res, "map", "userId").ToString()
|
||||
d.account = utils.Json.Get(res, "map", "account").ToString()
|
||||
log.Debugf("[ilanzou] init response: %s", res)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ILanZou) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ILanZou) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
offset := 1
|
||||
limit := 60
|
||||
var res []ListItem
|
||||
for {
|
||||
var resp ListResp
|
||||
_, err := d.proved("/record/file/list", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"type": "0",
|
||||
"folderId": dir.GetID(),
|
||||
"offset": strconv.Itoa(offset),
|
||||
"limit": strconv.Itoa(limit),
|
||||
}).SetResult(&resp)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, resp.List...)
|
||||
if resp.TotalPage <= resp.Offset {
|
||||
break
|
||||
}
|
||||
offset++
|
||||
}
|
||||
return utils.SliceConvert(res, func(f ListItem) (model.Obj, error) {
|
||||
updTime, err := time.ParseInLocation("2006-01-02 15:04:05", f.UpdTime, time.Local)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obj := model.Object{
|
||||
ID: strconv.FormatInt(f.FileId, 10),
|
||||
//Path: "",
|
||||
Name: f.FileName,
|
||||
Size: f.FileSize * 1024,
|
||||
Modified: updTime,
|
||||
Ctime: updTime,
|
||||
IsFolder: false,
|
||||
//HashInfo: utils.HashInfo{},
|
||||
}
|
||||
if f.FileType == 2 {
|
||||
obj.IsFolder = true
|
||||
obj.Size = 0
|
||||
obj.ID = strconv.FormatInt(f.FolderId, 10)
|
||||
obj.Name = f.FolderName
|
||||
}
|
||||
return &obj, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *ILanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
u, err := url.Parse(d.conf.base + "/" + d.conf.unproved + "/file/redirect")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query := u.Query()
|
||||
query.Set("uuid", d.UUID)
|
||||
query.Set("devType", "6")
|
||||
query.Set("devCode", d.UUID)
|
||||
query.Set("devModel", "chrome")
|
||||
query.Set("devVersion", d.conf.devVersion)
|
||||
query.Set("appVersion", "")
|
||||
ts, err := getTimestamp(d.conf.secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query.Set("timestamp", ts)
|
||||
query.Set("appToken", d.Token)
|
||||
query.Set("enable", "1")
|
||||
downloadId, err := mopan.AesEncrypt([]byte(fmt.Sprintf("%s|%s", file.GetID(), d.userID)), d.conf.secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query.Set("downloadId", hex.EncodeToString(downloadId))
|
||||
auth, err := mopan.AesEncrypt([]byte(fmt.Sprintf("%s|%d", file.GetID(), time.Now().UnixMilli())), d.conf.secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query.Set("auth", hex.EncodeToString(auth))
|
||||
u.RawQuery = query.Encode()
|
||||
realURL := u.String()
|
||||
// get the url after redirect
|
||||
res, err := base.NoRedirectClient.R().SetHeaders(map[string]string{
|
||||
//"Origin": d.conf.site,
|
||||
"Referer": d.conf.site + "/",
|
||||
}).Get(realURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res.StatusCode() == 302 {
|
||||
realURL = res.Header().Get("location")
|
||||
} else {
|
||||
return nil, fmt.Errorf("redirect failed, status: %d", res.StatusCode())
|
||||
}
|
||||
link := model.Link{URL: realURL}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (d *ILanZou) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
||||
res, err := d.proved("/file/folder/save", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"folderDesc": "",
|
||||
"folderId": parentDir.GetID(),
|
||||
"folderName": dirName,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.Object{
|
||||
ID: utils.Json.Get(res, "list", "0", "id").ToString(),
|
||||
//Path: "",
|
||||
Name: dirName,
|
||||
Size: 0,
|
||||
Modified: time.Now(),
|
||||
Ctime: time.Now(),
|
||||
IsFolder: true,
|
||||
//HashInfo: utils.HashInfo{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *ILanZou) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||
var fileIds, folderIds []string
|
||||
if srcObj.IsDir() {
|
||||
folderIds = []string{srcObj.GetID()}
|
||||
} else {
|
||||
fileIds = []string{srcObj.GetID()}
|
||||
}
|
||||
_, err := d.proved("/file/folder/move", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"folderIds": strings.Join(folderIds, ","),
|
||||
"fileIds": strings.Join(fileIds, ","),
|
||||
"targetId": dstDir.GetID(),
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return srcObj, nil
|
||||
}
|
||||
|
||||
func (d *ILanZou) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
||||
var err error
|
||||
if srcObj.IsDir() {
|
||||
_, err = d.proved("/file/folder/edit", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"folderDesc": "",
|
||||
"folderId": srcObj.GetID(),
|
||||
"folderName": newName,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
_, err = d.proved("/file/edit", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"fileDesc": "",
|
||||
"fileId": srcObj.GetID(),
|
||||
"fileName": newName,
|
||||
})
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.Object{
|
||||
ID: srcObj.GetID(),
|
||||
//Path: "",
|
||||
Name: newName,
|
||||
Size: srcObj.GetSize(),
|
||||
Modified: time.Now(),
|
||||
Ctime: srcObj.CreateTime(),
|
||||
IsFolder: srcObj.IsDir(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *ILanZou) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||
// TODO copy obj, optional
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *ILanZou) Remove(ctx context.Context, obj model.Obj) error {
|
||||
var fileIds, folderIds []string
|
||||
if obj.IsDir() {
|
||||
folderIds = []string{obj.GetID()}
|
||||
} else {
|
||||
fileIds = []string{obj.GetID()}
|
||||
}
|
||||
_, err := d.proved("/file/delete", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"folderIds": strings.Join(folderIds, ","),
|
||||
"fileIds": strings.Join(fileIds, ","),
|
||||
"status": 0,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
const DefaultPartSize = 1024 * 1024 * 8
|
||||
|
||||
func (d *ILanZou) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
||||
h := md5.New()
|
||||
// need to calculate md5 of the full content
|
||||
tempFile, err := stream.CacheFullInTempFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = tempFile.Close()
|
||||
}()
|
||||
if _, err = io.Copy(h, tempFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = tempFile.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
etag := hex.EncodeToString(h.Sum(nil))
|
||||
// get upToken
|
||||
res, err := d.proved("/7n/getUpToken", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"fileId": "",
|
||||
"fileName": stream.GetName(),
|
||||
"fileSize": stream.GetSize() / 1024,
|
||||
"folderId": dstDir.GetID(),
|
||||
"md5": etag,
|
||||
"type": 1,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upToken := utils.Json.Get(res, "upToken").ToString()
|
||||
now := time.Now()
|
||||
key := fmt.Sprintf("disk/%d/%d/%d/%s/%016d", now.Year(), now.Month(), now.Day(), d.account, now.UnixMilli())
|
||||
var token string
|
||||
if stream.GetSize() <= DefaultPartSize {
|
||||
res, err := d.upClient.R().SetMultipartFormData(map[string]string{
|
||||
"token": upToken,
|
||||
"key": key,
|
||||
"fname": stream.GetName(),
|
||||
}).SetMultipartField("file", stream.GetName(), stream.GetMimetype(), tempFile).
|
||||
Post("https://upload.qiniup.com/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token = utils.Json.Get(res.Body(), "token").ToString()
|
||||
} else {
|
||||
keyBase64 := base64.URLEncoding.EncodeToString([]byte(key))
|
||||
res, err := d.upClient.R().SetHeader("Authorization", "UpToken "+upToken).Post(fmt.Sprintf("https://upload.qiniup.com/buckets/%s/objects/%s/uploads", d.conf.bucket, keyBase64))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uploadId := utils.Json.Get(res.Body(), "uploadId").ToString()
|
||||
parts := make([]Part, 0)
|
||||
partNum := (stream.GetSize() + DefaultPartSize - 1) / DefaultPartSize
|
||||
for i := 1; i <= int(partNum); i++ {
|
||||
u := fmt.Sprintf("https://upload.qiniup.com/buckets/%s/objects/%s/uploads/%s/%d", d.conf.bucket, keyBase64, uploadId, i)
|
||||
res, err = d.upClient.R().SetHeader("Authorization", "UpToken "+upToken).SetBody(io.LimitReader(tempFile, DefaultPartSize)).Put(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
etag := utils.Json.Get(res.Body(), "etag").ToString()
|
||||
parts = append(parts, Part{
|
||||
PartNumber: i,
|
||||
ETag: etag,
|
||||
})
|
||||
}
|
||||
res, err = d.upClient.R().SetHeader("Authorization", "UpToken "+upToken).SetBody(base.Json{
|
||||
"fnmae": stream.GetName(),
|
||||
"parts": parts,
|
||||
}).Post(fmt.Sprintf("https://upload.qiniup.com/buckets/%s/objects/%s/uploads/%s", d.conf.bucket, keyBase64, uploadId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token = utils.Json.Get(res.Body(), "token").ToString()
|
||||
}
|
||||
// commit upload
|
||||
var resp UploadResultResp
|
||||
for i := 0; i < 10; i++ {
|
||||
_, err = d.unproved("/7n/results", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"tokenList": token,
|
||||
"tokenTime": time.Now().Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)"),
|
||||
}).SetResult(&resp)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.List) == 0 {
|
||||
return nil, fmt.Errorf("upload failed, empty response")
|
||||
}
|
||||
if resp.List[0].Status == 1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second * 1)
|
||||
}
|
||||
file := resp.List[0]
|
||||
if file.Status != 1 {
|
||||
return nil, fmt.Errorf("upload failed, status: %d", resp.List[0].Status)
|
||||
}
|
||||
return &model.Object{
|
||||
ID: strconv.FormatInt(file.FileId, 10),
|
||||
//Path: ,
|
||||
Name: file.FileName,
|
||||
Size: stream.GetSize(),
|
||||
Modified: stream.ModTime(),
|
||||
Ctime: stream.CreateTime(),
|
||||
IsFolder: false,
|
||||
HashInfo: utils.NewHashInfo(utils.MD5, etag),
|
||||
}, nil
|
||||
}
|
||||
|
||||
//func (d *ILanZou) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*ILanZou)(nil)
|
80
drivers/ilanzou/meta.go
Normal file
80
drivers/ilanzou/meta.go
Normal file
@ -0,0 +1,80 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootID
|
||||
Username string `json:"username" type:"string" required:"true"`
|
||||
Password string `json:"password" type:"string" required:"true"`
|
||||
|
||||
Token string
|
||||
UUID string
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
base string
|
||||
secret []byte
|
||||
bucket string
|
||||
unproved string
|
||||
proved string
|
||||
devVersion string
|
||||
site string
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &ILanZou{
|
||||
config: driver.Config{
|
||||
Name: "ILanZou",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
},
|
||||
conf: Conf{
|
||||
base: "https://api.ilanzou.com",
|
||||
secret: []byte("lanZouY-disk-app"),
|
||||
bucket: "wpanstore-lanzou",
|
||||
unproved: "unproved",
|
||||
proved: "proved",
|
||||
devVersion: "122",
|
||||
site: "https://www.ilanzou.com",
|
||||
},
|
||||
}
|
||||
})
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &ILanZou{
|
||||
config: driver.Config{
|
||||
Name: "FeijiPan",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
},
|
||||
conf: Conf{
|
||||
base: "https://api.feijipan.com",
|
||||
secret: []byte("dingHao-disk-app"),
|
||||
bucket: "wpanstore",
|
||||
unproved: "ws",
|
||||
proved: "app",
|
||||
devVersion: "121",
|
||||
site: "https://www.feijipan.com",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
57
drivers/ilanzou/types.go
Normal file
57
drivers/ilanzou/types.go
Normal file
@ -0,0 +1,57 @@
|
||||
package template
|
||||
|
||||
type ListResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Total int `json:"total"`
|
||||
Code int `json:"code"`
|
||||
Offset int `json:"offset"`
|
||||
TotalPage int `json:"totalPage"`
|
||||
Limit int `json:"limit"`
|
||||
List []ListItem `json:"list"`
|
||||
}
|
||||
|
||||
type ListItem struct {
|
||||
IconId int `json:"iconId"`
|
||||
IsAmt int `json:"isAmt"`
|
||||
FolderDesc string `json:"folderDesc,omitempty"`
|
||||
AddTime string `json:"addTime"`
|
||||
FolderId int64 `json:"folderId"`
|
||||
ParentId int64 `json:"parentId"`
|
||||
ParentName string `json:"parentName"`
|
||||
NoteType int `json:"noteType,omitempty"`
|
||||
UpdTime string `json:"updTime"`
|
||||
IsShare int `json:"isShare"`
|
||||
FolderIcon string `json:"folderIcon,omitempty"`
|
||||
FolderName string `json:"folderName,omitempty"`
|
||||
FileType int `json:"fileType"`
|
||||
Status int `json:"status"`
|
||||
IsFileShare int `json:"isFileShare,omitempty"`
|
||||
FileName string `json:"fileName,omitempty"`
|
||||
FileStars float64 `json:"fileStars,omitempty"`
|
||||
IsFileDownload int `json:"isFileDownload,omitempty"`
|
||||
FileComments int `json:"fileComments,omitempty"`
|
||||
FileSize int64 `json:"fileSize,omitempty"`
|
||||
FileIcon string `json:"fileIcon,omitempty"`
|
||||
FileDownloads int `json:"fileDownloads,omitempty"`
|
||||
FileUrl interface{} `json:"fileUrl"`
|
||||
FileLikes int `json:"fileLikes,omitempty"`
|
||||
FileId int64 `json:"fileId,omitempty"`
|
||||
}
|
||||
|
||||
type Part struct {
|
||||
PartNumber int `json:"partNumber"`
|
||||
ETag string `json:"etag"`
|
||||
}
|
||||
|
||||
type UploadResultResp struct {
|
||||
Msg string `json:"msg"`
|
||||
Code int `json:"code"`
|
||||
List []struct {
|
||||
FileIconId int `json:"fileIconId"`
|
||||
FileName string `json:"fileName"`
|
||||
FileIcon string `json:"fileIcon"`
|
||||
FileId int64 `json:"fileId"`
|
||||
Status int `json:"status"`
|
||||
Token string `json:"token"`
|
||||
} `json:"list"`
|
||||
}
|
101
drivers/ilanzou/util.go
Normal file
101
drivers/ilanzou/util.go
Normal file
@ -0,0 +1,101 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/foxxorcat/mopan-sdk-go"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (d *ILanZou) login() error {
|
||||
res, err := d.unproved("/login", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"loginName": d.Username,
|
||||
"loginPwd": d.Password,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.Token = utils.Json.Get(res, "data", "appToken").ToString()
|
||||
if d.Token == "" {
|
||||
return fmt.Errorf("failed to login: token is empty, resp: %s", res)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTimestamp(secret []byte) (string, error) {
|
||||
ts := time.Now().UnixMilli()
|
||||
tsStr := strconv.FormatInt(ts, 10)
|
||||
res, err := mopan.AesEncrypt([]byte(tsStr), secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(res), nil
|
||||
}
|
||||
|
||||
func (d *ILanZou) request(pathname, method string, callback base.ReqCallback, proved bool, retry ...bool) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
ts, err := getTimestamp(d.conf.secret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.SetQueryParams(map[string]string{
|
||||
"uuid": d.UUID,
|
||||
"devType": "6",
|
||||
"devCode": d.UUID,
|
||||
"devModel": "chrome",
|
||||
"devVersion": d.conf.devVersion,
|
||||
"appVersion": "",
|
||||
"timestamp": ts,
|
||||
//"appToken": d.Token,
|
||||
"extra": "2",
|
||||
})
|
||||
req.SetHeaders(map[string]string{
|
||||
"Origin": d.conf.site,
|
||||
"Referer": d.conf.site + "/",
|
||||
})
|
||||
if proved {
|
||||
req.SetQueryParam("appToken", d.Token)
|
||||
}
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
res, err := req.Execute(method, d.conf.base+pathname)
|
||||
if err != nil {
|
||||
if res != nil {
|
||||
log.Errorf("[iLanZou] request error: %s", res.String())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
isRetry := len(retry) > 0 && retry[0]
|
||||
body := res.Body()
|
||||
code := utils.Json.Get(body, "code").ToInt()
|
||||
msg := utils.Json.Get(body, "msg").ToString()
|
||||
if code != 200 {
|
||||
if !isRetry && proved && (utils.SliceContains([]int{-1, -2}, code) || d.Token == "") {
|
||||
err = d.login()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.request(pathname, method, callback, proved, true)
|
||||
}
|
||||
return nil, fmt.Errorf("%d: %s", code, msg)
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (d *ILanZou) unproved(pathname, method string, callback base.ReqCallback) ([]byte, error) {
|
||||
return d.request("/"+d.conf.unproved+pathname, method, callback, false)
|
||||
}
|
||||
|
||||
func (d *ILanZou) proved(pathname, method string, callback base.ReqCallback) ([]byte, error) {
|
||||
return d.request("/"+d.conf.proved+pathname, method, callback, true)
|
||||
}
|
@ -62,7 +62,7 @@ func (d *IPFS) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]
|
||||
for _, file := range dirs {
|
||||
gateurl := *d.gateURL
|
||||
gateurl.Path = "ipfs/" + file.Hash
|
||||
gateurl.RawQuery = "filename=" + file.Name
|
||||
gateurl.RawQuery = "filename=" + url.PathEscape(file.Name)
|
||||
objlist = append(objlist, &model.ObjectURL{
|
||||
Object: model.Object{ID: file.Hash, Name: file.Name, Size: int64(file.Size), IsFolder: file.Type == 1},
|
||||
Url: model.Url{Url: gateurl.String()},
|
||||
@ -73,7 +73,7 @@ func (d *IPFS) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]
|
||||
}
|
||||
|
||||
func (d *IPFS) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
link := d.Gateway + "/ipfs/" + file.GetID() + "/?filename=" + file.GetName()
|
||||
link := d.Gateway + "/ipfs/" + file.GetID() + "/?filename=" + url.PathEscape(file.GetName())
|
||||
return &model.Link{URL: link}, nil
|
||||
}
|
||||
|
||||
|
@ -257,10 +257,18 @@ func (d *Local) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
|
||||
func (d *Local) Remove(ctx context.Context, obj model.Obj) error {
|
||||
var err error
|
||||
if obj.IsDir() {
|
||||
err = os.RemoveAll(obj.GetPath())
|
||||
if utils.SliceContains([]string{"", "delete permanently"}, d.RecycleBinPath) {
|
||||
if obj.IsDir() {
|
||||
err = os.RemoveAll(obj.GetPath())
|
||||
} else {
|
||||
err = os.Remove(obj.GetPath())
|
||||
}
|
||||
} else {
|
||||
err = os.Remove(obj.GetPath())
|
||||
dstPath := filepath.Join(d.RecycleBinPath, obj.GetName())
|
||||
if utils.Exists(dstPath) {
|
||||
dstPath = filepath.Join(d.RecycleBinPath, obj.GetName()+"_"+time.Now().Format("20060102150405"))
|
||||
}
|
||||
err = os.Rename(obj.GetPath(), dstPath)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -11,6 +11,7 @@ type Addition struct {
|
||||
ThumbCacheFolder string `json:"thumb_cache_folder"`
|
||||
ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"`
|
||||
MkdirPerm string `json:"mkdir_perm" default:"777"`
|
||||
RecycleBinPath string `json:"recycle_bin_path" default:"delete permanently" help:"path to recycle bin, delete permanently if empty or keep 'delete permanently'"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -188,6 +188,9 @@ func (d *MediaTrack) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
_ = tempFile.Close()
|
||||
}()
|
||||
uploader := s3manager.NewUploader(s)
|
||||
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &resp.Data.Bucket,
|
||||
Key: &resp.Data.Object,
|
||||
|
@ -43,23 +43,31 @@ func (d *MoPan) Init(ctx context.Context) error {
|
||||
if d.uploadThread < 1 || d.uploadThread > 32 {
|
||||
d.uploadThread, d.UploadThread = 3, "3"
|
||||
}
|
||||
login := func() error {
|
||||
data, err := d.client.Login(d.Phone, d.Password)
|
||||
|
||||
defer func() { d.SMSCode = "" }()
|
||||
|
||||
login := func() (err error) {
|
||||
var loginData *mopan.LoginResp
|
||||
if d.SMSCode != "" {
|
||||
loginData, err = d.client.LoginBySmsStep2(d.Phone, d.SMSCode)
|
||||
} else {
|
||||
loginData, err = d.client.Login(d.Phone, d.Password)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.client.SetAuthorization(data.Token)
|
||||
d.client.SetAuthorization(loginData.Token)
|
||||
|
||||
info, err := d.client.GetUserInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.userID = info.UserID
|
||||
log.Debugf("[mopan] Phone: %s UserCloudStorageRelations: %+v", d.Phone, data.UserCloudStorageRelations)
|
||||
log.Debugf("[mopan] Phone: %s UserCloudStorageRelations: %+v", d.Phone, loginData.UserCloudStorageRelations)
|
||||
cloudCircleApp, _ := d.client.QueryAllCloudCircleApp()
|
||||
log.Debugf("[mopan] Phone: %s CloudCircleApp: %+v", d.Phone, cloudCircleApp)
|
||||
if d.RootFolderID == "" {
|
||||
for _, userCloudStorage := range data.UserCloudStorageRelations {
|
||||
for _, userCloudStorage := range loginData.UserCloudStorageRelations {
|
||||
if userCloudStorage.Path == "/文件" {
|
||||
d.RootFolderID = userCloudStorage.FolderID
|
||||
}
|
||||
@ -76,8 +84,20 @@ func (d *MoPan) Init(ctx context.Context) error {
|
||||
op.MustSaveDriverStorage(d)
|
||||
}
|
||||
return err
|
||||
}).SetDeviceInfo(d.DeviceInfo)
|
||||
d.DeviceInfo = d.client.GetDeviceInfo()
|
||||
})
|
||||
|
||||
var deviceInfo mopan.DeviceInfo
|
||||
if strings.TrimSpace(d.DeviceInfo) != "" && utils.Json.UnmarshalFromString(d.DeviceInfo, &deviceInfo) == nil {
|
||||
d.client.SetDeviceInfo(&deviceInfo)
|
||||
}
|
||||
d.DeviceInfo, _ = utils.Json.MarshalToString(d.client.GetDeviceInfo())
|
||||
|
||||
if strings.Contains(d.SMSCode, "send") {
|
||||
if _, err := d.client.LoginBySms(d.Phone); err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.New("please enter the SMS code")
|
||||
}
|
||||
return login()
|
||||
}
|
||||
|
||||
@ -119,10 +139,13 @@ func (d *MoPan) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
||||
}
|
||||
|
||||
data.DownloadUrl = strings.Replace(strings.ReplaceAll(data.DownloadUrl, "&", "&"), "http://", "https://", 1)
|
||||
res, err := base.NoRedirectClient.R().SetContext(ctx).Head(data.DownloadUrl)
|
||||
res, err := base.NoRedirectClient.R().SetDoNotParseResponse(true).SetContext(ctx).Get(data.DownloadUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = res.RawBody().Close()
|
||||
}()
|
||||
if res.StatusCode() == 302 {
|
||||
data.DownloadUrl = res.Header().Get("location")
|
||||
}
|
||||
@ -272,7 +295,7 @@ func (d *MoPan) Put(ctx context.Context, dstDir model.Obj, stream model.FileStre
|
||||
}
|
||||
|
||||
if !initUpdload.FileDataExists {
|
||||
utils.Log.Error(d.client.CloudDiskStartBusiness())
|
||||
// utils.Log.Error(d.client.CloudDiskStartBusiness())
|
||||
|
||||
threadG, upCtx := errgroup.NewGroupWithContext(ctx, d.uploadThread,
|
||||
retry.Attempts(3),
|
||||
@ -300,6 +323,7 @@ func (d *MoPan) Put(ctx context.Context, dstDir model.Obj, stream model.FileStre
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.ContentLength = byteSize
|
||||
resp, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
type Addition struct {
|
||||
Phone string `json:"phone" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
SMSCode string `json:"sms_code" help:"input 'send' send sms "`
|
||||
|
||||
RootFolderID string `json:"root_folder_id" default:""`
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@ -19,6 +20,8 @@ type Onedrive struct {
|
||||
model.Storage
|
||||
Addition
|
||||
AccessToken string
|
||||
root *Object
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (d *Onedrive) Config() driver.Config {
|
||||
@ -40,6 +43,42 @@ func (d *Onedrive) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Onedrive) GetRoot(ctx context.Context) (model.Obj, error) {
|
||||
if d.root != nil {
|
||||
return d.root, nil
|
||||
}
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
root := &Object{
|
||||
ObjThumb: model.ObjThumb{
|
||||
Object: model.Object{
|
||||
ID: "root",
|
||||
Path: d.RootFolderPath,
|
||||
Name: "root",
|
||||
Size: 0,
|
||||
Modified: d.Modified,
|
||||
Ctime: d.Modified,
|
||||
IsFolder: true,
|
||||
},
|
||||
},
|
||||
ParentID: "",
|
||||
}
|
||||
if !utils.PathEqual(d.RootFolderPath, "/") {
|
||||
// get root folder id
|
||||
url := d.GetMetaUrl(false, d.RootFolderPath)
|
||||
var resp struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
_, err := d.Request(url, http.MethodGet, nil, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root.ID = resp.Id
|
||||
}
|
||||
d.root = root
|
||||
return d.root, nil
|
||||
}
|
||||
|
||||
func (d *Onedrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.getFiles(dir.GetPath())
|
||||
if err != nil {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@ -19,6 +20,8 @@ type OnedriveAPP struct {
|
||||
model.Storage
|
||||
Addition
|
||||
AccessToken string
|
||||
root *Object
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) Config() driver.Config {
|
||||
@ -40,6 +43,42 @@ func (d *OnedriveAPP) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) GetRoot(ctx context.Context) (model.Obj, error) {
|
||||
if d.root != nil {
|
||||
return d.root, nil
|
||||
}
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
root := &Object{
|
||||
ObjThumb: model.ObjThumb{
|
||||
Object: model.Object{
|
||||
ID: "root",
|
||||
Path: d.RootFolderPath,
|
||||
Name: "root",
|
||||
Size: 0,
|
||||
Modified: d.Modified,
|
||||
Ctime: d.Modified,
|
||||
IsFolder: true,
|
||||
},
|
||||
},
|
||||
ParentID: "",
|
||||
}
|
||||
if !utils.PathEqual(d.RootFolderPath, "/") {
|
||||
// get root folder id
|
||||
url := d.GetMetaUrl(false, d.RootFolderPath)
|
||||
var resp struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
_, err := d.Request(url, http.MethodGet, nil, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root.ID = resp.Id
|
||||
}
|
||||
d.root = root
|
||||
return d.root, nil
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.getFiles(dir.GetPath())
|
||||
if err != nil {
|
||||
|
@ -172,6 +172,9 @@ func (d *PikPak) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
||||
return err
|
||||
}
|
||||
uploader := s3manager.NewUploader(ss)
|
||||
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: ¶ms.Bucket,
|
||||
Key: ¶ms.Key,
|
||||
|
437
drivers/quqi/driver.go
Normal file
437
drivers/quqi/driver.go
Normal file
@ -0,0 +1,437 @@
|
||||
package quqi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/pkg/utils/random"
|
||||
"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"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Quqi struct {
|
||||
model.Storage
|
||||
Addition
|
||||
Cookie string // Cookie
|
||||
GroupID string // 私人云群组ID
|
||||
ClientID string // 随机生成客户端ID 经过测试,部分接口调用若不携带client id会出现错误
|
||||
}
|
||||
|
||||
func (d *Quqi) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *Quqi) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Quqi) Init(ctx context.Context) error {
|
||||
// 登录
|
||||
if err := d.login(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 生成随机client id (与网页端生成逻辑一致)
|
||||
d.ClientID = "quqipc_" + random.String(10)
|
||||
|
||||
// 获取私人云ID (暂时仅获取私人云)
|
||||
groupResp := &GroupRes{}
|
||||
if _, err := d.request("group.quqi.com", "/v1/group/list", resty.MethodGet, nil, groupResp); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, groupInfo := range groupResp.Data {
|
||||
if groupInfo == nil {
|
||||
continue
|
||||
}
|
||||
if groupInfo.Type == 2 {
|
||||
d.GroupID = strconv.Itoa(groupInfo.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
if d.GroupID == "" {
|
||||
return errs.StorageNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Quqi) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Quqi) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
var (
|
||||
listResp = &ListRes{}
|
||||
files []model.Obj
|
||||
)
|
||||
|
||||
if _, err := d.request("", "/api/dir/ls", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"node_id": dir.GetID(),
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, listResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if listResp.Data == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// dirs
|
||||
for _, dirInfo := range listResp.Data.Dir {
|
||||
if dirInfo == nil {
|
||||
continue
|
||||
}
|
||||
files = append(files, &model.Object{
|
||||
ID: strconv.FormatInt(dirInfo.NodeID, 10),
|
||||
Name: dirInfo.Name,
|
||||
Modified: time.Unix(dirInfo.UpdateTime, 0),
|
||||
Ctime: time.Unix(dirInfo.AddTime, 0),
|
||||
IsFolder: true,
|
||||
})
|
||||
}
|
||||
|
||||
// files
|
||||
for _, fileInfo := range listResp.Data.File {
|
||||
if fileInfo == nil {
|
||||
continue
|
||||
}
|
||||
if fileInfo.EXT != "" {
|
||||
fileInfo.Name = strings.Join([]string{fileInfo.Name, fileInfo.EXT}, ".")
|
||||
}
|
||||
|
||||
files = append(files, &model.Object{
|
||||
ID: strconv.FormatInt(fileInfo.NodeID, 10),
|
||||
Name: fileInfo.Name,
|
||||
Size: fileInfo.Size,
|
||||
Modified: time.Unix(fileInfo.UpdateTime, 0),
|
||||
Ctime: time.Unix(fileInfo.AddTime, 0),
|
||||
})
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
if d.CDN {
|
||||
link, err := d.linkFromCDN(file.GetID())
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
} else {
|
||||
return link, nil
|
||||
}
|
||||
}
|
||||
|
||||
link, err := d.linkFromPreview(file.GetID())
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
} else {
|
||||
return link, nil
|
||||
}
|
||||
|
||||
link, err = d.linkFromDownload(file.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
||||
var (
|
||||
makeDirRes = &MakeDirRes{}
|
||||
timeNow = time.Now()
|
||||
)
|
||||
|
||||
if _, err := d.request("", "/api/dir/mkDir", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"parent_id": parentDir.GetID(),
|
||||
"name": dirName,
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, makeDirRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Object{
|
||||
ID: strconv.FormatInt(makeDirRes.Data.NodeID, 10),
|
||||
Name: dirName,
|
||||
Modified: timeNow,
|
||||
Ctime: timeNow,
|
||||
IsFolder: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||
var moveRes = &MoveRes{}
|
||||
|
||||
if _, err := d.request("", "/api/dir/mvDir", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"node_id": dstDir.GetID(),
|
||||
"source_quqi_id": d.GroupID,
|
||||
"source_tree_id": "1",
|
||||
"source_node_id": srcObj.GetID(),
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, moveRes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Object{
|
||||
ID: strconv.FormatInt(moveRes.Data.NodeID, 10),
|
||||
Name: moveRes.Data.NodeName,
|
||||
Size: srcObj.GetSize(),
|
||||
Modified: time.Now(),
|
||||
Ctime: srcObj.CreateTime(),
|
||||
IsFolder: srcObj.IsDir(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
||||
var realName = newName
|
||||
|
||||
if !srcObj.IsDir() {
|
||||
srcExt, newExt := utils.Ext(srcObj.GetName()), utils.Ext(newName)
|
||||
|
||||
// 曲奇网盘的文件名称由文件名和扩展名组成,若存在扩展名,则重命名时仅支持更改文件名,扩展名在曲奇服务端保留
|
||||
if srcExt != "" && srcExt == newExt {
|
||||
parts := strings.Split(newName, ".")
|
||||
if len(parts) > 1 {
|
||||
realName = strings.Join(parts[:len(parts)-1], ".")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := d.request("", "/api/dir/renameDir", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"node_id": srcObj.GetID(),
|
||||
"rename": realName,
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Object{
|
||||
ID: srcObj.GetID(),
|
||||
Name: newName,
|
||||
Size: srcObj.GetSize(),
|
||||
Modified: time.Now(),
|
||||
Ctime: srcObj.CreateTime(),
|
||||
IsFolder: srcObj.IsDir(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||
// 无法从曲奇接口响应中直接获取复制后的文件信息
|
||||
if _, err := d.request("", "/api/node/copy", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"node_id": dstDir.GetID(),
|
||||
"source_quqi_id": d.GroupID,
|
||||
"source_tree_id": "1",
|
||||
"source_node_id": srcObj.GetID(),
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) Remove(ctx context.Context, obj model.Obj) error {
|
||||
// 暂时不做直接删除,默认都放到回收站。直接删除方法:先调用删除接口放入回收站,在通过回收站接口删除文件
|
||||
if _, err := d.request("", "/api/node/del", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"node_id": obj.GetID(),
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Quqi) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
||||
// base info
|
||||
sizeStr := strconv.FormatInt(stream.GetSize(), 10)
|
||||
f, err := stream.CacheFullInTempFile()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
md5, err := utils.HashFile(utils.MD5, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sha, err := utils.HashFile(utils.SHA256, f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// init upload
|
||||
var uploadInitResp UploadInitResp
|
||||
_, err = d.request("", "/api/upload/v1/file/init", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"parent_id": dstDir.GetID(),
|
||||
"size": sizeStr,
|
||||
"file_name": stream.GetName(),
|
||||
"md5": md5,
|
||||
"sha": sha,
|
||||
"is_slice": "true",
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, &uploadInitResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// check exist
|
||||
// if the file already exists in Quqi server, there is no need to actually upload it
|
||||
if uploadInitResp.Data.Exist {
|
||||
// the file name returned by Quqi does not include the extension name
|
||||
nodeName, nodeExt := uploadInitResp.Data.NodeName, rawExt(stream.GetName())
|
||||
if nodeExt != "" {
|
||||
nodeName = nodeName + "." + nodeExt
|
||||
}
|
||||
return &model.Object{
|
||||
ID: strconv.FormatInt(uploadInitResp.Data.NodeID, 10),
|
||||
Name: nodeName,
|
||||
Size: stream.GetSize(),
|
||||
Modified: stream.ModTime(),
|
||||
Ctime: stream.CreateTime(),
|
||||
}, nil
|
||||
}
|
||||
// listParts
|
||||
_, err = d.request("upload.quqi.com:20807", "/upload/v1/listParts", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"token": uploadInitResp.Data.Token,
|
||||
"task_id": uploadInitResp.Data.TaskID,
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// get temp key
|
||||
var tempKeyResp TempKeyResp
|
||||
_, err = d.request("upload.quqi.com:20807", "/upload/v1/tempKey", resty.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"token": uploadInitResp.Data.Token,
|
||||
"task_id": uploadInitResp.Data.TaskID,
|
||||
})
|
||||
}, &tempKeyResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// upload
|
||||
// u, err := url.Parse(fmt.Sprintf("https://%s.cos.ap-shanghai.myqcloud.com", uploadInitResp.Data.Bucket))
|
||||
// b := &cos.BaseURL{BucketURL: u}
|
||||
// client := cos.NewClient(b, &http.Client{
|
||||
// Transport: &cos.CredentialTransport{
|
||||
// Credential: cos.NewTokenCredential(tempKeyResp.Data.Credentials.TmpSecretID, tempKeyResp.Data.Credentials.TmpSecretKey, tempKeyResp.Data.Credentials.SessionToken),
|
||||
// },
|
||||
// })
|
||||
// partSize := int64(1024 * 1024 * 2)
|
||||
// partCount := (stream.GetSize() + partSize - 1) / partSize
|
||||
// for i := 1; i <= int(partCount); i++ {
|
||||
// length := partSize
|
||||
// if i == int(partCount) {
|
||||
// length = stream.GetSize() - (int64(i)-1)*partSize
|
||||
// }
|
||||
// _, err := client.Object.UploadPart(
|
||||
// ctx, uploadInitResp.Data.Key, uploadInitResp.Data.UploadID, i, io.LimitReader(f, partSize), &cos.ObjectUploadPartOptions{
|
||||
// ContentLength: length,
|
||||
// },
|
||||
// )
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
|
||||
cfg := &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(tempKeyResp.Data.Credentials.TmpSecretID, tempKeyResp.Data.Credentials.TmpSecretKey, tempKeyResp.Data.Credentials.SessionToken),
|
||||
Region: aws.String("ap-shanghai"),
|
||||
Endpoint: aws.String("cos.ap-shanghai.myqcloud.com"),
|
||||
}
|
||||
s, err := session.NewSession(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uploader := s3manager.NewUploader(s)
|
||||
buf := make([]byte, 1024*1024*2)
|
||||
for partNumber := int64(1); ; partNumber++ {
|
||||
n, err := io.ReadFull(f, buf)
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
_, err = uploader.S3.UploadPartWithContext(ctx, &s3.UploadPartInput{
|
||||
UploadId: &uploadInitResp.Data.UploadID,
|
||||
Key: &uploadInitResp.Data.Key,
|
||||
Bucket: &uploadInitResp.Data.Bucket,
|
||||
PartNumber: aws.Int64(partNumber),
|
||||
Body: bytes.NewReader(buf[:n]),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// finish upload
|
||||
var uploadFinishResp UploadFinishResp
|
||||
_, err = d.request("", "/api/upload/v1/file/finish", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"token": uploadInitResp.Data.Token,
|
||||
"task_id": uploadInitResp.Data.TaskID,
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, &uploadFinishResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// the file name returned by Quqi does not include the extension name
|
||||
nodeName, nodeExt := uploadFinishResp.Data.NodeName, rawExt(stream.GetName())
|
||||
if nodeExt != "" {
|
||||
nodeName = nodeName + "." + nodeExt
|
||||
}
|
||||
return &model.Object{
|
||||
ID: strconv.FormatInt(uploadFinishResp.Data.NodeID, 10),
|
||||
Name: nodeName,
|
||||
Size: stream.GetSize(),
|
||||
Modified: stream.ModTime(),
|
||||
Ctime: stream.CreateTime(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
//func (d *Template) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*Quqi)(nil)
|
28
drivers/quqi/meta.go
Normal file
28
drivers/quqi/meta.go
Normal file
@ -0,0 +1,28 @@
|
||||
package quqi
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootID
|
||||
Phone string `json:"phone"`
|
||||
Password string `json:"password"`
|
||||
Cookie string `json:"cookie" help:"Cookie can be used on multiple clients at the same time"`
|
||||
CDN bool `json:"cdn" help:"If you enable this option, the download speed can be increased, but there will be some performance loss"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Quqi",
|
||||
OnlyLocal: true,
|
||||
LocalSort: true,
|
||||
//NoUpload: true,
|
||||
DefaultRoot: "0",
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Quqi{}
|
||||
})
|
||||
}
|
197
drivers/quqi/types.go
Normal file
197
drivers/quqi/types.go
Normal file
@ -0,0 +1,197 @@
|
||||
package quqi
|
||||
|
||||
type BaseReqQuery struct {
|
||||
ID string `json:"quqiid"`
|
||||
}
|
||||
|
||||
type BaseReq struct {
|
||||
GroupID string `json:"quqi_id"`
|
||||
}
|
||||
|
||||
type BaseRes struct {
|
||||
//Data interface{} `json:"data"`
|
||||
Code int `json:"err"`
|
||||
Message string `json:"msg"`
|
||||
}
|
||||
|
||||
type GroupRes struct {
|
||||
BaseRes
|
||||
Data []*Group `json:"data"`
|
||||
}
|
||||
|
||||
type ListRes struct {
|
||||
BaseRes
|
||||
Data *List `json:"data"`
|
||||
}
|
||||
|
||||
type GetDocRes struct {
|
||||
BaseRes
|
||||
Data struct {
|
||||
OriginPath string `json:"origin_path"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type GetDownloadResp struct {
|
||||
BaseRes
|
||||
Data struct {
|
||||
Url string `json:"url"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type MakeDirRes struct {
|
||||
BaseRes
|
||||
Data struct {
|
||||
IsRoot bool `json:"is_root"`
|
||||
NodeID int64 `json:"node_id"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type MoveRes struct {
|
||||
BaseRes
|
||||
Data struct {
|
||||
NodeChildNum int64 `json:"node_child_num"`
|
||||
NodeID int64 `json:"node_id"`
|
||||
NodeName string `json:"node_name"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
GroupID int64 `json:"quqi_id"`
|
||||
TreeID int64 `json:"tree_id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type RenameRes struct {
|
||||
BaseRes
|
||||
Data struct {
|
||||
NodeID int64 `json:"node_id"`
|
||||
GroupID int64 `json:"quqi_id"`
|
||||
Rename string `json:"rename"`
|
||||
TreeID int64 `json:"tree_id"`
|
||||
UpdateTime int64 `json:"updatetime"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type CopyRes struct {
|
||||
BaseRes
|
||||
}
|
||||
|
||||
type RemoveRes struct {
|
||||
BaseRes
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
ID int `json:"quqi_id"`
|
||||
Type int `json:"type"`
|
||||
Name string `json:"name"`
|
||||
IsAdministrator int `json:"is_administrator"`
|
||||
Role int `json:"role"`
|
||||
Avatar string `json:"avatar_url"`
|
||||
IsStick int `json:"is_stick"`
|
||||
Nickname string `json:"nickname"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
type List struct {
|
||||
ListDir
|
||||
Dir []*ListDir `json:"dir"`
|
||||
File []*ListFile `json:"file"`
|
||||
}
|
||||
|
||||
type ListItem struct {
|
||||
AddTime int64 `json:"add_time"`
|
||||
IsDir int `json:"is_dir"`
|
||||
IsExpand int `json:"is_expand"`
|
||||
IsFinalize int `json:"is_finalize"`
|
||||
LastEditorName string `json:"last_editor_name"`
|
||||
Name string `json:"name"`
|
||||
NodeID int64 `json:"nid"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
Permission int `json:"permission"`
|
||||
TreeID int64 `json:"tid"`
|
||||
UpdateCNT int64 `json:"update_cnt"`
|
||||
UpdateTime int64 `json:"update_time"`
|
||||
}
|
||||
|
||||
type ListDir struct {
|
||||
ListItem
|
||||
ChildDocNum int64 `json:"child_doc_num"`
|
||||
DirDetail string `json:"dir_detail"`
|
||||
DirType int `json:"dir_type"`
|
||||
}
|
||||
|
||||
type ListFile struct {
|
||||
ListItem
|
||||
BroadDocType string `json:"broad_doc_type"`
|
||||
CanDisplay bool `json:"can_display"`
|
||||
Detail string `json:"detail"`
|
||||
EXT string `json:"ext"`
|
||||
Filetype string `json:"filetype"`
|
||||
HasMobileThumbnail bool `json:"has_mobile_thumbnail"`
|
||||
HasThumbnail bool `json:"has_thumbnail"`
|
||||
Size int64 `json:"size"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type UploadInitResp struct {
|
||||
Data struct {
|
||||
Bucket string `json:"bucket"`
|
||||
Exist bool `json:"exist"`
|
||||
Key string `json:"key"`
|
||||
TaskID string `json:"task_id"`
|
||||
Token string `json:"token"`
|
||||
UploadID string `json:"upload_id"`
|
||||
URL string `json:"url"`
|
||||
NodeID int64 `json:"node_id"`
|
||||
NodeName string `json:"node_name"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
} `json:"data"`
|
||||
Err int `json:"err"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type TempKeyResp struct {
|
||||
Err int `json:"err"`
|
||||
Msg string `json:"msg"`
|
||||
Data struct {
|
||||
ExpiredTime int `json:"expiredTime"`
|
||||
Expiration string `json:"expiration"`
|
||||
Credentials struct {
|
||||
SessionToken string `json:"sessionToken"`
|
||||
TmpSecretID string `json:"tmpSecretId"`
|
||||
TmpSecretKey string `json:"tmpSecretKey"`
|
||||
} `json:"credentials"`
|
||||
RequestID string `json:"requestId"`
|
||||
StartTime int `json:"startTime"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type UploadFinishResp struct {
|
||||
Data struct {
|
||||
NodeID int64 `json:"node_id"`
|
||||
NodeName string `json:"node_name"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
QuqiID int64 `json:"quqi_id"`
|
||||
TreeID int64 `json:"tree_id"`
|
||||
} `json:"data"`
|
||||
Err int `json:"err"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type UrlExchangeResp struct {
|
||||
BaseRes
|
||||
Data struct {
|
||||
Name string `json:"name"`
|
||||
Mime string `json:"mime"`
|
||||
Size int64 `json:"size"`
|
||||
DownloadType int `json:"download_type"`
|
||||
ChannelType int `json:"channel_type"`
|
||||
ChannelID int `json:"channel_id"`
|
||||
Url string `json:"url"`
|
||||
ExpiredTime int64 `json:"expired_time"`
|
||||
IsEncrypted bool `json:"is_encrypted"`
|
||||
EncryptedSize int64 `json:"encrypted_size"`
|
||||
EncryptedAlg string `json:"encrypted_alg"`
|
||||
EncryptedKey string `json:"encrypted_key"`
|
||||
PassportID int64 `json:"passport_id"`
|
||||
RequestExpiredTime int64 `json:"request_expired_time"`
|
||||
} `json:"data"`
|
||||
}
|
316
drivers/quqi/util.go
Normal file
316
drivers/quqi/util.go
Normal file
@ -0,0 +1,316 @@
|
||||
package quqi
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
func (d *Quqi) request(host string, path string, method string, callback base.ReqCallback, resp interface{}) (*resty.Response, error) {
|
||||
var (
|
||||
reqUrl = url.URL{
|
||||
Scheme: "https",
|
||||
Host: "quqi.com",
|
||||
Path: path,
|
||||
}
|
||||
req = base.RestyClient.R()
|
||||
result BaseRes
|
||||
)
|
||||
|
||||
if host != "" {
|
||||
reqUrl.Host = host
|
||||
}
|
||||
req.SetHeaders(map[string]string{
|
||||
"Origin": "https://quqi.com",
|
||||
"Cookie": d.Cookie,
|
||||
})
|
||||
|
||||
if d.GroupID != "" {
|
||||
req.SetQueryParam("quqiid", d.GroupID)
|
||||
}
|
||||
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
|
||||
res, err := req.Execute(method, reqUrl.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// resty.Request.SetResult cannot parse result correctly sometimes
|
||||
err = utils.Json.Unmarshal(res.Body(), &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result.Code != 0 {
|
||||
return nil, errors.New(result.Message)
|
||||
}
|
||||
if resp != nil {
|
||||
err = utils.Json.Unmarshal(res.Body(), resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) login() error {
|
||||
if d.Addition.Cookie != "" {
|
||||
d.Cookie = d.Addition.Cookie
|
||||
}
|
||||
if d.checkLogin() {
|
||||
return nil
|
||||
}
|
||||
if d.Cookie != "" {
|
||||
return errors.New("cookie is invalid")
|
||||
}
|
||||
if d.Phone == "" {
|
||||
return errors.New("phone number is empty")
|
||||
}
|
||||
if d.Password == "" {
|
||||
return errs.EmptyPassword
|
||||
}
|
||||
|
||||
resp, err := d.request("", "/auth/person/v2/login/password", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"phone": d.Phone,
|
||||
"password": base64.StdEncoding.EncodeToString([]byte(d.Password)),
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var cookies []string
|
||||
for _, cookie := range resp.RawResponse.Cookies() {
|
||||
cookies = append(cookies, fmt.Sprintf("%s=%s", cookie.Name, cookie.Value))
|
||||
}
|
||||
d.Cookie = strings.Join(cookies, ";")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Quqi) checkLogin() bool {
|
||||
if _, err := d.request("", "/auth/account/baseInfo", resty.MethodGet, nil, nil); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// rawExt 保留扩展名大小写
|
||||
func rawExt(name string) string {
|
||||
ext := stdpath.Ext(name)
|
||||
if strings.HasPrefix(ext, ".") {
|
||||
ext = ext[1:]
|
||||
}
|
||||
|
||||
return ext
|
||||
}
|
||||
|
||||
// decryptKey 获取密码
|
||||
func decryptKey(encodeKey string) []byte {
|
||||
// 移除非法字符
|
||||
u := strings.ReplaceAll(encodeKey, "[^A-Za-z0-9+\\/]", "")
|
||||
|
||||
// 计算输出字节数组的长度
|
||||
o := len(u)
|
||||
a := 32
|
||||
|
||||
// 创建输出字节数组
|
||||
c := make([]byte, a)
|
||||
|
||||
// 编码循环
|
||||
s := uint32(0) // 累加器
|
||||
f := 0 // 输出数组索引
|
||||
for l := 0; l < o; l++ {
|
||||
r := l & 3 // 取模4,得到当前字符在四字节块中的位置
|
||||
i := u[l] // 当前字符的ASCII码
|
||||
|
||||
// 编码当前字符
|
||||
switch {
|
||||
case i >= 65 && i < 91: // 大写字母
|
||||
s |= uint32(i-65) << uint32(6*(3-r))
|
||||
case i >= 97 && i < 123: // 小写字母
|
||||
s |= uint32(i-71) << uint32(6*(3-r))
|
||||
case i >= 48 && i < 58: // 数字
|
||||
s |= uint32(i+4) << uint32(6*(3-r))
|
||||
case i == 43: // 加号
|
||||
s |= uint32(62) << uint32(6*(3-r))
|
||||
case i == 47: // 斜杠
|
||||
s |= uint32(63) << uint32(6*(3-r))
|
||||
}
|
||||
|
||||
// 如果累加器已经包含了四个字符,或者是最后一个字符,则写入输出数组
|
||||
if r == 3 || l == o-1 {
|
||||
for e := 0; e < 3 && f < a; e, f = e+1, f+1 {
|
||||
c[f] = byte(s >> (16 >> e & 24) & 255)
|
||||
}
|
||||
s = 0
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (d *Quqi) linkFromPreview(id string) (*model.Link, error) {
|
||||
var getDocResp GetDocRes
|
||||
if _, err := d.request("", "/api/doc/getDoc", resty.MethodPost, func(req *resty.Request) {
|
||||
req.SetFormData(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"node_id": id,
|
||||
"client_id": d.ClientID,
|
||||
})
|
||||
}, &getDocResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if getDocResp.Data.OriginPath == "" {
|
||||
return nil, errors.New("cannot get link from preview")
|
||||
}
|
||||
return &model.Link{
|
||||
URL: getDocResp.Data.OriginPath,
|
||||
Header: http.Header{
|
||||
"Origin": []string{"https://quqi.com"},
|
||||
"Cookie": []string{d.Cookie},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) linkFromDownload(id string) (*model.Link, error) {
|
||||
var getDownloadResp GetDownloadResp
|
||||
if _, err := d.request("", "/api/doc/getDownload", resty.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"quqi_id": d.GroupID,
|
||||
"tree_id": "1",
|
||||
"node_id": id,
|
||||
"url_type": "undefined",
|
||||
"entry_type": "undefined",
|
||||
"client_id": d.ClientID,
|
||||
"no_redirect": "1",
|
||||
})
|
||||
}, &getDownloadResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if getDownloadResp.Data.Url == "" {
|
||||
return nil, errors.New("cannot get link from download")
|
||||
}
|
||||
|
||||
return &model.Link{
|
||||
URL: getDownloadResp.Data.Url,
|
||||
Header: http.Header{
|
||||
"Origin": []string{"https://quqi.com"},
|
||||
"Cookie": []string{d.Cookie},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Quqi) linkFromCDN(id string) (*model.Link, error) {
|
||||
downloadLink, err := d.linkFromDownload(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var urlExchangeResp UrlExchangeResp
|
||||
if _, err = d.request("api.quqi.com", "/preview/downloadInfo/url/exchange", resty.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParam("url", downloadLink.URL)
|
||||
}, &urlExchangeResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if urlExchangeResp.Data.Url == "" {
|
||||
return nil, errors.New("cannot get link from cdn")
|
||||
}
|
||||
|
||||
// 假设存在未加密的情况
|
||||
if !urlExchangeResp.Data.IsEncrypted {
|
||||
return &model.Link{
|
||||
URL: urlExchangeResp.Data.Url,
|
||||
Header: http.Header{
|
||||
"Origin": []string{"https://quqi.com"},
|
||||
"Cookie": []string{d.Cookie},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 根据sio(https://github.com/minio/sio/blob/master/DARE.md)描述及实际测试,得出以下结论:
|
||||
// 1. 加密后大小(encrypted_size)-原始文件大小(size) = 加密包的头大小+身份验证标识 = (16+16) * N -> N为加密包的数量
|
||||
// 2. 原始文件大小(size)+64*1024-1 / (64*1024) = N -> 每个包的有效负载为64K
|
||||
remoteClosers := utils.EmptyClosers()
|
||||
payloadSize := int64(1 << 16)
|
||||
expiration := time.Until(time.Unix(urlExchangeResp.Data.ExpiredTime, 0))
|
||||
resultRangeReader := func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||
encryptedOffset := httpRange.Start / payloadSize * (payloadSize + 32)
|
||||
decryptedOffset := httpRange.Start % payloadSize
|
||||
encryptedLength := (httpRange.Length+httpRange.Start+payloadSize-1)/payloadSize*(payloadSize+32) - encryptedOffset
|
||||
if httpRange.Length < 0 {
|
||||
encryptedLength = httpRange.Length
|
||||
} else {
|
||||
if httpRange.Length+httpRange.Start >= urlExchangeResp.Data.Size || encryptedLength+encryptedOffset >= urlExchangeResp.Data.EncryptedSize {
|
||||
encryptedLength = -1
|
||||
}
|
||||
}
|
||||
//log.Debugf("size: %d\tencrypted_size: %d", urlExchangeResp.Data.Size, urlExchangeResp.Data.EncryptedSize)
|
||||
//log.Debugf("http range offset: %d, length: %d", httpRange.Start, httpRange.Length)
|
||||
//log.Debugf("encrypted offset: %d, length: %d, decrypted offset: %d", encryptedOffset, encryptedLength, decryptedOffset)
|
||||
|
||||
rrc, err := stream.GetRangeReadCloserFromLink(urlExchangeResp.Data.EncryptedSize, &model.Link{
|
||||
URL: urlExchangeResp.Data.Url,
|
||||
Header: http.Header{
|
||||
"Origin": []string{"https://quqi.com"},
|
||||
"Cookie": []string{d.Cookie},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rc, err := rrc.RangeRead(ctx, http_range.Range{Start: encryptedOffset, Length: encryptedLength})
|
||||
remoteClosers.AddClosers(rrc.GetClosers())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decryptReader, err := sio.DecryptReader(rc, sio.Config{
|
||||
MinVersion: sio.Version10,
|
||||
MaxVersion: sio.Version20,
|
||||
CipherSuites: []byte{sio.CHACHA20_POLY1305, sio.AES_256_GCM},
|
||||
Key: decryptKey(urlExchangeResp.Data.EncryptedKey),
|
||||
SequenceNumber: uint32(httpRange.Start / payloadSize),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bufferReader := bufio.NewReader(decryptReader)
|
||||
bufferReader.Discard(int(decryptedOffset))
|
||||
|
||||
return utils.NewReadCloser(bufferReader, func() error {
|
||||
return nil
|
||||
}), nil
|
||||
}
|
||||
|
||||
return &model.Link{
|
||||
Header: http.Header{
|
||||
"Origin": []string{"https://quqi.com"},
|
||||
"Cookie": []string{d.Cookie},
|
||||
},
|
||||
RangeReadCloser: &model.RangeReadCloser{RangeReader: resultRangeReader, Closers: remoteClosers},
|
||||
Expiration: &expiration,
|
||||
}, nil
|
||||
}
|
62
drivers/s3/doge.go
Normal file
62
drivers/s3/doge.go
Normal file
@ -0,0 +1,62 @@
|
||||
package s3
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TmpTokenResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data TmpTokenResponseData `json:"data,omitempty"`
|
||||
}
|
||||
type TmpTokenResponseData struct {
|
||||
Credentials Credentials `json:"Credentials"`
|
||||
}
|
||||
type Credentials struct {
|
||||
AccessKeyId string `json:"accessKeyId,omitempty"`
|
||||
SecretAccessKey string `json:"secretAccessKey,omitempty"`
|
||||
SessionToken string `json:"sessionToken,omitempty"`
|
||||
}
|
||||
|
||||
func getCredentials(AccessKey, SecretKey string) (rst Credentials, err error) {
|
||||
apiPath := "/auth/tmp_token.json"
|
||||
reqBody, err := json.Marshal(map[string]interface{}{"channel": "OSS_FULL", "scopes": []string{"*"}})
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
|
||||
signStr := apiPath + "\n" + string(reqBody)
|
||||
hmacObj := hmac.New(sha1.New, []byte(SecretKey))
|
||||
hmacObj.Write([]byte(signStr))
|
||||
sign := hex.EncodeToString(hmacObj.Sum(nil))
|
||||
Authorization := "TOKEN " + AccessKey + ":" + sign
|
||||
|
||||
req, err := http.NewRequest("POST", "https://api.dogecloud.com"+apiPath, strings.NewReader(string(reqBody)))
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Add("Authorization", Authorization)
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
ret, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
var tmpTokenResp TmpTokenResponse
|
||||
err = json.Unmarshal(ret, &tmpTokenResp)
|
||||
if err != nil {
|
||||
return rst, err
|
||||
}
|
||||
return tmpTokenResp.Data.Credentials, nil
|
||||
}
|
@ -26,10 +26,12 @@ type S3 struct {
|
||||
Session *session.Session
|
||||
client *s3.S3
|
||||
linkClient *s3.S3
|
||||
|
||||
config driver.Config
|
||||
}
|
||||
|
||||
func (d *S3) Config() driver.Config {
|
||||
return config
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *S3) GetAddition() driver.Additional {
|
||||
|
@ -22,15 +22,25 @@ type Addition struct {
|
||||
AddFilenameToDisposition bool `json:"add_filename_to_disposition" help:"Add filename to Content-Disposition header."`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "S3",
|
||||
DefaultRoot: "/",
|
||||
LocalSort: true,
|
||||
CheckStatus: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &S3{}
|
||||
return &S3{
|
||||
config: driver.Config{
|
||||
Name: "S3",
|
||||
DefaultRoot: "/",
|
||||
LocalSort: true,
|
||||
CheckStatus: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &S3{
|
||||
config: driver.Config{
|
||||
Name: "Doge",
|
||||
DefaultRoot: "/",
|
||||
LocalSort: true,
|
||||
CheckStatus: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -21,13 +21,21 @@ import (
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
func (d *S3) initSession() error {
|
||||
var err error
|
||||
accessKeyID, secretAccessKey, sessionToken := d.AccessKeyID, d.SecretAccessKey, d.SessionToken
|
||||
if d.config.Name == "Doge" {
|
||||
credentialsTmp, err := getCredentials(d.AccessKeyID, d.SecretAccessKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accessKeyID, secretAccessKey, sessionToken = credentialsTmp.AccessKeyId, credentialsTmp.SecretAccessKey, credentialsTmp.SessionToken
|
||||
}
|
||||
cfg := &aws.Config{
|
||||
Credentials: credentials.NewStaticCredentials(d.AccessKeyID, d.SecretAccessKey, d.SessionToken),
|
||||
Credentials: credentials.NewStaticCredentials(accessKeyID, secretAccessKey, sessionToken),
|
||||
Region: &d.Region,
|
||||
Endpoint: &d.Endpoint,
|
||||
S3ForcePathStyle: aws.Bool(d.ForcePathStyle),
|
||||
}
|
||||
var err error
|
||||
d.Session, err = session.NewSession(cfg)
|
||||
return err
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -19,6 +18,7 @@ type Seafile struct {
|
||||
Addition
|
||||
|
||||
authorization string
|
||||
libraryMap map[string]*LibraryInfo
|
||||
}
|
||||
|
||||
func (d *Seafile) Config() driver.Config {
|
||||
@ -31,6 +31,8 @@ func (d *Seafile) GetAddition() driver.Additional {
|
||||
|
||||
func (d *Seafile) Init(ctx context.Context) error {
|
||||
d.Address = strings.TrimSuffix(d.Address, "/")
|
||||
d.RootFolderPath = utils.FixAndCleanPath(d.RootFolderPath)
|
||||
d.libraryMap = make(map[string]*LibraryInfo)
|
||||
return d.getToken()
|
||||
}
|
||||
|
||||
@ -38,10 +40,37 @@ func (d *Seafile) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Seafile) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
func (d *Seafile) List(ctx context.Context, dir model.Obj, args model.ListArgs) (result []model.Obj, err error) {
|
||||
path := dir.GetPath()
|
||||
if path == d.RootFolderPath {
|
||||
libraries, err := d.listLibraries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if path == "/" && d.RepoId == "" {
|
||||
return utils.SliceConvert(libraries, func(f LibraryItemResp) (model.Obj, error) {
|
||||
return &model.Object{
|
||||
Name: f.Name,
|
||||
Modified: time.Unix(f.Modified, 0),
|
||||
Size: f.Size,
|
||||
IsFolder: true,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
var repo *LibraryInfo
|
||||
repo, path, err = d.getRepoAndPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if repo.Encrypted {
|
||||
err = d.decryptLibrary(repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var resp []RepoDirItemResp
|
||||
_, err := d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/dir/", d.Addition.RepoId), func(req *resty.Request) {
|
||||
_, err = d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/dir/", repo.Id), func(req *resty.Request) {
|
||||
req.SetResult(&resp).SetQueryParams(map[string]string{
|
||||
"p": path,
|
||||
})
|
||||
@ -63,9 +92,13 @@ func (d *Seafile) List(ctx context.Context, dir model.Obj, args model.ListArgs)
|
||||
}
|
||||
|
||||
func (d *Seafile) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
res, err := d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
|
||||
repo, path, err := d.getRepoAndPath(file.GetPath())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/file/", repo.Id), func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"p": file.GetPath(),
|
||||
"p": path,
|
||||
"reuse": "1",
|
||||
})
|
||||
})
|
||||
@ -78,9 +111,14 @@ func (d *Seafile) Link(ctx context.Context, file model.Obj, args model.LinkArgs)
|
||||
}
|
||||
|
||||
func (d *Seafile) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
_, err := d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/dir/", d.Addition.RepoId), func(req *resty.Request) {
|
||||
repo, path, err := d.getRepoAndPath(parentDir.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path, _ = utils.JoinBasePath(path, dirName)
|
||||
_, err = d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/dir/", repo.Id), func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"p": filepath.Join(parentDir.GetPath(), dirName),
|
||||
"p": path,
|
||||
}).SetFormData(map[string]string{
|
||||
"operation": "mkdir",
|
||||
})
|
||||
@ -89,22 +127,34 @@ func (d *Seafile) MakeDir(ctx context.Context, parentDir model.Obj, dirName stri
|
||||
}
|
||||
|
||||
func (d *Seafile) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
_, err := d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
|
||||
repo, path, err := d.getRepoAndPath(srcObj.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstRepo, dstPath, err := d.getRepoAndPath(dstDir.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", repo.Id), func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"p": srcObj.GetPath(),
|
||||
"p": path,
|
||||
}).SetFormData(map[string]string{
|
||||
"operation": "move",
|
||||
"dst_repo": d.Addition.RepoId,
|
||||
"dst_dir": dstDir.GetPath(),
|
||||
"dst_repo": dstRepo.Id,
|
||||
"dst_dir": dstPath,
|
||||
})
|
||||
}, true)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Seafile) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
_, err := d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
|
||||
repo, path, err := d.getRepoAndPath(srcObj.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", repo.Id), func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"p": srcObj.GetPath(),
|
||||
"p": path,
|
||||
}).SetFormData(map[string]string{
|
||||
"operation": "rename",
|
||||
"newname": newName,
|
||||
@ -114,31 +164,47 @@ func (d *Seafile) Rename(ctx context.Context, srcObj model.Obj, newName string)
|
||||
}
|
||||
|
||||
func (d *Seafile) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
_, err := d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
|
||||
repo, path, err := d.getRepoAndPath(srcObj.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dstRepo, dstPath, err := d.getRepoAndPath(dstDir.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/file/", repo.Id), func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"p": srcObj.GetPath(),
|
||||
"p": path,
|
||||
}).SetFormData(map[string]string{
|
||||
"operation": "copy",
|
||||
"dst_repo": d.Addition.RepoId,
|
||||
"dst_dir": dstDir.GetPath(),
|
||||
"dst_repo": dstRepo.Id,
|
||||
"dst_dir": dstPath,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Seafile) Remove(ctx context.Context, obj model.Obj) error {
|
||||
_, err := d.request(http.MethodDelete, fmt.Sprintf("/api2/repos/%s/file/", d.Addition.RepoId), func(req *resty.Request) {
|
||||
repo, path, err := d.getRepoAndPath(obj.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.request(http.MethodDelete, fmt.Sprintf("/api2/repos/%s/file/", repo.Id), func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"p": obj.GetPath(),
|
||||
"p": path,
|
||||
})
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Seafile) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
res, err := d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/upload-link/", d.Addition.RepoId), func(req *resty.Request) {
|
||||
repo, path, err := d.getRepoAndPath(dstDir.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res, err := d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/upload-link/", repo.Id), func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"p": dstDir.GetPath(),
|
||||
"p": path,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
@ -150,7 +216,7 @@ func (d *Seafile) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
|
||||
_, err = d.request(http.MethodPost, u, func(req *resty.Request) {
|
||||
req.SetFileReader("file", stream.GetName(), stream).
|
||||
SetFormData(map[string]string{
|
||||
"parent_dir": dstDir.GetPath(),
|
||||
"parent_dir": path,
|
||||
"replace": "1",
|
||||
})
|
||||
})
|
||||
|
@ -11,7 +11,8 @@ type Addition struct {
|
||||
Address string `json:"address" required:"true"`
|
||||
UserName string `json:"username" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
RepoId string `json:"repoId" required:"true"`
|
||||
RepoId string `json:"repoId" required:"false"`
|
||||
RepoPwd string `json:"repoPwd" required:"false"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -1,14 +1,44 @@
|
||||
package seafile
|
||||
|
||||
import "time"
|
||||
|
||||
type AuthTokenResp struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type RepoDirItemResp struct {
|
||||
type RepoItemResp struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"` // dir, file
|
||||
Type string `json:"type"` // repo, dir, file
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Modified int64 `json:"mtime"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
|
||||
type LibraryItemResp struct {
|
||||
RepoItemResp
|
||||
OwnerContactEmail string `json:"owner_contact_email"`
|
||||
OwnerName string `json:"owner_name"`
|
||||
Owner string `json:"owner"`
|
||||
ModifierEmail string `json:"modifier_email"`
|
||||
ModifierContactEmail string `json:"modifier_contact_email"`
|
||||
ModifierName string `json:"modifier_name"`
|
||||
Virtual bool `json:"virtual"`
|
||||
MtimeRelative string `json:"mtime_relative"`
|
||||
Encrypted bool `json:"encrypted"`
|
||||
Version int `json:"version"`
|
||||
HeadCommitId string `json:"head_commit_id"`
|
||||
Root string `json:"root"`
|
||||
Salt string `json:"salt"`
|
||||
SizeFormatted string `json:"size_formatted"`
|
||||
}
|
||||
|
||||
type RepoDirItemResp struct {
|
||||
RepoItemResp
|
||||
}
|
||||
|
||||
type LibraryInfo struct {
|
||||
LibraryItemResp
|
||||
decryptedTime time.Time
|
||||
decryptedSuccess bool
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
package seafile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/go-resty/resty/v2"
|
||||
@ -60,3 +65,110 @@ func (d *Seafile) request(method string, pathname string, callback base.ReqCallb
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *Seafile) getRepoAndPath(fullPath string) (repo *LibraryInfo, path string, err error) {
|
||||
libraryMap := d.libraryMap
|
||||
repoId := d.Addition.RepoId
|
||||
if repoId != "" {
|
||||
if len(repoId) == 36 /* uuid */ {
|
||||
for _, library := range libraryMap {
|
||||
if library.Id == repoId {
|
||||
return library, fullPath, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var repoName string
|
||||
str := fullPath[1:]
|
||||
pos := strings.IndexRune(str, '/')
|
||||
if pos == -1 {
|
||||
repoName = str
|
||||
} else {
|
||||
repoName = str[:pos]
|
||||
}
|
||||
path = utils.FixAndCleanPath(fullPath[1+len(repoName):])
|
||||
if library, ok := libraryMap[repoName]; ok {
|
||||
return library, path, nil
|
||||
}
|
||||
}
|
||||
return nil, "", errs.ObjectNotFound
|
||||
}
|
||||
|
||||
func (d *Seafile) listLibraries() (resp []LibraryItemResp, err error) {
|
||||
repoId := d.Addition.RepoId
|
||||
if repoId == "" {
|
||||
_, err = d.request(http.MethodGet, "/api2/repos/", func(req *resty.Request) {
|
||||
req.SetResult(&resp)
|
||||
})
|
||||
} else {
|
||||
var oneResp LibraryItemResp
|
||||
_, err = d.request(http.MethodGet, fmt.Sprintf("/api2/repos/%s/", repoId), func(req *resty.Request) {
|
||||
req.SetResult(&oneResp)
|
||||
})
|
||||
if err == nil {
|
||||
resp = append(resp, oneResp)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
libraryMap := make(map[string]*LibraryInfo)
|
||||
var putLibraryMap func(library LibraryItemResp, index int)
|
||||
putLibraryMap = func(library LibraryItemResp, index int) {
|
||||
name := library.Name
|
||||
if index > 0 {
|
||||
name = fmt.Sprintf("%s (%d)", name, index)
|
||||
}
|
||||
if _, exist := libraryMap[name]; exist {
|
||||
putLibraryMap(library, index+1)
|
||||
} else {
|
||||
libraryInfo := LibraryInfo{}
|
||||
data, _ := utils.Json.Marshal(library)
|
||||
_ = utils.Json.Unmarshal(data, &libraryInfo)
|
||||
libraryMap[name] = &libraryInfo
|
||||
}
|
||||
}
|
||||
for _, library := range resp {
|
||||
putLibraryMap(library, 0)
|
||||
}
|
||||
d.libraryMap = libraryMap
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var repoPwdNotConfigured = errors.New("library password not configured")
|
||||
var repoPwdIncorrect = errors.New("library password is incorrect")
|
||||
|
||||
func (d *Seafile) decryptLibrary(repo *LibraryInfo) (err error) {
|
||||
if !repo.Encrypted {
|
||||
return nil
|
||||
}
|
||||
if d.RepoPwd == "" {
|
||||
return repoPwdNotConfigured
|
||||
}
|
||||
now := time.Now()
|
||||
decryptedTime := repo.decryptedTime
|
||||
if repo.decryptedSuccess {
|
||||
if now.Sub(decryptedTime).Minutes() <= 30 {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if now.Sub(decryptedTime).Seconds() <= 10 {
|
||||
return repoPwdIncorrect
|
||||
}
|
||||
}
|
||||
var resp string
|
||||
_, err = d.request(http.MethodPost, fmt.Sprintf("/api2/repos/%s/", repo.Id), func(req *resty.Request) {
|
||||
req.SetResult(&resp).SetFormData(map[string]string{
|
||||
"password": d.RepoPwd,
|
||||
})
|
||||
})
|
||||
repo.decryptedTime = time.Now()
|
||||
if err != nil || !strings.Contains(resp, "success") {
|
||||
repo.decryptedSuccess = false
|
||||
return err
|
||||
}
|
||||
repo.decryptedSuccess = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
|
@ -11,6 +11,7 @@ type Addition struct {
|
||||
PrivateKey string `json:"private_key" type:"text"`
|
||||
Password string `json:"password"`
|
||||
driver.RootPath
|
||||
IgnoreSymlinkError bool `json:"ignore_symlink_error" default:"false" info:"Ignore symlink error"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -30,6 +30,14 @@ func (d *SFTP) fileToObj(f os.FileInfo, dir string) (model.Obj, error) {
|
||||
}
|
||||
_f, err := d.client.Stat(target)
|
||||
if err != nil {
|
||||
if d.IgnoreSymlinkError {
|
||||
return &model.Object{
|
||||
Name: f.Name(),
|
||||
Size: f.Size(),
|
||||
Modified: f.ModTime(),
|
||||
IsFolder: f.IsDir(),
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
// set basic info
|
||||
|
@ -3,12 +3,12 @@ package teambition
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"net/http"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
@ -128,11 +128,23 @@ func (d *Teambition) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
if d.UseS3UploadMethod {
|
||||
return d.newUpload(ctx, dstDir, stream, up)
|
||||
}
|
||||
res, err := d.request("/api/v2/users/me", http.MethodGet, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
var (
|
||||
token string
|
||||
err error
|
||||
)
|
||||
if d.isInternational() {
|
||||
res, err := d.request("/projects", http.MethodGet, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token = getBetweenStr(string(res), "strikerAuth":"", "","phoneForLogin")
|
||||
} else {
|
||||
res, err := d.request("/api/v2/users/me", http.MethodGet, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token = utils.Json.Get(res, "strikerAuth").ToString()
|
||||
}
|
||||
token := utils.Json.Get(res, "strikerAuth").ToString()
|
||||
var newFile *FileUpload
|
||||
if stream.GetSize() <= 20971520 {
|
||||
// post upload
|
||||
|
18
drivers/teambition/help.go
Normal file
18
drivers/teambition/help.go
Normal file
@ -0,0 +1,18 @@
|
||||
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
|
||||
}
|
@ -126,19 +126,20 @@ func (d *Teambition) upload(ctx context.Context, file model.FileStreamer, token
|
||||
prefix = "us-tcs"
|
||||
}
|
||||
var newFile FileUpload
|
||||
_, err := base.RestyClient.R().
|
||||
res, err := base.RestyClient.R().
|
||||
SetContext(ctx).
|
||||
SetResult(&newFile).SetHeader("Authorization", token).
|
||||
SetMultipartFormData(map[string]string{
|
||||
"name": file.GetName(),
|
||||
"type": file.GetMimetype(),
|
||||
"size": strconv.FormatInt(file.GetSize(), 10),
|
||||
//"lastModifiedDate": "",
|
||||
"name": file.GetName(),
|
||||
"type": file.GetMimetype(),
|
||||
"size": strconv.FormatInt(file.GetSize(), 10),
|
||||
"lastModifiedDate": time.Now().Format("Mon Jan 02 2006 15:04:05 GMT+0800 (中国标准时间)"),
|
||||
}).SetMultipartField("file", file.GetName(), file.GetMimetype(), file).
|
||||
Post(fmt.Sprintf("https://%s.teambition.net/upload", prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("[teambition] upload response: %s", res.String())
|
||||
return &newFile, nil
|
||||
}
|
||||
|
||||
@ -243,6 +244,9 @@ func (d *Teambition) newUpload(ctx context.Context, dstDir model.Obj, stream mod
|
||||
return err
|
||||
}
|
||||
uploader := s3manager.NewUploader(ss)
|
||||
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &uploadToken.Upload.Bucket,
|
||||
Key: &uploadToken.Upload.Key,
|
||||
|
@ -373,7 +373,11 @@ func (xc *XunLeiCommon) Put(ctx context.Context, dstDir model.Obj, stream model.
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s3manager.NewUploader(s).UploadWithContext(ctx, &s3manager.UploadInput{
|
||||
uploader := s3manager.NewUploader(s)
|
||||
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
_, err = uploader.UploadWithContext(ctx, &s3manager.UploadInput{
|
||||
Bucket: aws.String(param.Bucket),
|
||||
Key: aws.String(param.Key),
|
||||
Expires: aws.Time(param.Expiration),
|
||||
|
208
drivers/vtencent/drive.go
Normal file
208
drivers/vtencent/drive.go
Normal file
@ -0,0 +1,208 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/cron"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type Vtencent struct {
|
||||
model.Storage
|
||||
Addition
|
||||
cron *cron.Cron
|
||||
config driver.Config
|
||||
conf Conf
|
||||
}
|
||||
|
||||
func (d *Vtencent) Config() driver.Config {
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *Vtencent) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Vtencent) Init(ctx context.Context) error {
|
||||
tfUid, err := d.LoadUser()
|
||||
if err != nil {
|
||||
d.Status = err.Error()
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
d.Addition.TfUid = tfUid
|
||||
op.MustSaveDriverStorage(d)
|
||||
d.cron = cron.NewCron(time.Hour * 12)
|
||||
d.cron.Do(func() {
|
||||
_, err := d.LoadUser()
|
||||
if err != nil {
|
||||
d.Status = err.Error()
|
||||
op.MustSaveDriverStorage(d)
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) Drop(ctx context.Context) error {
|
||||
d.cron.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.GetFiles(dir.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||
return fileToObj(src), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Vtencent) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
form := fmt.Sprintf(`{"MaterialIds":["%s"]}`, file.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resps RspDown
|
||||
api := "https://api.vs.tencent.com/SaaS/Material/DescribeMaterialDownloadUrl"
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(rsp, &resps); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resps.Data.DownloadURLInfoSet) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
u := resps.Data.DownloadURLInfoSet[0].DownloadURL
|
||||
link := &model.Link{
|
||||
URL: u,
|
||||
Header: http.Header{
|
||||
"Referer": []string{d.conf.referer},
|
||||
"User-Agent": []string{d.conf.ua},
|
||||
},
|
||||
Concurrency: 2,
|
||||
PartSize: 10 * utils.MB,
|
||||
}
|
||||
if file.GetSize() == 0 {
|
||||
link.Concurrency = 0
|
||||
link.PartSize = 0
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
classId, err := strconv.Atoi(parentDir.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.request("https://api.vs.tencent.com/PaaS/Material/CreateClass", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"Owner": base.Json{
|
||||
"Type": "PERSON",
|
||||
"Id": d.TfUid,
|
||||
},
|
||||
"ParentClassId": classId,
|
||||
"Name": dirName,
|
||||
"VerifySign": ""})
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
srcType := "MATERIAL"
|
||||
if srcObj.IsDir() {
|
||||
srcType = "CLASS"
|
||||
}
|
||||
form := fmt.Sprintf(`{"SourceInfos":[
|
||||
{"Owner":{"Id":"%s","Type":"PERSON"},
|
||||
"Resource":{"Type":"%s","Id":"%s"}}
|
||||
],
|
||||
"Destination":{"Owner":{"Id":"%s","Type":"PERSON"},
|
||||
"Resource":{"Type":"CLASS","Id":"%s"}}
|
||||
}`, d.TfUid, srcType, srcObj.GetID(), d.TfUid, dstDir.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request("https://api.vs.tencent.com/PaaS/Material/MoveResource", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/ModifyMaterial"
|
||||
form := fmt.Sprintf(`{
|
||||
"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"MaterialId":"%s","Name":"%s"}`, d.TfUid, srcObj.GetID(), newName)
|
||||
if srcObj.IsDir() {
|
||||
classId, err := strconv.Atoi(srcObj.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
api = "https://api.vs.tencent.com/PaaS/Material/ModifyClass"
|
||||
form = fmt.Sprintf(`{"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"ClassId":%d,"Name":"%s"}`, d.TfUid, classId, newName)
|
||||
}
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
// TODO copy obj, optional
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *Vtencent) Remove(ctx context.Context, obj model.Obj) error {
|
||||
srcType := "MATERIAL"
|
||||
if obj.IsDir() {
|
||||
srcType = "CLASS"
|
||||
}
|
||||
form := fmt.Sprintf(`{
|
||||
"SourceInfos":[
|
||||
{"Owner":{"Type":"PERSON","Id":"%s"},
|
||||
"Resource":{"Type":"%s","Id":"%s"}}
|
||||
]
|
||||
}`, d.TfUid, srcType, obj.GetID())
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(form), &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := d.request("https://api.vs.tencent.com/PaaS/Material/DeleteResource", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(dat)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Vtencent) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
err := d.FileUpload(ctx, dstDir, stream, up)
|
||||
return err
|
||||
}
|
||||
|
||||
//func (d *Vtencent) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*Vtencent)(nil)
|
39
drivers/vtencent/meta.go
Normal file
39
drivers/vtencent/meta.go
Normal file
@ -0,0 +1,39 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootID
|
||||
Cookie string `json:"cookie" required:"true"`
|
||||
TfUid string `json:"tf_uid"`
|
||||
OrderBy string `json:"order_by" type:"select" options:"Name,Size,UpdateTime,CreatTime"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"Asc,Desc"`
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
ua string
|
||||
referer string
|
||||
origin string
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Vtencent{
|
||||
config: driver.Config{
|
||||
Name: "VTencent",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "9",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
conf: Conf{
|
||||
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
|
||||
referer: "https://app.v.tencent.com/",
|
||||
origin: "https://app.v.tencent.com",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
33
drivers/vtencent/signature.go
Normal file
33
drivers/vtencent/signature.go
Normal file
@ -0,0 +1,33 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func QSignatureKey(timeKey string, signPath string, key string) string {
|
||||
signKey := hmac.New(sha1.New, []byte(key))
|
||||
signKey.Write([]byte(timeKey))
|
||||
signKeyBytes := signKey.Sum(nil)
|
||||
signKeyHex := hex.EncodeToString(signKeyBytes)
|
||||
sha := sha1.New()
|
||||
sha.Write([]byte(signPath))
|
||||
shaBytes := sha.Sum(nil)
|
||||
shaHex := hex.EncodeToString(shaBytes)
|
||||
|
||||
O := "sha1\n" + timeKey + "\n" + shaHex + "\n"
|
||||
dataSignKey := hmac.New(sha1.New, []byte(signKeyHex))
|
||||
dataSignKey.Write([]byte(O))
|
||||
dataSignKeyBytes := dataSignKey.Sum(nil)
|
||||
dataSignKeyHex := hex.EncodeToString(dataSignKeyBytes)
|
||||
return dataSignKeyHex
|
||||
}
|
||||
|
||||
func QTwoSignatureKey(timeKey string, key string) string {
|
||||
signKey := hmac.New(sha1.New, []byte(key))
|
||||
signKey.Write([]byte(timeKey))
|
||||
signKeyBytes := signKey.Sum(nil)
|
||||
signKeyHex := hex.EncodeToString(signKeyBytes)
|
||||
return signKeyHex
|
||||
}
|
252
drivers/vtencent/types.go
Normal file
252
drivers/vtencent/types.go
Normal file
@ -0,0 +1,252 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type RespErr struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
}
|
||||
|
||||
type Reqfiles struct {
|
||||
ScrollToken string `json:"ScrollToken"`
|
||||
Text string `json:"Text"`
|
||||
Offset int `json:"Offset"`
|
||||
Limit int `json:"Limit"`
|
||||
Sort struct {
|
||||
Field string `json:"Field"`
|
||||
Order string `json:"Order"`
|
||||
} `json:"Sort"`
|
||||
CreateTimeRanges []any `json:"CreateTimeRanges"`
|
||||
MaterialTypes []any `json:"MaterialTypes"`
|
||||
ReviewStatuses []any `json:"ReviewStatuses"`
|
||||
Tags []any `json:"Tags"`
|
||||
SearchScopes []struct {
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
ClassID int `json:"ClassId"`
|
||||
SearchOneDepth bool `json:"SearchOneDepth"`
|
||||
} `json:"SearchScopes"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Type string `json:"Type"`
|
||||
ClassInfo struct {
|
||||
ClassID int `json:"ClassId"`
|
||||
Name string `json:"Name"`
|
||||
UpdateTime time.Time `json:"UpdateTime"`
|
||||
CreateTime time.Time `json:"CreateTime"`
|
||||
FileInboxID string `json:"FileInboxId"`
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
ClassPath string `json:"ClassPath"`
|
||||
ParentClassID int `json:"ParentClassId"`
|
||||
AttachmentInfo struct {
|
||||
SubClassCount int `json:"SubClassCount"`
|
||||
MaterialCount int `json:"MaterialCount"`
|
||||
Size int64 `json:"Size"`
|
||||
} `json:"AttachmentInfo"`
|
||||
ClassPreviewURLSet []string `json:"ClassPreviewUrlSet"`
|
||||
} `json:"ClassInfo"`
|
||||
MaterialInfo struct {
|
||||
BasicInfo struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
MaterialType string `json:"MaterialType"`
|
||||
Name string `json:"Name"`
|
||||
CreateTime time.Time `json:"CreateTime"`
|
||||
UpdateTime time.Time `json:"UpdateTime"`
|
||||
ClassPath string `json:"ClassPath"`
|
||||
ClassID int `json:"ClassId"`
|
||||
TagInfoSet []any `json:"TagInfoSet"`
|
||||
TagSet []any `json:"TagSet"`
|
||||
PreviewURL string `json:"PreviewUrl"`
|
||||
MediaURL string `json:"MediaUrl"`
|
||||
UnifiedMediaPreviewURL string `json:"UnifiedMediaPreviewUrl"`
|
||||
Owner struct {
|
||||
Type string `json:"Type"`
|
||||
ID string `json:"Id"`
|
||||
} `json:"Owner"`
|
||||
PermissionSet any `json:"PermissionSet"`
|
||||
PermissionInfoSet []any `json:"PermissionInfoSet"`
|
||||
TfUID string `json:"TfUid"`
|
||||
GroupID string `json:"GroupId"`
|
||||
VersionMaterialIDSet []any `json:"VersionMaterialIdSet"`
|
||||
FileType string `json:"FileType"`
|
||||
CmeMaterialPlayList []any `json:"CmeMaterialPlayList"`
|
||||
Status string `json:"Status"`
|
||||
DownloadSwitch string `json:"DownloadSwitch"`
|
||||
} `json:"BasicInfo"`
|
||||
MediaInfo struct {
|
||||
Width int `json:"Width"`
|
||||
Height int `json:"Height"`
|
||||
Size int `json:"Size"`
|
||||
Duration float64 `json:"Duration"`
|
||||
Fps int `json:"Fps"`
|
||||
BitRate int `json:"BitRate"`
|
||||
Codec string `json:"Codec"`
|
||||
MediaType string `json:"MediaType"`
|
||||
FavoriteStatus string `json:"FavoriteStatus"`
|
||||
} `json:"MediaInfo"`
|
||||
MaterialStatus struct {
|
||||
ContentReviewStatus string `json:"ContentReviewStatus"`
|
||||
EditorUsableStatus string `json:"EditorUsableStatus"`
|
||||
UnifiedPreviewStatus string `json:"UnifiedPreviewStatus"`
|
||||
EditPreviewImageSpiritStatus string `json:"EditPreviewImageSpiritStatus"`
|
||||
TranscodeStatus string `json:"TranscodeStatus"`
|
||||
AdaptiveStreamingStatus string `json:"AdaptiveStreamingStatus"`
|
||||
StreamConnectable string `json:"StreamConnectable"`
|
||||
AiAnalysisStatus string `json:"AiAnalysisStatus"`
|
||||
AiRecognitionStatus string `json:"AiRecognitionStatus"`
|
||||
} `json:"MaterialStatus"`
|
||||
ImageMaterial struct {
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Size int `json:"Size"`
|
||||
MaterialURL string `json:"MaterialUrl"`
|
||||
Resolution string `json:"Resolution"`
|
||||
VodFileID string `json:"VodFileId"`
|
||||
OriginalURL string `json:"OriginalUrl"`
|
||||
} `json:"ImageMaterial"`
|
||||
VideoMaterial struct {
|
||||
MetaData struct {
|
||||
Size int `json:"Size"`
|
||||
Container string `json:"Container"`
|
||||
Bitrate int `json:"Bitrate"`
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Duration float64 `json:"Duration"`
|
||||
Rotate int `json:"Rotate"`
|
||||
VideoStreamInfoSet []struct {
|
||||
Bitrate int `json:"Bitrate"`
|
||||
Height int `json:"Height"`
|
||||
Width int `json:"Width"`
|
||||
Codec string `json:"Codec"`
|
||||
Fps int `json:"Fps"`
|
||||
} `json:"VideoStreamInfoSet"`
|
||||
AudioStreamInfoSet []struct {
|
||||
Bitrate int `json:"Bitrate"`
|
||||
SamplingRate int `json:"SamplingRate"`
|
||||
Codec string `json:"Codec"`
|
||||
} `json:"AudioStreamInfoSet"`
|
||||
} `json:"MetaData"`
|
||||
ImageSpriteInfo any `json:"ImageSpriteInfo"`
|
||||
MaterialURL string `json:"MaterialUrl"`
|
||||
CoverURL string `json:"CoverUrl"`
|
||||
Resolution string `json:"Resolution"`
|
||||
VodFileID string `json:"VodFileId"`
|
||||
OriginalURL string `json:"OriginalUrl"`
|
||||
AudioWaveformURL string `json:"AudioWaveformUrl"`
|
||||
SubtitleURL string `json:"SubtitleUrl"`
|
||||
TranscodeInfoSet []any `json:"TranscodeInfoSet"`
|
||||
ImageSpriteInfoSet []any `json:"ImageSpriteInfoSet"`
|
||||
} `json:"VideoMaterial"`
|
||||
} `json:"MaterialInfo"`
|
||||
}
|
||||
|
||||
type RspFiles struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
TotalCount int `json:"TotalCount"`
|
||||
ResourceInfoSet []File `json:"ResourceInfoSet"`
|
||||
ScrollToken string `json:"ScrollToken"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspDown struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
DownloadURLInfoSet []struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
DownloadURL string `json:"DownloadUrl"`
|
||||
} `json:"DownloadUrlInfoSet"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspCreatrMaterial struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
UploadContext string `json:"UploadContext"`
|
||||
VodUploadSign string `json:"VodUploadSign"`
|
||||
QuickUpload bool `json:"QuickUpload"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
type RspApplyUploadUGC struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Video struct {
|
||||
StorageSignature string `json:"storageSignature"`
|
||||
StoragePath string `json:"storagePath"`
|
||||
} `json:"video"`
|
||||
StorageAppID int `json:"storageAppId"`
|
||||
StorageBucket string `json:"storageBucket"`
|
||||
StorageRegion string `json:"storageRegion"`
|
||||
StorageRegionV5 string `json:"storageRegionV5"`
|
||||
Domain string `json:"domain"`
|
||||
VodSessionKey string `json:"vodSessionKey"`
|
||||
TempCertificate struct {
|
||||
SecretID string `json:"secretId"`
|
||||
SecretKey string `json:"secretKey"`
|
||||
Token string `json:"token"`
|
||||
ExpiredTime int `json:"expiredTime"`
|
||||
} `json:"tempCertificate"`
|
||||
AppID int `json:"appId"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
StorageRegionV50 string `json:"StorageRegionV5"`
|
||||
MiniProgramAccelerateHost string `json:"MiniProgramAccelerateHost"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type RspCommitUploadUGC struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Video struct {
|
||||
URL string `json:"url"`
|
||||
VerifyContent string `json:"verify_content"`
|
||||
} `json:"video"`
|
||||
FileID string `json:"fileId"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type RspFinishUpload struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
EnglishMessage string `json:"EnglishMessage"`
|
||||
Data struct {
|
||||
MaterialID string `json:"MaterialId"`
|
||||
} `json:"Data"`
|
||||
}
|
||||
|
||||
func fileToObj(f File) *model.Object {
|
||||
obj := &model.Object{}
|
||||
if f.Type == "CLASS" {
|
||||
obj.Name = f.ClassInfo.Name
|
||||
obj.ID = strconv.Itoa(f.ClassInfo.ClassID)
|
||||
obj.IsFolder = true
|
||||
obj.Modified = f.ClassInfo.CreateTime
|
||||
obj.Size = 0
|
||||
} else if f.Type == "MATERIAL" {
|
||||
obj.Name = f.MaterialInfo.BasicInfo.Name
|
||||
obj.ID = f.MaterialInfo.BasicInfo.MaterialID
|
||||
obj.IsFolder = false
|
||||
obj.Modified = f.MaterialInfo.BasicInfo.CreateTime
|
||||
obj.Size = int64(f.MaterialInfo.MediaInfo.Size)
|
||||
}
|
||||
return obj
|
||||
}
|
300
drivers/vtencent/util.go
Normal file
300
drivers/vtencent/util.go
Normal file
@ -0,0 +1,300 @@
|
||||
package vtencent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/http_range"
|
||||
"github.com/alist-org/alist/v3/pkg/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/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
func (d *Vtencent) request(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"cookie": d.Cookie,
|
||||
"content-type": "application/json",
|
||||
"origin": d.conf.origin,
|
||||
"referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
} else {
|
||||
req.SetBody("{}")
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code := utils.Json.Get(res.Body(), "Code").ToString()
|
||||
if code != "Success" {
|
||||
switch code {
|
||||
case "AuthFailure.SessionInvalid":
|
||||
if err != nil {
|
||||
return nil, errors.New(code)
|
||||
}
|
||||
default:
|
||||
return nil, errors.New(code)
|
||||
}
|
||||
return d.request(url, method, callback, resp)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) ugcRequest(url, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"cookie": d.Cookie,
|
||||
"content-type": "application/json",
|
||||
"origin": d.conf.origin,
|
||||
"referer": d.conf.referer,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
} else {
|
||||
req.SetBody("{}")
|
||||
}
|
||||
if resp != nil {
|
||||
req.SetResult(resp)
|
||||
}
|
||||
res, err := req.Execute(method, url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
code := utils.Json.Get(res.Body(), "Code").ToInt()
|
||||
if code != 0 {
|
||||
message := utils.Json.Get(res.Body(), "message").ToString()
|
||||
if len(message) == 0 {
|
||||
message = utils.Json.Get(res.Body(), "msg").ToString()
|
||||
}
|
||||
return nil, errors.New(message)
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) LoadUser() (string, error) {
|
||||
api := "https://api.vs.tencent.com/SaaS/Account/DescribeAccount"
|
||||
res, err := d.request(api, http.MethodPost, func(req *resty.Request) {}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return utils.Json.Get(res, "Data", "TfUid").ToString(), nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) GetFiles(dirId string) ([]File, error) {
|
||||
var res []File
|
||||
//offset := 0
|
||||
for {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/SearchResource"
|
||||
form := fmt.Sprintf(`{
|
||||
"Text":"",
|
||||
"Text":"",
|
||||
"Offset":%d,
|
||||
"Limit":50,
|
||||
"Sort":{"Field":"%s","Order":"%s"},
|
||||
"CreateTimeRanges":[],
|
||||
"MaterialTypes":[],
|
||||
"ReviewStatuses":[],
|
||||
"Tags":[],
|
||||
"SearchScopes":[{"Owner":{"Type":"PERSON","Id":"%s"},"ClassId":%s,"SearchOneDepth":true}]
|
||||
}`, len(res), d.Addition.OrderBy, d.Addition.OrderDirection, d.TfUid, dirId)
|
||||
var resp RspFiles
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, resp.Data.ResourceInfoSet...)
|
||||
if len(resp.Data.ResourceInfoSet) <= 0 || len(res) >= resp.Data.TotalCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) CreateUploadMaterial(classId int, fileName string, UploadSummaryKey string) (RspCreatrMaterial, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/CreateUploadMaterial"
|
||||
form := base.Json{"Owner": base.Json{"Type": "PERSON", "Id": d.TfUid},
|
||||
"MaterialType": "VIDEO", "Name": fileName, "ClassId": classId,
|
||||
"UploadSummaryKey": UploadSummaryKey}
|
||||
var resps RspCreatrMaterial
|
||||
_, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspCreatrMaterial{}, err
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) ApplyUploadUGC(signature string, stream model.FileStreamer) (RspApplyUploadUGC, error) {
|
||||
api := "https://vod2.qcloud.com/v3/index.php?Action=ApplyUploadUGC"
|
||||
form := base.Json{
|
||||
"signature": signature,
|
||||
"videoName": stream.GetName(),
|
||||
"videoType": strings.ReplaceAll(path.Ext(stream.GetName()), ".", ""),
|
||||
"videoSize": stream.GetSize(),
|
||||
}
|
||||
var resps RspApplyUploadUGC
|
||||
_, err := d.ugcRequest(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspApplyUploadUGC{}, err
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) CommitUploadUGC(signature string, vodSessionKey string) (RspCommitUploadUGC, error) {
|
||||
api := "https://vod2.qcloud.com/v3/index.php?Action=CommitUploadUGC"
|
||||
form := base.Json{
|
||||
"signature": signature,
|
||||
"vodSessionKey": vodSessionKey,
|
||||
}
|
||||
var resps RspCommitUploadUGC
|
||||
rsp, err := d.ugcRequest(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspCommitUploadUGC{}, err
|
||||
}
|
||||
if len(resps.Data.Video.URL) == 0 {
|
||||
return RspCommitUploadUGC{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FinishUploadMaterial(SummaryKey string, VodVerifyKey string, UploadContext, VodFileId string) (RspFinishUpload, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/FinishUploadMaterial"
|
||||
form := base.Json{
|
||||
"UploadContext": UploadContext,
|
||||
"VodVerifyKey": VodVerifyKey,
|
||||
"VodFileId": VodFileId,
|
||||
"UploadFullKey": SummaryKey}
|
||||
var resps RspFinishUpload
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspFinishUpload{}, err
|
||||
}
|
||||
if len(resps.Data.MaterialID) == 0 {
|
||||
return RspFinishUpload{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FinishHashUploadMaterial(SummaryKey string, UploadContext string) (RspFinishUpload, error) {
|
||||
api := "https://api.vs.tencent.com/PaaS/Material/FinishUploadMaterial"
|
||||
var resps RspFinishUpload
|
||||
form := base.Json{
|
||||
"UploadContext": UploadContext,
|
||||
"UploadFullKey": SummaryKey}
|
||||
rsp, err := d.request(api, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(form).ForceContentType("application/json")
|
||||
}, &resps)
|
||||
if err != nil {
|
||||
return RspFinishUpload{}, err
|
||||
}
|
||||
if len(resps.Data.MaterialID) == 0 {
|
||||
return RspFinishUpload{}, errors.New(string(rsp))
|
||||
}
|
||||
return resps, nil
|
||||
}
|
||||
|
||||
func (d *Vtencent) FileUpload(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
classId, err := strconv.Atoi(dstDir.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
const chunkLength int64 = 1024 * 1024 * 10
|
||||
reader, err := stream.RangeRead(http_range.Range{Start: 0, Length: chunkLength})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chunkHash, err := utils.HashReader(utils.SHA1, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rspCreatrMaterial, err := d.CreateUploadMaterial(classId, stream.GetName(), chunkHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rspCreatrMaterial.Data.QuickUpload {
|
||||
SummaryKey := stream.GetHash().GetHash(utils.SHA1)
|
||||
if len(SummaryKey) < utils.SHA1.Width {
|
||||
if SummaryKey, err = utils.HashReader(utils.SHA1, stream); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
UploadContext := rspCreatrMaterial.Data.UploadContext
|
||||
_, err = d.FinishHashUploadMaterial(SummaryKey, UploadContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
hash := sha1.New()
|
||||
rspUGC, err := d.ApplyUploadUGC(rspCreatrMaterial.Data.VodUploadSign, stream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := rspUGC.Data
|
||||
certificate := params.TempCertificate
|
||||
cfg := &aws.Config{
|
||||
HTTPClient: base.HttpClient,
|
||||
// S3ForcePathStyle: aws.Bool(true),
|
||||
Credentials: credentials.NewStaticCredentials(certificate.SecretID, certificate.SecretKey, certificate.Token),
|
||||
Region: aws.String(params.StorageRegionV5),
|
||||
Endpoint: aws.String(fmt.Sprintf("cos.%s.myqcloud.com", params.StorageRegionV5)),
|
||||
}
|
||||
ss, err := session.NewSession(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uploader := s3manager.NewUploader(ss)
|
||||
if stream.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: aws.String(fmt.Sprintf("%s-%d", params.StorageBucket, params.StorageAppID)),
|
||||
Key: ¶ms.Video.StoragePath,
|
||||
Body: io.TeeReader(stream, io.MultiWriter(hash, driver.NewProgress(stream.GetSize(), up))),
|
||||
}
|
||||
_, err = uploader.UploadWithContext(ctx, input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rspCommitUGC, err := d.CommitUploadUGC(rspCreatrMaterial.Data.VodUploadSign, rspUGC.Data.VodSessionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
VodVerifyKey := rspCommitUGC.Data.Video.VerifyContent
|
||||
VodFileId := rspCommitUGC.Data.FileID
|
||||
UploadContext := rspCreatrMaterial.Data.UploadContext
|
||||
SummaryKey := hex.EncodeToString(hash.Sum(nil))
|
||||
_, err = d.FinishUploadMaterial(SummaryKey, VodVerifyKey, UploadContext, VodFileId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
@ -11,6 +11,7 @@ type Addition struct {
|
||||
Username string `json:"username" required:"true"`
|
||||
Password string `json:"password" required:"true"`
|
||||
driver.RootPath
|
||||
TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" default:"false"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
|
@ -1,6 +1,7 @@
|
||||
package webdav
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
|
||||
@ -17,6 +18,10 @@ func (d *WebDav) isSharepoint() bool {
|
||||
|
||||
func (d *WebDav) setClient() error {
|
||||
c := gowebdav.NewClient(d.Address, d.Username, d.Password)
|
||||
c.SetTransport(&http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: d.TlsInsecureSkipVerify},
|
||||
})
|
||||
if d.isSharepoint() {
|
||||
cookie, err := odrvcookie.GetCookie(d.Username, d.Password, d.Address)
|
||||
if err == nil {
|
||||
|
@ -4,4 +4,8 @@ chown -R ${PUID}:${PGID} /opt/alist/
|
||||
|
||||
umask ${UMASK}
|
||||
|
||||
exec su-exec ${PUID}:${PGID} ./alist server --no-prefix
|
||||
if [ "$1" = "version" ]; then
|
||||
./alist version
|
||||
else
|
||||
exec su-exec ${PUID}:${PGID} ./alist server --no-prefix
|
||||
fi
|
101
go.mod
101
go.mod
@ -1,58 +1,67 @@
|
||||
module github.com/alist-org/alist/v3
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/SheltonZhu/115driver v1.0.16
|
||||
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700
|
||||
github.com/SheltonZhu/115driver v1.0.22
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.2
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/aws/aws-sdk-go v1.44.327
|
||||
github.com/aws/aws-sdk-go v1.50.24
|
||||
github.com/blevesearch/bleve/v2 v2.3.10
|
||||
github.com/caarlos0/env/v9 v9.0.0
|
||||
github.com/charmbracelet/bubbles v0.16.1
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/bubbles v0.17.1
|
||||
github.com/charmbracelet/bubbletea v0.25.0
|
||||
github.com/charmbracelet/lipgloss v0.9.1
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/deckarep/golang-set/v2 v2.3.1
|
||||
github.com/deckarep/golang-set/v2 v2.6.0
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/djherbis/times v1.5.0
|
||||
github.com/djherbis/times v1.6.0
|
||||
github.com/dlclark/regexp2 v1.10.0
|
||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.4
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.2
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.5
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3
|
||||
github.com/gaoyb7/115drive-webdav v0.1.8
|
||||
github.com/gin-contrib/cors v1.5.0
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-resty/resty/v2 v2.9.1
|
||||
github.com/go-webauthn/webauthn v0.8.6
|
||||
github.com/go-resty/resty/v2 v2.11.0
|
||||
github.com/go-webauthn/webauthn v0.10.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.3.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/google/uuid v1.5.0
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/hirochachacha/go-smb2 v1.1.0
|
||||
github.com/ipfs/go-ipfs-api v0.7.0
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/maruel/natural v1.1.0
|
||||
github.com/maruel/natural v1.1.1
|
||||
github.com/meilisearch/meilisearch-go v0.26.1
|
||||
github.com/minio/sio v0.3.0
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
github.com/ncw/swift/v2 v2.0.2
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.6
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/rclone/rclone v1.63.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7
|
||||
github.com/u2takey/ffmpeg-go v0.5.0
|
||||
github.com/upyun/go-sdk/v3 v3.0.4
|
||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5
|
||||
golang.org/x/crypto v0.14.0
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d
|
||||
golang.org/x/image v0.11.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/oauth2 v0.12.0
|
||||
golang.org/x/time v0.3.0
|
||||
github.com/xhofe/tache v0.1.1
|
||||
golang.org/x/crypto v0.19.0
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/net v0.21.0
|
||||
golang.org/x/oauth2 v0.16.0
|
||||
golang.org/x/time v0.5.0
|
||||
google.golang.org/appengine v1.6.8
|
||||
gopkg.in/ldap.v3 v3.1.0
|
||||
gorm.io/driver/mysql v1.4.7
|
||||
gorm.io/driver/postgres v1.4.8
|
||||
gorm.io/driver/sqlite v1.4.4
|
||||
@ -67,6 +76,7 @@ require (
|
||||
github.com/abbot/go-http-auth v0.4.0 // indirect
|
||||
github.com/aead/ecdh v0.2.0 // indirect
|
||||
github.com/andreburgaud/crypt2go v1.2.0 // indirect
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/benbjohnson/clock v1.3.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
@ -89,28 +99,28 @@ require (
|
||||
github.com/blevesearch/zapx/v15 v15.3.13 // indirect
|
||||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bytedance/sonic v1.9.1 // indirect
|
||||
github.com/bytedance/sonic v1.10.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.0 // indirect
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/gaoyb7/115drive-webdav v0.1.8 // indirect
|
||||
github.com/geoffgarside/ber v1.1.0 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.10 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.15.5 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/go-webauthn/x v0.1.4 // indirect
|
||||
github.com/go-webauthn/x v0.1.6 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
@ -123,11 +133,14 @@ require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgx/v5 v5.3.0 // indirect
|
||||
github.com/jaevor/go-nanoid v1.3.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
|
||||
@ -135,13 +148,14 @@ require (
|
||||
github.com/libp2p/go-libp2p v0.27.8 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.15 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
@ -160,8 +174,7 @@ require (
|
||||
github.com/multiformats/go-multihash v0.2.3 // indirect
|
||||
github.com/multiformats/go-multistream v0.4.1 // indirect
|
||||
github.com/multiformats/go-varint v0.0.7 // indirect
|
||||
github.com/ncw/swift/v2 v2.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.18 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
|
||||
@ -172,6 +185,8 @@ require (
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/rfjakob/eme v1.1.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect
|
||||
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
|
||||
@ -182,19 +197,23 @@ require (
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/u2takey/go-utils v0.3.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/term v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/arch v0.5.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/term v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.18.0 // indirect
|
||||
google.golang.org/api v0.134.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
|
||||
google.golang.org/grpc v1.57.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
321
go.sum
321
go.sum
@ -2,44 +2,40 @@ cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
|
||||
cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY=
|
||||
cloud.google.com/go/compute v1.23.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
|
||||
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
|
||||
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
|
||||
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700 h1:r3fp2/Ro+0RtpjNY0/wsbN7vRmCW//dXTOZDQTct25Q=
|
||||
github.com/Mikubill/gofakes3 v0.0.3-0.20230622102024-284c0f988700/go.mod h1:OSXqXEGUe9CmPiwLMMnVrbXonMf4BeLBkBdLufxxiyY=
|
||||
github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY=
|
||||
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
|
||||
github.com/SheltonZhu/115driver v1.0.15 h1:RRvgXvXEzvrPwkRno0CUIg7ucEphbsfwct2mQxfNOdQ=
|
||||
github.com/SheltonZhu/115driver v1.0.15/go.mod h1:e3fPOBANbH/FsTya8FquJwOR3ErhCQgEab3q6CVY2k4=
|
||||
github.com/SheltonZhu/115driver v1.0.16 h1:XOhqRtKF9huTCobM5rWVd9DtJyBKLJSnBwWPF3+GM+k=
|
||||
github.com/SheltonZhu/115driver v1.0.16/go.mod h1:e3fPOBANbH/FsTya8FquJwOR3ErhCQgEab3q6CVY2k4=
|
||||
github.com/SheltonZhu/115driver v1.0.22 h1:Wp8pN7/gK3YwEO5P18ggbIOHM++lo9eP/pBhuvXfI6U=
|
||||
github.com/SheltonZhu/115driver v1.0.22/go.mod h1:e3fPOBANbH/FsTya8FquJwOR3ErhCQgEab3q6CVY2k4=
|
||||
github.com/Unknwon/goconfig v1.0.0 h1:9IAu/BYbSLQi8puFjUQApZTxIHqSwrj5d8vpP8vTq4A=
|
||||
github.com/Unknwon/goconfig v1.0.0/go.mod h1:wngxua9XCNjvHjDiTiV26DaKDT+0c63QR6H5hjVUUxw=
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a h1:RenIAa2q4H8UcS/cqmwdT1WCWIAH5aumP8m8RpbqVsE=
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a/go.mod h1:sSBbaOg90XwWKtpT56kVujF0bIeVITnPlssLclogS04=
|
||||
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4 h1:WnvifFgYyogPz2ZFvaVLk4gI/Co0paF92FmxSR6U1zY=
|
||||
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4/go.mod h1:8pWlL2rpusvx7Xa6yYaIWOJ8bR3gPdFBUT7OystyGOY=
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.1 h1:dSrTxNYclqNuo9libjtC+R6C4RCen/inh/dUXd12vpM=
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.1/go.mod h1:xWcUS7PoFLDD9gy2BK2VQfilEsZngLMz2Vkx3oF2zJY=
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.2 h1:6Gh4YTT7b7YHN0OoJ33j7Jm9ru/ckuvcDxPnRmH07jc=
|
||||
github.com/Xhofe/wopan-sdk-go v0.1.2/go.mod h1:ktLYb4t7rnPFq1AshLaPXq5kZER+DkEagT6/i/in0uo=
|
||||
github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0=
|
||||
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
|
||||
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
||||
github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1eXdZyMw3F7uGA9qPn2J4+R8=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
|
||||
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andreburgaud/crypt2go v1.2.0 h1:oly/ENAodeqTYpUafgd4r3v+VKLQnmOKUyfpj+TxHbE=
|
||||
github.com/andreburgaud/crypt2go v1.2.0/go.mod h1:kKRqlrX/3Q9Ki7HdUsoh0cX1Urq14/Hcta4l4VrIXrI=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.44.327 h1:ZS8oO4+7MOBLhkdwIhgtVeDzCeWOlTfKJS7EgggbIEY=
|
||||
github.com/aws/aws-sdk-go v1.44.327/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go v1.50.24 h1:3o2Pg7mOoVL0jv54vWtuafoZqAeEXLhm1tltWA2GcEw=
|
||||
github.com/aws/aws-sdk-go v1.50.24/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
@ -50,16 +46,10 @@ github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjL
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/blevesearch/bleve/v2 v2.3.9 h1:pUMvK0mxAexqasZcVj8lazmWnEW5XiV0tASIqANiNTQ=
|
||||
github.com/blevesearch/bleve/v2 v2.3.9/go.mod h1:1PibElcjlQMQHF9uS9mRv58ODQgj4pCWHA1Wfd+qagU=
|
||||
github.com/blevesearch/bleve/v2 v2.3.10 h1:z8V0wwGoL4rp7nG/O3qVVLYxUqCbEwskMt4iRJsPLgg=
|
||||
github.com/blevesearch/bleve/v2 v2.3.10/go.mod h1:RJzeoeHC+vNHsoLR54+crS1HmOWpnH87fL70HAUCzIA=
|
||||
github.com/blevesearch/bleve_index_api v1.0.5 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw=
|
||||
github.com/blevesearch/bleve_index_api v1.0.5/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||
github.com/blevesearch/bleve_index_api v1.0.6 h1:gyUUxdsrvmW3jVhhYdCVL6h9dCjNT/geNU7PxGn37p8=
|
||||
github.com/blevesearch/bleve_index_api v1.0.6/go.mod h1:YXMDwaXFFXwncRS8UobWs7nvo0DmusriM1nztTlj1ms=
|
||||
github.com/blevesearch/geo v0.1.17 h1:AguzI6/5mHXapzB0gE9IKWo+wWPHZmXZoscHcjFgAFA=
|
||||
github.com/blevesearch/geo v0.1.17/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
|
||||
github.com/blevesearch/geo v0.1.18 h1:Np8jycHTZ5scFe7VEPLrDoHnnb9C4j636ue/CGrhtDw=
|
||||
github.com/blevesearch/geo v0.1.18/go.mod h1:uRMGWG0HJYfWfFJpK3zTdnnr1K+ksZTuWKhXeSokfnM=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
@ -68,8 +58,6 @@ github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZG
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.0.4 h1:OVhDhT5B/M1HNPpYPBKIEJaD0F3Si+CrEKULGCDPWmc=
|
||||
github.com/blevesearch/mmap-go v1.0.4/go.mod h1:EWmEAOmdAS9z/pi/+Toxu99DnsbhG1TIxUoRmJw/pSs=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.5 h1:1g713kpCQZ8u4a3stRGBfrwVOuGRnmxOVU5MQkUPrHU=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.5/go.mod h1:f2nOkKS1HcjgIWZgDAErgBdxmr2eyt0Kn7IY+FU1Xe4=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.6 h1:CdekX/Ob6YCYmeHzD72cKpwzBjvkOGegHOqhAkXp6yA=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.1.6/go.mod h1:nQQYlp51XvoSVxcciBjtvuHPIVjlWrN1hX4qwK2cqdc=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
@ -80,24 +68,14 @@ github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMG
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.0.10 h1:HGPJDT2bTva12hrHepVT3rOyIKFFF4t7Gf6yMxyMIPI=
|
||||
github.com/blevesearch/vellum v1.0.10/go.mod h1:ul1oT0FhSMDIExNjIxHqJoGpVrBpKCdgDQNxfqgJt7k=
|
||||
github.com/blevesearch/zapx/v11 v11.3.9 h1:y3ijS4h4MJdmQ07MHASxat4owAixreK2xdo76w9ncrw=
|
||||
github.com/blevesearch/zapx/v11 v11.3.9/go.mod h1:jcAYnQwlr+LqD2vLjDWjWiZDXDXGFqPbpPDRTd3XmS4=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10 h1:hvjgj9tZ9DeIqBCxKhi70TtSZYMdcFn7gDb71Xo/fvk=
|
||||
github.com/blevesearch/zapx/v11 v11.3.10/go.mod h1:0+gW+FaE48fNxoVtMY5ugtNHHof/PxCqh7CnhYdnMzQ=
|
||||
github.com/blevesearch/zapx/v12 v12.3.9 h1:MXGLlZ03oxXH3DMJTZaBaRj2xb6t4wQVZeZK/wu1M6w=
|
||||
github.com/blevesearch/zapx/v12 v12.3.9/go.mod h1:QXCMwmOkdLnMDgTN1P4CcuX5F851iUOtOwXbw0HMBYs=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10 h1:yHfj3vXLSYmmsBleJFROXuO08mS3L1qDCdDK81jDl8s=
|
||||
github.com/blevesearch/zapx/v12 v12.3.10/go.mod h1:0yeZg6JhaGxITlsS5co73aqPtM04+ycnI6D1v0mhbCs=
|
||||
github.com/blevesearch/zapx/v13 v13.3.9 h1:+VAz9V0VmllHXlZV4DCvfYj0nqaZHgF3MeEHwOyRBwQ=
|
||||
github.com/blevesearch/zapx/v13 v13.3.9/go.mod h1:s+WjNp4WSDtrBVBpa37DUOd7S/Gr/jTZ7ST/MbCVj/0=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10 h1:0KY9tuxg06rXxOZHg3DwPJBjniSlqEgVpxIqMGahDE8=
|
||||
github.com/blevesearch/zapx/v13 v13.3.10/go.mod h1:w2wjSDQ/WBVeEIvP0fvMJZAzDwqwIEzVPnCPrz93yAk=
|
||||
github.com/blevesearch/zapx/v14 v14.3.9 h1:wuqxATgsTCNHM9xsOFOeFp8H2heZ/gMX/tsl9lRK8U4=
|
||||
github.com/blevesearch/zapx/v14 v14.3.9/go.mod h1:MWZ4v8AzFBRurhDzkLvokFW8ljcq9Evm27mkWe8OGbM=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10 h1:SG6xlsL+W6YjhX5N3aEiL/2tcWh3DO75Bnz77pSwwKU=
|
||||
github.com/blevesearch/zapx/v14 v14.3.10/go.mod h1:qqyuR0u230jN1yMmE4FIAuCxmahRQEOehF78m6oTgns=
|
||||
github.com/blevesearch/zapx/v15 v15.3.12 h1:w/kU9aHyfMDEdwHGZzCiakC3HZ9z5gYlXaALDC4Dct8=
|
||||
github.com/blevesearch/zapx/v15 v15.3.12/go.mod h1:tx53gDJS/7Oa3Je820cmVurqCuJ4dqdAy1kiDMV/IUo=
|
||||
github.com/blevesearch/zapx/v15 v15.3.13 h1:6EkfaZiPlAxqXz0neniq35my6S48QI94W/wyhnpDHHQ=
|
||||
github.com/blevesearch/zapx/v15 v15.3.13/go.mod h1:Turk/TNRKj9es7ZpKK95PS7f6D44Y7fAFy8F4LXQtGg=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
@ -105,65 +83,69 @@ github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
|
||||
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc=
|
||||
github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
|
||||
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
|
||||
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
|
||||
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
|
||||
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
|
||||
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
|
||||
github.com/charmbracelet/bubbles v0.17.1 h1:0SIyjOnkrsfDo88YvPgAWvZMwXe26TP6drRvmkjyUu4=
|
||||
github.com/charmbracelet/bubbles v0.17.1/go.mod h1:9HxZWlkCqz2PRwsCbYl7a3KXvGzFaDHpYbSYMJ+nE3o=
|
||||
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
|
||||
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
|
||||
github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764=
|
||||
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
|
||||
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg=
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deckarep/golang-set/v2 v2.3.1 h1:vjmkvJt/IV27WXPyYQpAh4bRyWJc5Y435D17XQ9QU5A=
|
||||
github.com/deckarep/golang-set/v2 v2.3.1/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
|
||||
github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU=
|
||||
github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564 h1:I6KUy4CI6hHjqnyJLNCEi7YHVMkwwtfSr2k9splgdSM=
|
||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564/go.mod h1:yekO+3ZShy19S+bsmnERmznGy9Rfg6dWWWpiGJjNAz8=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.3 h1:6ww0ulyLDh6neXZBqUM2PDbxQ6lfdkQbr0FCh9BTY0Y=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.3/go.mod h1:iWHA2JFhzmKR28ySp1ON0g6DjLaYtvb5jhTqPVTDW9A=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.4 h1:6utvPiBv8KDRDVKB7A4FERdrVxcHKZd2fBFCNuKcXzU=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.4/go.mod h1:iWHA2JFhzmKR28ySp1ON0g6DjLaYtvb5jhTqPVTDW9A=
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.2 h1:waRWIBmjL9GCcndJ8HvOYrrVB4hhoPYzRrn3I/Cnzqw=
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.2/go.mod h1:AKsLFuWhWlClpGrg1zxTdMejugZEZtmhIuElAk3W83s=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.5 h1:N3LqOvk2aWWxszsFIkArP5udIv74uTei/bH2jM3tfSc=
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.5/go.mod h1:iWHA2JFhzmKR28ySp1ON0g6DjLaYtvb5jhTqPVTDW9A=
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3 h1:I5c5nfGErhq9DBumyjCVCggRA74jhgriMqRRFu5jeeY=
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3/go.mod h1:TPxzN0d2PahweUEHlOBWlwZSA+rELSUlGYMWgXRn9ps=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
|
||||
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/gaoyb7/115drive-webdav v0.1.8 h1:EJt4PSmcbvBY4KUh2zSo5p6fN9LZFNkIzuKejipubVw=
|
||||
github.com/gaoyb7/115drive-webdav v0.1.8/go.mod h1:BKbeY6j8SKs3+rzBFFALznGxbPmefEm3vA+dGhqgOGU=
|
||||
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
|
||||
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
|
||||
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
||||
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
|
||||
github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk=
|
||||
github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
@ -176,6 +158,7 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
@ -184,35 +167,32 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
|
||||
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/go-resty/resty/v2 v2.8.0 h1:J29d0JFWwSWrDCysnOK/YjsPMLQTx0TvgJEHVGvf2L8=
|
||||
github.com/go-resty/resty/v2 v2.8.0/go.mod h1:UCui0cMHekLrSntoMyofdSTaPpinlRHFtPpizuyDW2w=
|
||||
github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM=
|
||||
github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4=
|
||||
github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8=
|
||||
github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-webauthn/webauthn v0.8.6 h1:bKMtL1qzd2WTFkf1mFTVbreYrwn7dsYmEPjTq6QN90E=
|
||||
github.com/go-webauthn/webauthn v0.8.6/go.mod h1:emwVLMCI5yx9evTTvr0r+aOZCdWJqMfbRhF0MufyUog=
|
||||
github.com/go-webauthn/x v0.1.4 h1:sGmIFhcY70l6k7JIDfnjVBiAAFEssga5lXIUXe0GtAs=
|
||||
github.com/go-webauthn/x v0.1.4/go.mod h1:75Ug0oK6KYpANh5hDOanfDI+dvPWHk788naJVG/37H8=
|
||||
github.com/go-webauthn/webauthn v0.10.0 h1:yuW2e1tXnRAwAvKrR4q4LQmc6XtCMH639/ypZGhZCwk=
|
||||
github.com/go-webauthn/webauthn v0.10.0/go.mod h1:l0NiauXhL6usIKqNLCUM3Qir43GK7ORg8ggold0Uv/Y=
|
||||
github.com/go-webauthn/x v0.1.6 h1:QNAX+AWeqRt9loE8mULeWJCqhVG5D/jvdmJ47fIWCkQ=
|
||||
github.com/go-webauthn/x v0.1.6/go.mod h1:W8dFVZ79o4f+nY1eOUICy/uq5dhrRl7mxQkYhXTo0FA=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
@ -225,15 +205,16 @@ github.com/google/go-tpm v0.9.0 h1:sQF6YqWMi+SCXpsmS3fd21oPy/vSddwZry4JnmltHVk=
|
||||
github.com/google/go-tpm v0.9.0/go.mod h1:FkNVkc6C+IsvDI9Jw1OveJmxGZUUaKxtrpOS47QWKfU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
|
||||
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5 h1:UR4rDjcgpgEnqpIEvkiqTYKBCKLNmlge2eVjoZfySzM=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.2.5/go.mod h1:RxW0N9901Cko1VOCW3SXCpWP+mlIEkk2tP7jnHy9a3w=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@ -244,14 +225,10 @@ github.com/hirochachacha/go-smb2 v1.1.0 h1:b6hs9qKIql9eVXAiN0M2wSFY5xnhbHAQoCwRK
|
||||
github.com/hirochachacha/go-smb2 v1.1.0/go.mod h1:8F1A4d5EZzrGu5R7PU163UcMRDJQl4FtcxjBfsY8TZE=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/ipfs/boxo v0.8.0 h1:UdjAJmHzQHo/j3g3b1bAcAXCj/GM6iTwvSlBDvPBNBs=
|
||||
github.com/ipfs/boxo v0.8.0/go.mod h1:RIsi4CnTyQ7AUsNn5gXljJYZlQrHBMnJp94p73liFiA=
|
||||
github.com/ipfs/boxo v0.12.0 h1:AXHg/1ONZdRQHQLgG5JHsSC3XoE4DjCAMgK+asZvUcQ=
|
||||
github.com/ipfs/boxo v0.12.0/go.mod h1:xAnfiU6PtxWCnRqu7dcXQ10bB5/kvI1kXRotuGqGBhg=
|
||||
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
|
||||
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
|
||||
github.com/ipfs/go-ipfs-api v0.6.1 h1:nK5oeFOdMh1ogT+GCOcyBFOOcFGNuudSb1rg9YDyAKE=
|
||||
github.com/ipfs/go-ipfs-api v0.6.1/go.mod h1:8pl+ZMF2LX42szbqGbpOBEiI1/rYaImvTvJtG0g+rL4=
|
||||
github.com/ipfs/go-ipfs-api v0.7.0 h1:CMBNCUl0b45coC+lQCXEVpMhwoqjiaCwUIrM+coYW2Q=
|
||||
github.com/ipfs/go-ipfs-api v0.7.0/go.mod h1:AIxsTNB0+ZhkqIfTZpdZ0VR/cpX5zrXjATa3prSay3g=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
@ -261,6 +238,8 @@ github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZ
|
||||
github.com/jackc/pgx/v5 v5.3.0 h1:/NQi8KHMpKWHInxXesC8yD4DhkXPrVhmnwYkjp9AmBA=
|
||||
github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
|
||||
github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jaevor/go-nanoid v1.3.0 h1:nD+iepesZS6pr3uOVf20vR9GdGgJW1HPaR46gtrxzkg=
|
||||
github.com/jaevor/go-nanoid v1.3.0/go.mod h1:SI+jFaPuddYkqkVQoNGHs81navCtH388TcrH0RqFKgY=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
@ -272,25 +251,30 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC9jFiTxyptEKuNIAbiN5ZCQzX2a74lj3xg=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@ -309,8 +293,10 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||
github.com/maruel/natural v1.1.0 h1:2z1NgP/Vae+gYrtC0VuvrTJ6U35OuyUqDdfluLqMWuQ=
|
||||
github.com/maruel/natural v1.1.0/go.mod h1:eFVhYCcUOfZFxXoDZam8Ktya72wa79fNC3lc/leA0DQ=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo=
|
||||
github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
@ -320,16 +306,18 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||
github.com/meilisearch/meilisearch-go v0.26.1 h1:3bmo2uLijX7kvBmiZ9LupVfC95TFcRJDgrRTzbOoE4A=
|
||||
github.com/meilisearch/meilisearch-go v0.26.1/go.mod h1:SxuSqDcPBIykjWz1PX+KzsYzArNLSCadQodWs8extS0=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/minio/sio v0.3.0 h1:syEFBewzOMOYVzSTFpp1MqpSZk8rUNbz8VIIc+PNzus=
|
||||
github.com/minio/sio v0.3.0/go.mod h1:8b0yPp2avGThviy/+OCJBI6OMpvxoUuiLvE6F1lebhw=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
@ -351,8 +339,6 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
|
||||
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
|
||||
@ -363,44 +349,34 @@ github.com/multiformats/go-multiaddr v0.9.0 h1:3h4V1LHIk5w4hJHekMKWALPXErDfz/sgg
|
||||
github.com/multiformats/go-multiaddr v0.9.0/go.mod h1:mI67Lb1EeTOYb8GQfL/7wpIZwc46ElrvzhYnoJOmTT0=
|
||||
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
|
||||
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
|
||||
github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8=
|
||||
github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
|
||||
github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
|
||||
github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
|
||||
github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108=
|
||||
github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc=
|
||||
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
|
||||
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
|
||||
github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo=
|
||||
github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q=
|
||||
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
|
||||
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/ncw/swift/v2 v2.0.2 h1:jx282pcAKFhmoZBSdMcCRFn9VWkoBIRsCpe+yZq7vEk=
|
||||
github.com/ncw/swift/v2 v2.0.2/go.mod h1:z0A9RVdYPjNjXVo2pDOPxZ4eu3oarO1P91fTItcb+Kg=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77 h1:dg/EaaJLPIg4xn2kaZil7Ax3wfoxcFXaBwyOTlcz5AI=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20221009101310-08b764073b77/go.mod h1:FD9a09Vw07CSMTdT0Y7ttStOa1WZsnPBslliMw2DkeM=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831 h1:K3T3eu4h5aYIOzUtLjN08L4Qt4WGaJONMgcaD0ayBJQ=
|
||||
github.com/orzogc/fake115uploader v0.3.3-0.20230715111618-58f9eb76f831/go.mod h1:lSHD4lC4zlMl+zcoysdJcd5KFzsWwOD8BJbyg1Ws9Ng=
|
||||
github.com/panjf2000/ants/v2 v2.4.2/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
|
||||
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6 h1:5TvW1dv00Y13njmQ1AWkxSWtPkwE7ZEF6yDuv9q+Als=
|
||||
github.com/pkg/sftp v1.13.6-0.20230213180117-971c283182b6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
@ -429,7 +405,12 @@ github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
|
||||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500 h1:WnNuhiq+FOY3jNj6JXFT+eLN3CQ/oPIsDPRanvwsmbI=
|
||||
github.com/shabbyrobe/gocovmerge v0.0.0-20190829150210-3e036491d500/go.mod h1:+njLrG5wSeoG4Ds61rFgEzKvenR2UHbjMoDHsczxly0=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
|
||||
github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
@ -442,11 +423,12 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@ -463,11 +445,10 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca h1:I9rVnNXdIkij4UvMT7OmKhH9sOIvS8iXkxfPdnn9wQA=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7 h1:Jtcrb09q0AVWe3BGe8qtuuGxNSHWGkTWr43kHTJ+CpA=
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20240219080617-d494b6a8ace7/go.mod h1:suDIky6yrK07NnaBadCB4sS0CqFOvUK91lH7CR+JlDA=
|
||||
github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
|
||||
github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
|
||||
github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
|
||||
@ -475,8 +456,6 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/u2takey/ffmpeg-go v0.4.1 h1:l5ClIwL3N2LaH1zF3xivb3kP2HW95eyG5xhHE1JdZ9Y=
|
||||
github.com/u2takey/ffmpeg-go v0.4.1/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc=
|
||||
github.com/u2takey/ffmpeg-go v0.5.0 h1:r7d86XuL7uLWJ5mzSeQ03uvjfIhiJYvsRAJFCW4uklU=
|
||||
github.com/u2takey/ffmpeg-go v0.5.0/go.mod h1:ruZWkvC1FEiUNjmROowOAps3ZcWxEiOpFoHCvk97kGc=
|
||||
github.com/u2takey/go-utils v0.3.1 h1:TaQTgmEZZeDHQFYfd+AdUT1cT4QJgJn/XVPELhHw4ys=
|
||||
@ -487,85 +466,76 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
|
||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/upyun/go-sdk/v3 v3.0.4 h1:2DCJa/Yi7/3ZybT9UCPATSzvU3wpPPxhXinNlb1Hi8Q=
|
||||
github.com/upyun/go-sdk/v3 v3.0.4/go.mod h1:P/SnuuwhrIgAVRd/ZpzDWqCsBAf/oHg7UggbAxyZa0E=
|
||||
github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d h1:xS9QTPgKl9ewGsAOPc+xW7DeStJDqYPfisDmeSCcbco=
|
||||
github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
|
||||
github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ=
|
||||
github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5 h1:jxZvjx8Ve5sOXorZG0KzTxbp0Cr1n3FEegfmyd9br1k=
|
||||
github.com/winfsp/cgofuse v1.5.1-0.20230130140708-f87f5db493b5/go.mod h1:uxjoF2jEYT3+x+vC2KJddEGdk/LU8pRowXmyVMHSV5I=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25 h1:eDfebW/yfq9DtG9RO3KP7BT2dot2CvJGIvrB0NEoDXI=
|
||||
github.com/xhofe/gsync v0.0.0-20230917091818-2111ceb38a25/go.mod h1:fH4oNm5F9NfI5dLi0oIMtsLNKQOirUDbEMCIBb/7SU0=
|
||||
github.com/xhofe/tache v0.1.1 h1:O5QY4cVjIGELx3UGh6LbVAc18MWGXgRNQjMt72x6w/8=
|
||||
github.com/xhofe/tache v0.1.1/go.mod h1:iKumPFvywf30FRpAHHCt64G0JHLMzT0K+wyGedHsmTQ=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ=
|
||||
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
gocv.io/x/gocv v0.25.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
|
||||
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ=
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ=
|
||||
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.10.0 h1:gXjUUtwtx5yOE0VKWq1CH4IJAClq4UGgUA3i+rpON9M=
|
||||
golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
|
||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=
|
||||
golang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
|
||||
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||
golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4=
|
||||
golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@ -578,7 +548,10 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -589,55 +562,48 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190829051458-42f498d34c4d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.134.0 h1:ktL4Goua+UBgoP1eL1/60LwZJqa1sIzkLmvoR3hR6Gw=
|
||||
google.golang.org/api v0.134.0/go.mod h1:sjRL3UnjTx5UqNQS9EWr9N8p7xbHpy1k0XGRLCf3Spk=
|
||||
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I=
|
||||
google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw=
|
||||
@ -647,11 +613,15 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ldap.v3 v3.1.0 h1:DIDWEjI7vQWREh0S8X5/NFPCZ3MCVd55LmXKPW4XLGE=
|
||||
gopkg.in/ldap.v3 v3.1.0/go.mod h1:dQjCc0R0kfyFjIlWNMH1DORwUASZyDxo2Ry1B51dXaQ=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
@ -678,5 +648,6 @@ gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
|
||||
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0=
|
||||
lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
@ -1,61 +0,0 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func AddURI(ctx context.Context, uri string, dstDirPath string) error {
|
||||
// check storage
|
||||
storage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed get storage")
|
||||
}
|
||||
// check is it could upload
|
||||
if storage.Config().NoUpload {
|
||||
return errors.WithStack(errs.UploadNotSupported)
|
||||
}
|
||||
// check path is valid
|
||||
obj, err := op.Get(ctx, storage, dstDirActualPath)
|
||||
if err != nil {
|
||||
if !errs.IsObjectNotFound(err) {
|
||||
return errors.WithMessage(err, "failed get object")
|
||||
}
|
||||
} else {
|
||||
if !obj.IsDir() {
|
||||
// can't add to a file
|
||||
return errors.WithStack(errs.NotFolder)
|
||||
}
|
||||
}
|
||||
// call aria2 rpc
|
||||
tempDir := filepath.Join(conf.Conf.TempDir, "aria2", uuid.NewString())
|
||||
options := map[string]interface{}{
|
||||
"dir": tempDir,
|
||||
}
|
||||
gid, err := client.AddURI([]string{uri}, options)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to add uri %s", uri)
|
||||
}
|
||||
DownTaskManager.Submit(task.WithCancelCtx(&task.Task[string]{
|
||||
ID: gid,
|
||||
Name: fmt.Sprintf("download %s to [%s](%s)", uri, storage.GetStorage().MountPath, dstDirActualPath),
|
||||
Func: func(tsk *task.Task[string]) error {
|
||||
m := &Monitor{
|
||||
tsk: tsk,
|
||||
tempDir: tempDir,
|
||||
retried: 0,
|
||||
dstDirPath: dstDirPath,
|
||||
}
|
||||
return m.Loop()
|
||||
},
|
||||
}))
|
||||
return nil
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/aria2/rpc"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var DownTaskManager = task.NewTaskManager[string](3)
|
||||
var notify = NewNotify()
|
||||
var client rpc.Client
|
||||
|
||||
func InitClient(timeout int) (string, error) {
|
||||
client = nil
|
||||
uri := setting.GetStr(conf.Aria2Uri)
|
||||
secret := setting.GetStr(conf.Aria2Secret)
|
||||
return InitAria2Client(uri, secret, timeout)
|
||||
}
|
||||
|
||||
func InitAria2Client(uri string, secret string, timeout int) (string, error) {
|
||||
c, err := rpc.New(context.Background(), uri, secret, time.Duration(timeout)*time.Second, notify)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to init aria2 client")
|
||||
}
|
||||
version, err := c.GetVersion()
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed get aria2 version")
|
||||
}
|
||||
client = c
|
||||
log.Infof("using aria2 version: %s", version.Version)
|
||||
return version.Version, nil
|
||||
}
|
||||
|
||||
func IsAria2Ready() bool {
|
||||
return client != nil
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
_ "github.com/alist-org/alist/v3/drivers"
|
||||
conf2 "github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
conf2.Conf = conf2.DefaultConfig()
|
||||
absPath, err := filepath.Abs("../../data/temp")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
conf2.Conf.TempDir = absPath
|
||||
dB, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
panic("failed to connect database")
|
||||
}
|
||||
db.Init(dB)
|
||||
}
|
||||
|
||||
func TestConnect(t *testing.T) {
|
||||
_, err := InitAria2Client("http://localhost:16800/jsonrpc", "secret", 3)
|
||||
if err != nil {
|
||||
t.Errorf("failed to init aria2: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDown(t *testing.T) {
|
||||
TestConnect(t)
|
||||
_, err := op.CreateStorage(context.Background(), model.Storage{
|
||||
ID: 0,
|
||||
MountPath: "/",
|
||||
Order: 0,
|
||||
Driver: "Local",
|
||||
Status: "",
|
||||
Addition: `{"root_folder":"../../data"}`,
|
||||
Remark: "",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create storage: %+v", err)
|
||||
}
|
||||
err = AddURI(context.Background(), "https://nodejs.org/dist/index.json", "/test")
|
||||
if err != nil {
|
||||
t.Errorf("failed to add uri: %+v", err)
|
||||
}
|
||||
tasks := DownTaskManager.GetAll()
|
||||
if len(tasks) != 1 {
|
||||
t.Errorf("failed to get tasks: %+v", tasks)
|
||||
}
|
||||
for {
|
||||
tsk := tasks[0]
|
||||
t.Logf("task: %+v", tsk)
|
||||
if tsk.GetState() == task.SUCCEEDED {
|
||||
break
|
||||
}
|
||||
if tsk.GetState() == task.ERRORED {
|
||||
t.Fatalf("failed to download: %+v", tsk)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
for {
|
||||
if len(TransferTaskManager.GetAll()) == 0 {
|
||||
continue
|
||||
}
|
||||
tsk := TransferTaskManager.GetAll()[0]
|
||||
t.Logf("task: %+v", tsk)
|
||||
if tsk.GetState() == task.SUCCEEDED {
|
||||
break
|
||||
}
|
||||
if tsk.GetState() == task.ERRORED {
|
||||
t.Fatalf("failed to download: %+v", tsk)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/stream"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/task"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
tsk *task.Task[string]
|
||||
tempDir string
|
||||
retried int
|
||||
c chan int
|
||||
dstDirPath string
|
||||
finish chan struct{}
|
||||
}
|
||||
|
||||
func (m *Monitor) Loop() error {
|
||||
defer func() {
|
||||
notify.Signals.Delete(m.tsk.ID)
|
||||
// clear temp dir, should do while complete
|
||||
//_ = os.RemoveAll(m.tempDir)
|
||||
}()
|
||||
m.c = make(chan int)
|
||||
m.finish = make(chan struct{})
|
||||
notify.Signals.Store(m.tsk.ID, m.c)
|
||||
var (
|
||||
err error
|
||||
ok bool
|
||||
)
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-m.tsk.Ctx.Done():
|
||||
_, err := client.Remove(m.tsk.ID)
|
||||
return err
|
||||
case <-m.c:
|
||||
ok, err = m.Update()
|
||||
if ok {
|
||||
break outer
|
||||
}
|
||||
case <-time.After(time.Second * 2):
|
||||
ok, err = m.Update()
|
||||
if ok {
|
||||
break outer
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.tsk.SetStatus("aria2 download completed, transferring")
|
||||
<-m.finish
|
||||
m.tsk.SetStatus("completed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Monitor) Update() (bool, error) {
|
||||
info, err := client.TellStatus(m.tsk.ID)
|
||||
if err != nil {
|
||||
m.retried++
|
||||
log.Errorf("failed to get status of %s, retried %d times", m.tsk.ID, m.retried)
|
||||
return false, nil
|
||||
}
|
||||
if m.retried > 5 {
|
||||
return true, errors.Errorf("failed to get status of %s, retried %d times", m.tsk.ID, m.retried)
|
||||
}
|
||||
m.retried = 0
|
||||
if len(info.FollowedBy) != 0 {
|
||||
log.Debugf("followen by: %+v", info.FollowedBy)
|
||||
gid := info.FollowedBy[0]
|
||||
notify.Signals.Delete(m.tsk.ID)
|
||||
oldId := m.tsk.ID
|
||||
m.tsk.ID = gid
|
||||
DownTaskManager.RawTasks().Delete(oldId)
|
||||
DownTaskManager.RawTasks().Store(m.tsk.ID, m.tsk)
|
||||
notify.Signals.Store(gid, m.c)
|
||||
return false, nil
|
||||
}
|
||||
// update download status
|
||||
total, err := strconv.ParseUint(info.TotalLength, 10, 64)
|
||||
if err != nil {
|
||||
total = 0
|
||||
}
|
||||
downloaded, err := strconv.ParseUint(info.CompletedLength, 10, 64)
|
||||
if err != nil {
|
||||
downloaded = 0
|
||||
}
|
||||
progress := float64(downloaded) / float64(total) * 100
|
||||
m.tsk.SetProgress(progress)
|
||||
switch info.Status {
|
||||
case "complete":
|
||||
err := m.Complete()
|
||||
return true, errors.WithMessage(err, "failed to transfer file")
|
||||
case "error":
|
||||
return true, errors.Errorf("failed to download %s, error: %s", m.tsk.ID, info.ErrorMessage)
|
||||
case "active":
|
||||
m.tsk.SetStatus("aria2: " + info.Status)
|
||||
if info.Seeder == "true" {
|
||||
err := m.Complete()
|
||||
return true, errors.WithMessage(err, "failed to transfer file")
|
||||
}
|
||||
return false, nil
|
||||
case "waiting", "paused":
|
||||
m.tsk.SetStatus("aria2: " + info.Status)
|
||||
return false, nil
|
||||
case "removed":
|
||||
return true, errors.Errorf("failed to download %s, removed", m.tsk.ID)
|
||||
default:
|
||||
return true, errors.Errorf("failed to download %s, unknown status %s", m.tsk.ID, info.Status)
|
||||
}
|
||||
}
|
||||
|
||||
var TransferTaskManager = task.NewTaskManager(3, func(k *uint64) {
|
||||
atomic.AddUint64(k, 1)
|
||||
})
|
||||
|
||||
func (m *Monitor) Complete() error {
|
||||
// check dstDir again
|
||||
storage, dstDirActualPath, err := op.GetStorageAndActualPath(m.dstDirPath)
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed get storage")
|
||||
}
|
||||
// get files
|
||||
files, err := client.GetFiles(m.tsk.ID)
|
||||
log.Debugf("files len: %d", len(files))
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get files of %s", m.tsk.ID)
|
||||
}
|
||||
// upload files
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(files))
|
||||
go func() {
|
||||
wg.Wait()
|
||||
err := os.RemoveAll(m.tempDir)
|
||||
m.finish <- struct{}{}
|
||||
if err != nil {
|
||||
log.Errorf("failed to remove aria2 temp dir: %+v", err.Error())
|
||||
}
|
||||
}()
|
||||
for i, _ := range files {
|
||||
file := files[i]
|
||||
TransferTaskManager.Submit(task.WithCancelCtx(&task.Task[uint64]{
|
||||
Name: fmt.Sprintf("transfer %s to [%s](%s)", file.Path, storage.GetStorage().MountPath, dstDirActualPath),
|
||||
Func: func(tsk *task.Task[uint64]) error {
|
||||
defer wg.Done()
|
||||
size, _ := strconv.ParseInt(file.Length, 10, 64)
|
||||
mimetype := utils.GetMimeType(file.Path)
|
||||
f, err := os.Open(file.Path)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to open file %s", file.Path)
|
||||
}
|
||||
s := stream.FileStream{
|
||||
Obj: &model.Object{
|
||||
Name: path.Base(file.Path),
|
||||
Size: size,
|
||||
Modified: time.Now(),
|
||||
IsFolder: false,
|
||||
},
|
||||
Reader: f,
|
||||
Closers: utils.NewClosers(f),
|
||||
Mimetype: mimetype,
|
||||
}
|
||||
ss, err := stream.NewSeekableStream(s, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
relDir, err := filepath.Rel(m.tempDir, filepath.Dir(file.Path))
|
||||
if err != nil {
|
||||
log.Errorf("find relation directory error: %v", err)
|
||||
}
|
||||
newDistDir := filepath.Join(dstDirActualPath, relDir)
|
||||
return op.Put(tsk.Ctx, storage, newDistDir, ss, tsk.SetProgress)
|
||||
},
|
||||
}))
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package aria2
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/pkg/aria2/rpc"
|
||||
"github.com/alist-org/alist/v3/pkg/generic_sync"
|
||||
)
|
||||
|
||||
const (
|
||||
Downloading = iota
|
||||
Paused
|
||||
Stopped
|
||||
Completed
|
||||
Errored
|
||||
)
|
||||
|
||||
type Notify struct {
|
||||
Signals generic_sync.MapOf[string, chan int]
|
||||
}
|
||||
|
||||
func NewNotify() *Notify {
|
||||
return &Notify{Signals: generic_sync.MapOf[string, chan int]{}}
|
||||
}
|
||||
|
||||
func (n *Notify) OnDownloadStart(events []rpc.Event) {
|
||||
for _, e := range events {
|
||||
if signal, ok := n.Signals.Load(e.Gid); ok {
|
||||
signal <- Downloading
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notify) OnDownloadPause(events []rpc.Event) {
|
||||
for _, e := range events {
|
||||
if signal, ok := n.Signals.Load(e.Gid); ok {
|
||||
signal <- Paused
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notify) OnDownloadStop(events []rpc.Event) {
|
||||
for _, e := range events {
|
||||
if signal, ok := n.Signals.Load(e.Gid); ok {
|
||||
signal <- Stopped
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notify) OnDownloadComplete(events []rpc.Event) {
|
||||
for _, e := range events {
|
||||
if signal, ok := n.Signals.Load(e.Gid); ok {
|
||||
signal <- Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notify) OnDownloadError(events []rpc.Event) {
|
||||
for _, e := range events {
|
||||
if signal, ok := n.Signals.Load(e.Gid); ok {
|
||||
signal <- Errored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notify) OnBtDownloadComplete(events []rpc.Event) {
|
||||
for _, e := range events {
|
||||
if signal, ok := n.Signals.Load(e.Gid); ok {
|
||||
signal <- Completed
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user