Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
15651a4356 | |||
7be476cce0 | |||
bb017c5f6d | |||
c51dc4594d | |||
8e30b02efc | |||
0aa438dce4 | |||
9c2fc8e860 | |||
b1d7a980d9 | |||
19d0a88b55 | |||
40567dee0e | |||
4b540a2297 | |||
8a62d55efe | |||
10fce6c0fe | |||
d31d49a9bb | |||
2e91f5ffa5 | |||
8f19c45a81 | |||
c63e05983d | |||
678a982535 | |||
b2c02e6c5e | |||
7e05b0317f | |||
19f06dfaed | |||
1680a18578 | |||
e8f440ca5c | |||
7deff76f49 | |||
cd0afb9536 | |||
668a953cd8 | |||
8bfbaa74f6 | |||
3ccf5ee620 | |||
b44243c021 | |||
4ae81b5a79 |
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
"README.md",
|
"CONTRIBUTORS.md"
|
||||||
"README_cn.md"
|
|
||||||
],
|
],
|
||||||
"imageSize": 100,
|
"imageSize": 100,
|
||||||
"commit": false,
|
"commit": false,
|
||||||
@ -43,6 +42,15 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Clansty",
|
||||||
|
"name": "凌莞~(=^▽^=)",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/18461360?v=4",
|
||||||
|
"profile": "https://c5y.moe",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
105
CONTRIBUTING.md
Normal file
105
CONTRIBUTING.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Setup your machine
|
||||||
|
|
||||||
|
`alist` is written in [Go](https://golang.org/) and [React](https://reactjs.org/).
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- [git](https://nodejs.org/zh-cn/)
|
||||||
|
- [Go 1.17+](https://golang.org/doc/install)
|
||||||
|
- [gcc](https://gcc.gnu.org/)
|
||||||
|
- [nodejs](https://nodejs.org/)
|
||||||
|
|
||||||
|
Clone `alist` and `alist-web` anywhere:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/Xhofe/alist.git
|
||||||
|
$ git clone https://github.com/Xhofe/alist-web.git
|
||||||
|
```
|
||||||
|
You should switch to the dev branch for development.
|
||||||
|
|
||||||
|
## Preview your change
|
||||||
|
### backend
|
||||||
|
```shell
|
||||||
|
$ go run alist.go
|
||||||
|
```
|
||||||
|
### frontend
|
||||||
|
```shell
|
||||||
|
$ yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a commit
|
||||||
|
|
||||||
|
Commit messages should be well formatted, and to make that "standardized".
|
||||||
|
|
||||||
|
### Commit Message Format
|
||||||
|
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
|
||||||
|
format that includes a **type**, a **scope** and a **subject**:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
<BLANK LINE>
|
||||||
|
<body>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
The **header** is mandatory and the **scope** of the header is optional.
|
||||||
|
|
||||||
|
Any line of the commit message cannot be longer than 100 characters! This allows the message to be easier
|
||||||
|
to read on GitHub as well as in various git tools.
|
||||||
|
|
||||||
|
### Revert
|
||||||
|
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header
|
||||||
|
of the reverted commit.
|
||||||
|
In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit
|
||||||
|
being reverted.
|
||||||
|
|
||||||
|
### Type
|
||||||
|
Must be one of the following:
|
||||||
|
|
||||||
|
* **feat**: A new feature
|
||||||
|
* **fix**: A bug fix
|
||||||
|
* **docs**: Documentation only changes
|
||||||
|
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||||
|
semi-colons, etc)
|
||||||
|
* **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||||
|
* **perf**: A code change that improves performance
|
||||||
|
* **test**: Adding missing or correcting existing tests
|
||||||
|
* **build**: Affects project builds or dependency modifications
|
||||||
|
* **revert**: Restore the previous commit
|
||||||
|
* **ci**: Continuous integration of related file modifications
|
||||||
|
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||||
|
generation
|
||||||
|
* **release**: Release a new version
|
||||||
|
* **workflow**: Workflow related file modification
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
The scope could be anything specifying place of the commit change. For example `$location`,
|
||||||
|
`$browser`, `$compile`, `$rootScope`, `ngHref`, `ngClick`, `ngView`, etc...
|
||||||
|
|
||||||
|
You can use `*` when the change affects more than a single scope.
|
||||||
|
|
||||||
|
### Subject
|
||||||
|
The subject contains succinct description of the change:
|
||||||
|
|
||||||
|
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||||
|
* don't capitalize first letter
|
||||||
|
* no dot (.) at the end
|
||||||
|
|
||||||
|
### Body
|
||||||
|
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes".
|
||||||
|
The body should include the motivation for the change and contrast this with previous behavior.
|
||||||
|
|
||||||
|
### Footer
|
||||||
|
The footer should contain any information about **Breaking Changes** and is also the place to
|
||||||
|
[reference GitHub issues that this commit closes](https://help.github.com/articles/closing-issues-via-commit-messages/).
|
||||||
|
|
||||||
|
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines.
|
||||||
|
The rest of the commit message is then used for this.
|
||||||
|
|
||||||
|
## Submit a pull request
|
||||||
|
|
||||||
|
Push your branch to your `alist` fork and open a pull request against the
|
||||||
|
`dev` branch.
|
27
CONTRIBUTORS.md
Normal file
27
CONTRIBUTORS.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
|
[](#contributors-)
|
||||||
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
|
## Contributors ✨
|
||||||
|
|
||||||
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=foxxorcat" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=vg-land" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://c5y.moe"><img src="https://avatars.githubusercontent.com/u/18461360?v=4?s=100" width="100px;" alt=""/><br /><sub><b>凌莞~(=^▽^=)</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Clansty" title="Documentation">📖</a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- markdownlint-restore -->
|
||||||
|
<!-- prettier-ignore-end -->
|
||||||
|
|
||||||
|
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||||
|
|
||||||
|
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
@ -11,4 +11,4 @@ VOLUME /opt/alist/data/
|
|||||||
WORKDIR /opt/alist/
|
WORKDIR /opt/alist/
|
||||||
COPY --from=builder /app/bin/alist ./
|
COPY --from=builder /app/bin/alist ./
|
||||||
EXPOSE 5244
|
EXPOSE 5244
|
||||||
CMD [ "./alist" ]
|
CMD [ "./alist", "-docker" ]
|
29
README.md
29
README.md
@ -13,11 +13,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
English | [中文](./README_cn.md)
|
English | [中文](./README_cn.md) | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
||||||
[](#contributors-)
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -73,29 +69,6 @@ Available at: <https://alist.nn.ci>.
|
|||||||
|
|
||||||
<https://alist-doc.nn.ci/en/>
|
<https://alist-doc.nn.ci/en/>
|
||||||
|
|
||||||
## Contributors ✨
|
|
||||||
|
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=foxxorcat" title="Code">💻</a></td>
|
|
||||||
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
|
|
||||||
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=vg-land" title="Code">💻</a></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
The `AList` is open-source software licensed under the AGPL-3.0 license.
|
The `AList` is open-source software licensed under the AGPL-3.0 license.
|
||||||
|
29
README_cn.md
29
README_cn.md
@ -13,11 +13,7 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[English](./README.md) | 中文
|
[English](./README.md) | 中文 | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
||||||
[](#contributors-)
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
||||||
|
|
||||||
## 支持
|
## 支持
|
||||||
|
|
||||||
@ -73,29 +69,6 @@
|
|||||||
|
|
||||||
<https://alist-doc.nn.ci/>
|
<https://alist-doc.nn.ci/>
|
||||||
|
|
||||||
## Contributors ✨
|
|
||||||
|
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=foxxorcat" title="Code">💻</a></td>
|
|
||||||
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
|
|
||||||
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=vg-land" title="Code">💻</a></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
||||||
|
|
||||||
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
|
||||||
|
|
||||||
## 许可
|
## 许可
|
||||||
|
|
||||||
`AList` 是在 AGPL-3.0 许可下许可的开源软件。
|
`AList` 是在 AGPL-3.0 许可下许可的开源软件。
|
||||||
|
@ -3,6 +3,7 @@ package bootstrap
|
|||||||
import (
|
import (
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
"github.com/Xhofe/alist/utils"
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/caarlos0/env/v6"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -42,8 +43,24 @@ func InitConf() {
|
|||||||
log.Fatalf("update config struct error: %s", err.Error())
|
log.Fatalf("update config struct error: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if !conf.Conf.Force {
|
||||||
|
confFromEnv()
|
||||||
|
}
|
||||||
err := os.MkdirAll(conf.Conf.TempDir, 0700)
|
err := os.MkdirAll(conf.Conf.TempDir, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("create temp dir error: %s", err.Error())
|
log.Fatalf("create temp dir error: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
log.Debugf("config: %+v", conf.Conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func confFromEnv() {
|
||||||
|
prefix := "ALIST_"
|
||||||
|
if conf.Docker {
|
||||||
|
prefix = ""
|
||||||
|
}
|
||||||
|
if err := env.Parse(conf.Conf, env.Options{
|
||||||
|
Prefix: prefix,
|
||||||
|
}); err != nil {
|
||||||
|
log.Fatalf("load config from env error: %s", err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ func init() {
|
|||||||
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
|
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
|
||||||
flag.BoolVar(&conf.Version, "version", false, "print version info")
|
flag.BoolVar(&conf.Version, "version", false, "print version info")
|
||||||
flag.BoolVar(&conf.Password, "password", false, "print current password")
|
flag.BoolVar(&conf.Password, "password", false, "print current password")
|
||||||
|
flag.BoolVar(&conf.Docker, "docker", false, "is using docker")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
InitLog()
|
InitLog()
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,14 @@ func InitSettings() {
|
|||||||
Access: model.PUBLIC,
|
Access: model.PUBLIC,
|
||||||
Group: model.FRONT,
|
Group: model.FRONT,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Key: "announcement",
|
||||||
|
Value: "This is a test announcement.",
|
||||||
|
Description: "announcement message (support markdown)",
|
||||||
|
Type: "text",
|
||||||
|
Access: model.PUBLIC,
|
||||||
|
Group: model.FRONT,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Key: "text types",
|
Key: "text types",
|
||||||
Value: strings.Join(conf.TextTypes, ","),
|
Value: strings.Join(conf.TextTypes, ","),
|
||||||
@ -245,7 +253,7 @@ func InitSettings() {
|
|||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
err = model.SaveSetting(v)
|
err = model.SaveSetting(v)
|
||||||
if v.Key == "password" {
|
if v.Key == "password" {
|
||||||
log.Infof("Initial password: %s", v.Value)
|
log.Infof("Initial password: %s", conf.C.Sprintf(v.Value))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed write setting: %s", err.Error())
|
log.Fatalf("failed write setting: %s", err.Error())
|
||||||
@ -261,6 +269,9 @@ func InitSettings() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed write setting: %s", err.Error())
|
log.Fatalf("failed write setting: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
if v.Key == "password" {
|
||||||
|
log.Infof("Your password: %s", conf.C.Sprintf(v.Value))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.LoadSettings()
|
model.LoadSettings()
|
||||||
|
@ -1,36 +1,37 @@
|
|||||||
package conf
|
package conf
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type" env:"DB_TYPE"`
|
||||||
User string `json:"user"`
|
Host string `json:"host" env:"DB_HOST"`
|
||||||
Password string `json:"password"`
|
Port int `json:"port" env:"DB_PORT"`
|
||||||
Host string `json:"host"`
|
User string `json:"user" env:"DB_USER"`
|
||||||
Port int `json:"port"`
|
Password string `json:"password" env:"DB_PASS"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name" env:"DB_NAME"`
|
||||||
TablePrefix string `json:"table_prefix"`
|
DBFile string `json:"db_file" env:"DB_FILE"`
|
||||||
DBFile string `json:"db_file"`
|
TablePrefix string `json:"table_prefix" env:"DB_TABLE_PREFIX"`
|
||||||
SslMode string `json:"ssl_mode"`
|
SslMode string `json:"ssl_mode" env:"DB_SLL_MODE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scheme struct {
|
type Scheme struct {
|
||||||
Https bool `json:"https"`
|
Https bool `json:"https" env:"HTTPS"`
|
||||||
CertFile string `json:"cert_file"`
|
CertFile string `json:"cert_file" env:"CERT_FILE"`
|
||||||
KeyFile string `json:"key_file"`
|
KeyFile string `json:"key_file" env:"KEY_FILE"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CacheConfig struct {
|
type CacheConfig struct {
|
||||||
Expiration int64 `json:"expiration"`
|
Expiration int64 `json:"expiration" env:"CACHE_EXPIRATION"`
|
||||||
CleanupInterval int64 `json:"cleanup_interval"`
|
CleanupInterval int64 `json:"cleanup_interval" env:"CLEANUP_INTERVAL"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Address string `json:"address"`
|
Force bool `json:"force"`
|
||||||
Port int `json:"port"`
|
Address string `json:"address" env:"ADDR"`
|
||||||
Assets string `json:"assets"`
|
Port int `json:"port" env:"PORT"`
|
||||||
|
Assets string `json:"assets" env:"ASSETS"`
|
||||||
Database Database `json:"database"`
|
Database Database `json:"database"`
|
||||||
Scheme Scheme `json:"scheme"`
|
Scheme Scheme `json:"scheme"`
|
||||||
Cache CacheConfig `json:"cache"`
|
Cache CacheConfig `json:"cache"`
|
||||||
TempDir string `json:"temp_dir"`
|
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultConfig() *Config {
|
func DefaultConfig() *Config {
|
||||||
|
@ -3,6 +3,7 @@ package conf
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/eko/gocache/v2/cache"
|
"github.com/eko/gocache/v2/cache"
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -23,11 +24,14 @@ var (
|
|||||||
Debug bool
|
Debug bool
|
||||||
Version bool
|
Version bool
|
||||||
Password bool
|
Password bool
|
||||||
|
Docker bool
|
||||||
|
|
||||||
DB *gorm.DB
|
DB *gorm.DB
|
||||||
Cache *cache.Cache
|
Cache *cache.Cache
|
||||||
Ctx = context.TODO()
|
Ctx = context.TODO()
|
||||||
Cron *cron.Cron
|
Cron *cron.Cron
|
||||||
|
|
||||||
|
C = color.New(color.FgHiBlue, color.Bold, color.BgHiWhite, color.Underline)
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -88,12 +88,6 @@ func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
|
|||||||
// return nil, ErrPathNotFound
|
// return nil, ErrPathNotFound
|
||||||
//}
|
//}
|
||||||
|
|
||||||
type Cloud189Down struct {
|
|
||||||
ResCode int `json:"res_code"`
|
|
||||||
ResMessage string `json:"res_message"`
|
|
||||||
FileDownloadUrl string `json:"fileDownloadUrl"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LoginResp struct {
|
type LoginResp struct {
|
||||||
Msg string `json:"msg"`
|
Msg string `json:"msg"`
|
||||||
Result int `json:"result"`
|
Result int `json:"result"`
|
||||||
|
@ -6,7 +6,9 @@ import (
|
|||||||
"github.com/Xhofe/alist/drivers/base"
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
"github.com/Xhofe/alist/model"
|
"github.com/Xhofe/alist/model"
|
||||||
"github.com/Xhofe/alist/utils"
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -152,14 +154,15 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
|
|||||||
if file.Type == conf.FOLDER {
|
if file.Type == conf.FOLDER {
|
||||||
return nil, base.ErrNotFile
|
return nil, base.ErrNotFile
|
||||||
}
|
}
|
||||||
var resp Cloud189Down
|
var resp DownResp
|
||||||
u := "https://cloud.189.cn/api/open/file/getFileDownloadUrl.action"
|
u := "https://cloud.189.cn/api/portal/getFileInfo.action"
|
||||||
body, err := driver.Request(u, base.Get, map[string]string{
|
body, err := driver.Request(u, base.Get, map[string]string{
|
||||||
"fileId": file.Id,
|
"fileId": file.Id,
|
||||||
}, nil, nil, account)
|
}, nil, nil, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
log.Debugln(string(body))
|
||||||
err = utils.Json.Unmarshal(body, &resp)
|
err = utils.Json.Unmarshal(body, &resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -167,10 +170,19 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
|
|||||||
if resp.ResCode != 0 {
|
if resp.ResCode != 0 {
|
||||||
return nil, fmt.Errorf(resp.ResMessage)
|
return nil, fmt.Errorf(resp.ResMessage)
|
||||||
}
|
}
|
||||||
res, err := base.NoRedirectClient.R().Get(resp.FileDownloadUrl)
|
client, err := driver.getClient(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
client = resty.NewWithClient(client.GetClient()).SetRedirectPolicy(
|
||||||
|
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}))
|
||||||
|
res, err := client.R().Get("https:" + resp.FileDownloadUrl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debugln(res.Status())
|
||||||
link := base.Link{
|
link := base.Link{
|
||||||
Headers: []base.Header{
|
Headers: []base.Header{
|
||||||
{Name: "User-Agent", Value: base.UserAgent},
|
{Name: "User-Agent", Value: base.UserAgent},
|
||||||
@ -179,6 +191,10 @@ func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link,
|
|||||||
}
|
}
|
||||||
if res.StatusCode() == 302 {
|
if res.StatusCode() == 302 {
|
||||||
link.Url = res.Header().Get("location")
|
link.Url = res.Header().Get("location")
|
||||||
|
res, err = client.R().Get(link.Url)
|
||||||
|
if res.StatusCode() == 302 {
|
||||||
|
link.Url = res.Header().Get("location")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
link.Url = resp.FileDownloadUrl
|
link.Url = resp.FileDownloadUrl
|
||||||
}
|
}
|
||||||
|
@ -53,3 +53,15 @@ type Rsa struct {
|
|||||||
PkId string `json:"pkId"`
|
PkId string `json:"pkId"`
|
||||||
PubKey string `json:"pubKey"`
|
PubKey string `json:"pubKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Cloud189Down struct {
|
||||||
|
ResCode int `json:"res_code"`
|
||||||
|
ResMessage string `json:"res_message"`
|
||||||
|
FileDownloadUrl string `json:"fileDownloadUrl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownResp struct {
|
||||||
|
ResCode int `json:"res_code"`
|
||||||
|
ResMessage string `json:"res_message"`
|
||||||
|
FileDownloadUrl string `json:"downloadUrl"`
|
||||||
|
}
|
||||||
|
331
drivers/189pc/189.go
Normal file
331
drivers/189pc/189.go
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
package _189
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var userStateCache = struct {
|
||||||
|
sync.Mutex
|
||||||
|
States map[string]*State
|
||||||
|
}{States: make(map[string]*State)}
|
||||||
|
|
||||||
|
func GetState(account *model.Account) *State {
|
||||||
|
userStateCache.Lock()
|
||||||
|
defer userStateCache.Unlock()
|
||||||
|
if v, ok := userStateCache.States[account.Username]; ok && v != nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
state := &State{client: resty.New().
|
||||||
|
SetHeaders(map[string]string{
|
||||||
|
"Accept": "application/json;charset=UTF-8",
|
||||||
|
"User-Agent": base.UserAgent,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
userStateCache.States[account.Username] = state
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
type State struct {
|
||||||
|
sync.Mutex
|
||||||
|
client *resty.Client
|
||||||
|
|
||||||
|
RsaPublicKey string
|
||||||
|
|
||||||
|
SessionKey string
|
||||||
|
SessionSecret string
|
||||||
|
FamilySessionKey string
|
||||||
|
FamilySessionSecret string
|
||||||
|
|
||||||
|
AccessToken string
|
||||||
|
|
||||||
|
//怎么刷新的???
|
||||||
|
RefreshToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) login(account *model.Account) error {
|
||||||
|
// 清除cookie
|
||||||
|
jar, _ := cookiejar.New(nil)
|
||||||
|
s.client.SetCookieJar(jar)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var res *resty.Response
|
||||||
|
defer func() {
|
||||||
|
account.Status = "work"
|
||||||
|
if err != nil {
|
||||||
|
account.Status = err.Error()
|
||||||
|
}
|
||||||
|
model.SaveAccount(account)
|
||||||
|
if res != nil {
|
||||||
|
log.Debug(res.String())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var param *LoginParam
|
||||||
|
param, err = s.getLoginParam()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交登录
|
||||||
|
s.RsaPublicKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", param.jRsaKey)
|
||||||
|
res, err = s.client.R().
|
||||||
|
SetHeaders(map[string]string{
|
||||||
|
"Referer": AUTH_URL,
|
||||||
|
"REQID": param.ReqId,
|
||||||
|
"lt": param.Lt,
|
||||||
|
}).
|
||||||
|
SetFormData(map[string]string{
|
||||||
|
"appKey": APP_ID,
|
||||||
|
"accountType": "02",
|
||||||
|
"userName": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Username),
|
||||||
|
"password": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Password),
|
||||||
|
"validateCode": param.vCodeRS,
|
||||||
|
"captchaToken": param.CaptchaToken,
|
||||||
|
"returnUrl": RETURN_URL,
|
||||||
|
"mailSuffix": "@189.cn",
|
||||||
|
"dynamicCheck": "FALSE",
|
||||||
|
"clientType": CLIENT_TYPE,
|
||||||
|
"cb_SaveName": "1",
|
||||||
|
"isOauth2": "false",
|
||||||
|
"state": "",
|
||||||
|
"paramId": param.ParamId,
|
||||||
|
}).
|
||||||
|
Post(AUTH_URL + "/api/logbox/oauth2/loginSubmit.do")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
toUrl := jsoniter.Get(res.Body(), "toUrl").ToString()
|
||||||
|
if toUrl == "" {
|
||||||
|
log.Error(res.String())
|
||||||
|
return fmt.Errorf(res.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取Session
|
||||||
|
var erron Erron
|
||||||
|
var sessionResp appSessionResp
|
||||||
|
res, err = s.client.R().
|
||||||
|
SetResult(&sessionResp).SetError(&erron).
|
||||||
|
SetQueryParams(clientSuffix()).
|
||||||
|
SetQueryParam("redirectURL", url.QueryEscape(toUrl)).
|
||||||
|
Post(API_URL + "/getSessionForPC.action")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if erron.ResCode != "" {
|
||||||
|
err = fmt.Errorf(erron.ResMessage)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if sessionResp.ResCode != 0 {
|
||||||
|
err = fmt.Errorf(sessionResp.ResMessage)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.SessionKey = sessionResp.SessionKey
|
||||||
|
s.SessionSecret = sessionResp.SessionSecret
|
||||||
|
s.FamilySessionKey = sessionResp.FamilySessionKey
|
||||||
|
s.FamilySessionSecret = sessionResp.FamilySessionSecret
|
||||||
|
s.AccessToken = sessionResp.AccessToken
|
||||||
|
s.RefreshToken = sessionResp.RefreshToken
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) getLoginParam() (*LoginParam, error) {
|
||||||
|
res, err := s.client.R().
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"appId": APP_ID,
|
||||||
|
"clientType": CLIENT_TYPE,
|
||||||
|
"returnURL": RETURN_URL,
|
||||||
|
"timeStamp": fmt.Sprint(timestamp()),
|
||||||
|
}).
|
||||||
|
Get(WEB_URL + "/api/portal/unifyLoginForPC.action")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debug(res.String())
|
||||||
|
param := &LoginParam{
|
||||||
|
CaptchaToken: regexp.MustCompile(`'captchaToken' value='(.+?)'`).FindStringSubmatch(res.String())[1],
|
||||||
|
Lt: regexp.MustCompile(`lt = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
||||||
|
ParamId: regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
||||||
|
ReqId: regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(res.String())[1],
|
||||||
|
jRsaKey: regexp.MustCompile(`"j_rsaKey" value="(.+?)"`).FindStringSubmatch(res.String())[1],
|
||||||
|
|
||||||
|
vCodeID: regexp.MustCompile(`token=([A-Za-z0-9&=]+)`).FindStringSubmatch(res.String())[1],
|
||||||
|
}
|
||||||
|
|
||||||
|
imgRes, err := s.client.R().Get(fmt.Sprint(AUTH_URL, "/api/logbox/oauth2/picCaptcha.do?token=", param.vCodeID, timestamp()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(imgRes.Body()) > 0 {
|
||||||
|
vRes, err := resty.New().R().
|
||||||
|
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
|
||||||
|
Post(conf.GetStr("ocr api"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||||
|
return nil, errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
||||||
|
}
|
||||||
|
param.vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
|
||||||
|
log.Debugln("code: ", param.vCodeRS)
|
||||||
|
}
|
||||||
|
return param, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) refreshSession(account *model.Account) error {
|
||||||
|
var erron Erron
|
||||||
|
var userSessionResp UserSessionResp
|
||||||
|
res, err := s.client.R().
|
||||||
|
SetResult(&userSessionResp).SetError(&erron).
|
||||||
|
SetQueryParams(clientSuffix()).
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"appId": APP_ID,
|
||||||
|
"accessToken": s.AccessToken,
|
||||||
|
}).
|
||||||
|
SetHeader("X-Request-ID", uuid.NewString()).
|
||||||
|
Get("https://api.cloud.189.cn/getSessionForPC.action")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debug(res.String())
|
||||||
|
if erron.ResCode != "" {
|
||||||
|
return fmt.Errorf(erron.ResMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch userSessionResp.ResCode {
|
||||||
|
case 0:
|
||||||
|
s.SessionKey = userSessionResp.SessionKey
|
||||||
|
s.SessionSecret = userSessionResp.SessionSecret
|
||||||
|
s.FamilySessionKey = userSessionResp.FamilySessionKey
|
||||||
|
s.FamilySessionSecret = userSessionResp.FamilySessionSecret
|
||||||
|
case 11, 18:
|
||||||
|
return s.login(account)
|
||||||
|
default:
|
||||||
|
account.Status = userSessionResp.ResMessage
|
||||||
|
_ = model.SaveAccount(account)
|
||||||
|
return fmt.Errorf(userSessionResp.ResMessage)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) IsLogin() bool {
|
||||||
|
_, err := s.Request("GET", API_URL+"/getUserInfo.action", nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
}, nil)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) Login(account *model.Account) error {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
return s.login(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) RefreshSession(account *model.Account) error {
|
||||||
|
s.Lock()
|
||||||
|
defer s.Unlock()
|
||||||
|
return s.refreshSession(account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) Request(method string, fullUrl string, params url.Values, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
|
||||||
|
s.Lock()
|
||||||
|
dateOfGmt := getHttpDateStr()
|
||||||
|
sessionKey := s.SessionKey
|
||||||
|
sessionSecret := s.SessionSecret
|
||||||
|
if account != nil && isFamily(account) {
|
||||||
|
sessionKey = s.FamilySessionKey
|
||||||
|
sessionSecret = s.FamilySessionSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
req := s.client.R()
|
||||||
|
req.SetHeaders(map[string]string{
|
||||||
|
"Date": dateOfGmt,
|
||||||
|
"SessionKey": sessionKey,
|
||||||
|
"X-Request-ID": uuid.NewString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置params
|
||||||
|
var paramsData string
|
||||||
|
if params != nil {
|
||||||
|
paramsData = AesECBEncrypt(params.Encode(), s.SessionSecret[:16])
|
||||||
|
req.SetQueryParam("params", paramsData)
|
||||||
|
}
|
||||||
|
req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, fullUrl, dateOfGmt, paramsData))
|
||||||
|
|
||||||
|
callback(req)
|
||||||
|
s.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var res *resty.Response
|
||||||
|
switch method {
|
||||||
|
case "GET":
|
||||||
|
res, err = req.Get(fullUrl)
|
||||||
|
case "POST":
|
||||||
|
res, err = req.Post(fullUrl)
|
||||||
|
case "DELETE":
|
||||||
|
res, err = req.Delete(fullUrl)
|
||||||
|
case "PATCH":
|
||||||
|
res, err = req.Patch(fullUrl)
|
||||||
|
case "PUT":
|
||||||
|
res, err = req.Put(fullUrl)
|
||||||
|
default:
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.Debug(res.String())
|
||||||
|
|
||||||
|
var erron Erron
|
||||||
|
utils.Json.Unmarshal(res.Body(), &erron)
|
||||||
|
if erron.ResCode != "" {
|
||||||
|
return nil, fmt.Errorf(erron.ResMessage)
|
||||||
|
}
|
||||||
|
if erron.Code != "" && erron.Code != "SUCCESS" {
|
||||||
|
if erron.Msg == "" {
|
||||||
|
return nil, fmt.Errorf(erron.Message)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(erron.Msg)
|
||||||
|
}
|
||||||
|
if erron.ErrorCode != "" {
|
||||||
|
return nil, fmt.Errorf(erron.ErrorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if account != nil {
|
||||||
|
switch jsoniter.Get(res.Body(), "res_code").ToInt64() {
|
||||||
|
case 11, 18:
|
||||||
|
if err := s.refreshSession(account); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.Request(method, fullUrl, params, callback, account)
|
||||||
|
case 0:
|
||||||
|
if res.StatusCode() == http.StatusOK {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf(res.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsoniter.Get(res.Body(), "res_code").ToInt64() != 0 {
|
||||||
|
return res, fmt.Errorf(jsoniter.Get(res.Body(), "res_message").ToString())
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
804
drivers/189pc/driver.go
Normal file
804
drivers/189pc/driver.go
Normal file
@ -0,0 +1,804 @@
|
|||||||
|
package _189
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(new(Cloud189))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cloud189 struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Config() base.DriverConfig {
|
||||||
|
return base.DriverConfig{
|
||||||
|
Name: "189CloudPC",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Items() []base.Item {
|
||||||
|
return []base.Item{
|
||||||
|
{
|
||||||
|
Name: "username",
|
||||||
|
Label: "username",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "account username/phone number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "password",
|
||||||
|
Label: "password",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "account password",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "root_folder",
|
||||||
|
Label: "root folder file_id",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "internal_type",
|
||||||
|
Label: "189cloud type",
|
||||||
|
Type: base.TypeSelect,
|
||||||
|
Required: true,
|
||||||
|
Values: "Personal,Family",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "site_id",
|
||||||
|
Label: "family id",
|
||||||
|
Type: base.TypeString,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "order_by",
|
||||||
|
Label: "order_by",
|
||||||
|
Type: base.TypeSelect,
|
||||||
|
Values: "filename,filesize,lastOpTime",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "order_direction",
|
||||||
|
Label: "desc",
|
||||||
|
Type: base.TypeSelect,
|
||||||
|
Values: "true,false",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
|
||||||
|
if account == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isFamily(account) && account.RootFolder == "" {
|
||||||
|
account.RootFolder = "-11"
|
||||||
|
}
|
||||||
|
|
||||||
|
state := GetState(account)
|
||||||
|
if !state.IsLogin() {
|
||||||
|
return state.Login(account)
|
||||||
|
}
|
||||||
|
account.Status = "work"
|
||||||
|
model.SaveAccount(account)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) File(path string, account *model.Account) (*model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
if path == "/" {
|
||||||
|
return &model.File{
|
||||||
|
Id: account.RootFolder,
|
||||||
|
Name: account.Name,
|
||||||
|
Size: 0,
|
||||||
|
Type: conf.FOLDER,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: account.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
dir, name := filepath.Split(path)
|
||||||
|
files, err := driver.Files(dir, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if file.Name == name {
|
||||||
|
return &file, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, base.ErrPathNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
cache, err := base.GetCache(path, account)
|
||||||
|
if err == nil {
|
||||||
|
files, _ := cache.([]model.File)
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fullUrl := API_URL
|
||||||
|
if isFamily(account) {
|
||||||
|
fullUrl += "/family/file"
|
||||||
|
}
|
||||||
|
fullUrl += "/listFiles.action"
|
||||||
|
|
||||||
|
files := make([]model.File, 0)
|
||||||
|
client := GetState(account)
|
||||||
|
for pageNum := 1; ; pageNum++ {
|
||||||
|
var resp Cloud189FilesResp
|
||||||
|
queryparam := map[string]string{
|
||||||
|
"folderId": file.Id,
|
||||||
|
"fileType": "0",
|
||||||
|
"mediaAttr": "0",
|
||||||
|
"iconOption": "5",
|
||||||
|
"pageNum": fmt.Sprint(pageNum),
|
||||||
|
"pageSize": "130",
|
||||||
|
}
|
||||||
|
_, err = client.Request("GET", fullUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix()).SetQueryParams(queryparam)
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"familyId": account.SiteId,
|
||||||
|
"orderBy": toFamilyOrderBy(account.OrderBy),
|
||||||
|
"descending": account.OrderDirection,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"recursive": "0",
|
||||||
|
"orderBy": account.OrderBy,
|
||||||
|
"descending": account.OrderDirection,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
r.SetResult(&resp)
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 获取完毕跳出
|
||||||
|
if resp.FileListAO.Count == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
mustTime := func(str string) *time.Time {
|
||||||
|
time, _ := http.ParseTime(str)
|
||||||
|
return &time
|
||||||
|
}
|
||||||
|
for _, folder := range resp.FileListAO.FolderList {
|
||||||
|
files = append(files, model.File{
|
||||||
|
Id: fmt.Sprint(folder.ID),
|
||||||
|
Name: folder.Name,
|
||||||
|
Size: 0,
|
||||||
|
Type: conf.FOLDER,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: mustTime(folder.CreateDate),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, file := range resp.FileListAO.FileList {
|
||||||
|
files = append(files, model.File{
|
||||||
|
Id: fmt.Sprint(file.ID),
|
||||||
|
Name: file.Name,
|
||||||
|
Size: file.Size,
|
||||||
|
Type: utils.GetFileType(filepath.Ext(file.Name)),
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: mustTime(file.CreateDate),
|
||||||
|
Thumbnail: file.Icon.SmallUrl,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(files) > 0 {
|
||||||
|
_ = base.SetCache(path, files, account)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
log.Debugf("189PC path: %s", path)
|
||||||
|
file, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !file.IsDir() {
|
||||||
|
return file, nil, nil
|
||||||
|
}
|
||||||
|
files, err := driver.Files(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return nil, files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||||
|
file, err := driver.File(utils.ParsePath(args.Path), account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if file.Type == conf.FOLDER {
|
||||||
|
return nil, base.ErrNotFile
|
||||||
|
}
|
||||||
|
|
||||||
|
fullUrl := API_URL
|
||||||
|
if isFamily(account) {
|
||||||
|
fullUrl += "/family/file"
|
||||||
|
}
|
||||||
|
fullUrl += "/getFileDownloadUrl.action"
|
||||||
|
|
||||||
|
var downloadUrl struct {
|
||||||
|
URL string `json:"fileDownloadUrl"`
|
||||||
|
}
|
||||||
|
_, err = GetState(account).Request("GET", fullUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix()).SetQueryParam("fileId", file.Id)
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"familyId": account.SiteId,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"dt": "3",
|
||||||
|
"flag": "1",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
r.SetResult(&downloadUrl)
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &base.Link{
|
||||||
|
Headers: []base.Header{
|
||||||
|
{Name: "User-Agent", Value: base.UserAgent},
|
||||||
|
},
|
||||||
|
Url: strings.ReplaceAll(downloadUrl.URL, "&", "&"),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
|
||||||
|
dir, name := filepath.Split(path)
|
||||||
|
parentFile, err := driver.File(dir, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !parentFile.IsDir() {
|
||||||
|
return base.ErrNotFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
fullUrl := API_URL
|
||||||
|
if isFamily(account) {
|
||||||
|
fullUrl += "/family/file"
|
||||||
|
}
|
||||||
|
fullUrl += "/createFolder.action"
|
||||||
|
|
||||||
|
_, err = GetState(account).Request("POST", fullUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix()).SetQueryParams(map[string]string{
|
||||||
|
"folderName": name,
|
||||||
|
"relativePath": "",
|
||||||
|
})
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"familyId": account.SiteId,
|
||||||
|
"parentId": parentFile.Id,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"parentFolderId": parentFile.Id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
|
||||||
|
srcFile, err := driver.File(src, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = GetState(account).Request("POST", API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
||||||
|
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
||||||
|
"type": "MOVE",
|
||||||
|
"taskInfos": string(MustToBytes(json.Marshal(
|
||||||
|
[]*BatchTaskInfo{
|
||||||
|
{
|
||||||
|
FileId: srcFile.Id,
|
||||||
|
FileName: srcFile.Name,
|
||||||
|
IsFolder: BoolToNumber(srcFile.IsDir()),
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
"targetFolderId": dstDirFile.Id,
|
||||||
|
})
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetFormData(map[string]string{
|
||||||
|
"familyId": account.SiteId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
|
||||||
|
srcFile, err := driver.File(src, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryParam map[string]string
|
||||||
|
fullUrl := API_URL
|
||||||
|
method := "POST"
|
||||||
|
if isFamily(account) {
|
||||||
|
fullUrl += "/family/file"
|
||||||
|
method = "GET"
|
||||||
|
}
|
||||||
|
if srcFile.IsDir() {
|
||||||
|
fullUrl += "/moveFolder.action"
|
||||||
|
queryParam = map[string]string{
|
||||||
|
"folderId": srcFile.Id,
|
||||||
|
"destFolderName": srcFile.Name,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fullUrl += "/moveFile.action"
|
||||||
|
queryParam = map[string]string{
|
||||||
|
"fileId": srcFile.Id,
|
||||||
|
"destFileName": srcFile.Name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"familyId": account.SiteId,
|
||||||
|
"destParentId": dstDirFile.Id,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
r.SetQueryParam("destParentFolderId", dstDirFile.Id)
|
||||||
|
}
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}*/
|
||||||
|
|
||||||
|
func (driver Cloud189) Rename(src string, dst string, account *model.Account) error {
|
||||||
|
srcFile, err := driver.File(src, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var queryParam map[string]string
|
||||||
|
fullUrl := API_URL
|
||||||
|
method := "POST"
|
||||||
|
if isFamily(account) {
|
||||||
|
fullUrl += "/family/file"
|
||||||
|
method = "GET"
|
||||||
|
}
|
||||||
|
if srcFile.IsDir() {
|
||||||
|
fullUrl += "/renameFolder.action"
|
||||||
|
queryParam = map[string]string{
|
||||||
|
"folderId": srcFile.Id,
|
||||||
|
"destFolderName": filepath.Base(dst),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fullUrl += "/renameFile.action"
|
||||||
|
queryParam = map[string]string{
|
||||||
|
"fileId": srcFile.Id,
|
||||||
|
"destFileName": filepath.Base(dst),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetQueryParam("familyId", account.SiteId)
|
||||||
|
}
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
|
||||||
|
srcFile, err := driver.File(src, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
dstDirFile, err := driver.File(filepath.Dir(dst), account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = GetState(account).Request("POST", API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
||||||
|
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
||||||
|
"type": "COPY",
|
||||||
|
"taskInfos": string(MustToBytes(json.Marshal(
|
||||||
|
[]*BatchTaskInfo{
|
||||||
|
{
|
||||||
|
FileId: srcFile.Id,
|
||||||
|
FileName: srcFile.Name,
|
||||||
|
IsFolder: BoolToNumber(srcFile.IsDir()),
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
"targetFolderId": dstDirFile.Id,
|
||||||
|
"targetFileName": filepath.Base(dst),
|
||||||
|
})
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetFormData(map[string]string{
|
||||||
|
"familyId": account.SiteId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Delete(path string, account *model.Account) error {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
srcFile, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = GetState(account).Request("POST", API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
|
||||||
|
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
||||||
|
"type": "DELETE",
|
||||||
|
"taskInfos": string(MustToBytes(json.Marshal(
|
||||||
|
[]*BatchTaskInfo{
|
||||||
|
{
|
||||||
|
FileId: srcFile.Id,
|
||||||
|
FileName: srcFile.Name,
|
||||||
|
IsFolder: BoolToNumber(srcFile.IsDir()),
|
||||||
|
},
|
||||||
|
}))),
|
||||||
|
})
|
||||||
|
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetFormData(map[string]string{
|
||||||
|
"familyId": account.SiteId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
|
if file == nil {
|
||||||
|
return base.ErrEmptyFile
|
||||||
|
}
|
||||||
|
|
||||||
|
parentFile, err := driver.File(file.ParentPath, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !parentFile.IsDir() {
|
||||||
|
return base.ErrNotFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFamily(account) {
|
||||||
|
return driver.uploadFamily(file, parentFile, account)
|
||||||
|
}
|
||||||
|
return driver.uploadPerson(file, parentFile, account)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) uploadFamily(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
||||||
|
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tempFile.Close()
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
fileMd5 := md5.New()
|
||||||
|
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := GetState(account)
|
||||||
|
var createUpload CreateUploadFileResult
|
||||||
|
_, err = client.Request("GET", API_URL+"/family/file/createFamilyFile.action", nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"fileMd5": hex.EncodeToString(fileMd5.Sum(nil)),
|
||||||
|
"fileName": file.Name,
|
||||||
|
"familyId": account.SiteId,
|
||||||
|
"parentId": parentFile.Id,
|
||||||
|
"resumePolicy": "1",
|
||||||
|
"fileSize": fmt.Sprint(file.Size),
|
||||||
|
})
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
r.SetResult(&createUpload)
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if createUpload.FileDataExists != 1 {
|
||||||
|
if err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Request("GET", createUpload.FileCommitUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
r.SetHeaders(map[string]string{
|
||||||
|
"FamilyId": account.SiteId,
|
||||||
|
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
|
||||||
|
"ResumePolicy": "1",
|
||||||
|
})
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) uploadPerson(file *model.FileStream, parentFile *model.File, account *model.Account) error {
|
||||||
|
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tempFile.Close()
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
fileMd5 := md5.New()
|
||||||
|
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client := GetState(account)
|
||||||
|
var createUpload CreateUploadFileResult
|
||||||
|
_, err = client.Request("POST", API_URL+"/createUploadFile.action", nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
|
||||||
|
"parentFolderId": parentFile.Id,
|
||||||
|
"baseFileId": "",
|
||||||
|
"fileName": file.Name,
|
||||||
|
"size": fmt.Sprint(file.Size),
|
||||||
|
"md5": hex.EncodeToString(fileMd5.Sum(nil)),
|
||||||
|
// "lastWrite": param.LastWrite,
|
||||||
|
// "localPath": strings.ReplaceAll(file.ParentPath, "\\", "/"),
|
||||||
|
"opertype": "1",
|
||||||
|
"flag": "1",
|
||||||
|
"resumePolicy": "1",
|
||||||
|
"isLog": "0",
|
||||||
|
"fileExt": "",
|
||||||
|
})
|
||||||
|
r.SetResult(&createUpload)
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if createUpload.FileDataExists != 1 {
|
||||||
|
if err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Request("POST", createUpload.FileCommitUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
r.SetFormData(map[string]string{
|
||||||
|
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
|
||||||
|
"opertype": "1", //5 覆盖
|
||||||
|
"ResumePolicy": "1",
|
||||||
|
"isLog": "0",
|
||||||
|
})
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) uploadFileData(file *model.FileStream, tempFile *os.File, createUpload CreateUploadFileResult, account *model.Account) error {
|
||||||
|
var uploadFileState *UploadFileStatusResult
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
if uploadFileState, err = driver.getUploadFileState(createUpload.UploadFileId, account); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if uploadFileState.FileDataExists == 1 || uploadFileState.DataSize == int64(file.Size) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = tempFile.Seek(uploadFileState.DataSize, io.SeekStart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = GetState(account).Request("PUT", uploadFileState.FileUploadUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
r.SetHeaders(map[string]string{
|
||||||
|
"ResumePolicy": "1",
|
||||||
|
"Edrive-UploadFileId": fmt.Sprint(createUpload.UploadFileId),
|
||||||
|
"Edrive-UploadFileRange": fmt.Sprintf("bytes=%d-%d", uploadFileState.DataSize, file.Size),
|
||||||
|
"Expect": "100-continue",
|
||||||
|
})
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetHeader("FamilyId", account.SiteId)
|
||||||
|
}
|
||||||
|
r.SetBody(tempFile)
|
||||||
|
}, account)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Cloud189) getUploadFileState(uploadFileId int64, account *model.Account) (*UploadFileStatusResult, error) {
|
||||||
|
fullUrl := API_URL
|
||||||
|
if isFamily(account) {
|
||||||
|
fullUrl += "/family/file/getFamilyFileStatus.action"
|
||||||
|
} else {
|
||||||
|
fullUrl += "/getUploadFileStatus.action"
|
||||||
|
}
|
||||||
|
var uploadFileState UploadFileStatusResult
|
||||||
|
_, err := GetState(account).Request("GET", fullUrl, nil, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
r.SetQueryParams(map[string]string{
|
||||||
|
"uploadFileId": fmt.Sprint(uploadFileId),
|
||||||
|
"resumePolicy": "1",
|
||||||
|
})
|
||||||
|
if isFamily(account) {
|
||||||
|
r.SetQueryParam("familyId", account.SiteId)
|
||||||
|
}
|
||||||
|
r.SetResult(&uploadFileState)
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &uploadFileState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
暂时未解决
|
||||||
|
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
|
if file == nil {
|
||||||
|
return base.ErrEmptyFile
|
||||||
|
}
|
||||||
|
|
||||||
|
parentFile, err := driver.File(file.ParentPath, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !parentFile.IsDir() {
|
||||||
|
return base.ErrNotFolder
|
||||||
|
}
|
||||||
|
|
||||||
|
fullUrl := UPLOAD_URL
|
||||||
|
if isFamily(account) {
|
||||||
|
fullUrl += "/family"
|
||||||
|
} else {
|
||||||
|
fullUrl += "/person"
|
||||||
|
}
|
||||||
|
|
||||||
|
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tempFile.Close()
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
|
// 初始化上传
|
||||||
|
const DEFAULT int64 = 10485760
|
||||||
|
count := int64(math.Ceil(float64(file.Size) / float64(DEFAULT)))
|
||||||
|
fileMd5 := md5.New()
|
||||||
|
silceMd5 := md5.New()
|
||||||
|
silceMd5Hexs := make([]string, 0, count)
|
||||||
|
silceMd5Base64s := make([]string, 0, count)
|
||||||
|
for i := int64(1); i <= count; i++ {
|
||||||
|
if _, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, tempFile), file, DEFAULT); err != io.EOF {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
md5Byte := silceMd5.Sum(nil)
|
||||||
|
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Byte)))
|
||||||
|
silceMd5Base64s = append(silceMd5Base64s, fmt.Sprint(i, "-", base64.StdEncoding.EncodeToString(md5Byte)))
|
||||||
|
}
|
||||||
|
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
||||||
|
sliceMd5Hex := fileMd5Hex
|
||||||
|
if int64(file.Size) > DEFAULT {
|
||||||
|
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
||||||
|
}
|
||||||
|
|
||||||
|
qID := uuid.NewString()
|
||||||
|
client := GetState(account)
|
||||||
|
param := MapToUrlValues(map[string]interface{}{
|
||||||
|
"parentFolderId": parentFile.Id,
|
||||||
|
"fileName": url.QueryEscape(file.Name),
|
||||||
|
"fileMd5": fileMd5Hex,
|
||||||
|
"fileSize": fmt.Sprint(file.Size),
|
||||||
|
"sliceMd5": sliceMd5Hex,
|
||||||
|
"sliceSize": fmt.Sprint(DEFAULT),
|
||||||
|
})
|
||||||
|
if isFamily(account) {
|
||||||
|
param.Set("familyId", account.SiteId)
|
||||||
|
}
|
||||||
|
|
||||||
|
var uploadInfo InitMultiUploadResp
|
||||||
|
_, err = client.Request("GET", fullUrl+"/initMultiUpload", param, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
r.SetHeader("X-Request-ID", qID)
|
||||||
|
r.SetResult(&uploadInfo)
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if uploadInfo.Data.FileDataExists != 1 {
|
||||||
|
param = MapToUrlValues(map[string]interface{}{
|
||||||
|
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||||
|
"partInfo": strings.Join(silceMd5Base64s, ","),
|
||||||
|
})
|
||||||
|
if isFamily(account) {
|
||||||
|
param.Set("familyId", account.SiteId)
|
||||||
|
}
|
||||||
|
var uploadUrls UploadUrlsResp
|
||||||
|
_, err := client.Request("GET", fullUrl+"/getMultiUploadUrls", param, func(r *resty.Request) {
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
r.SetHeader("X-Request-ID", qID).SetHeader("content-type", "application/x-www-form-urlencoded")
|
||||||
|
r.SetResult(&uploadUrls)
|
||||||
|
|
||||||
|
}, account)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var i int64
|
||||||
|
for _, uploadurl := range uploadUrls.UploadUrls {
|
||||||
|
req := resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).SetProxy("http://192.168.0.30:8888").R()
|
||||||
|
for _, header := range strings.Split(decodeURIComponent(uploadurl.RequestHeader), "&") {
|
||||||
|
i := strings.Index(header, "=")
|
||||||
|
req.SetHeader(header[0:i], header[i+1:])
|
||||||
|
}
|
||||||
|
_, err := req.SetBody(io.NewSectionReader(tempFile, i*DEFAULT, DEFAULT)).Put(uploadurl.RequestURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
param = MapToUrlValues(map[string]interface{}{
|
||||||
|
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||||
|
"isLog": "0",
|
||||||
|
"opertype": "1",
|
||||||
|
})
|
||||||
|
if isFamily(account) {
|
||||||
|
param.Set("familyId", account.SiteId)
|
||||||
|
}
|
||||||
|
_, err = client.Request("GET", fullUrl+"/commitMultiUploadFile", param, func(r *resty.Request) {
|
||||||
|
r.SetHeader("X-Request-ID", qID)
|
||||||
|
r.SetQueryParams(clientSuffix())
|
||||||
|
}, account)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
var _ base.Driver = (*Cloud189)(nil)
|
167
drivers/189pc/type.go
Normal file
167
drivers/189pc/type.go
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package _189
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
type LoginParam struct {
|
||||||
|
CaptchaToken string
|
||||||
|
Lt string
|
||||||
|
ParamId string
|
||||||
|
ReqId string
|
||||||
|
jRsaKey string
|
||||||
|
|
||||||
|
vCodeID string
|
||||||
|
vCodeRS string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 居然有四种返回方式
|
||||||
|
type Erron struct {
|
||||||
|
ResCode string `json:"res_code"`
|
||||||
|
ResMessage string `json:"res_message"`
|
||||||
|
|
||||||
|
XMLName xml.Name `xml:"error"`
|
||||||
|
Code string `json:"code" xml:"code"`
|
||||||
|
Message string `json:"message" xml:"message"`
|
||||||
|
|
||||||
|
// Code string `json:"code"`
|
||||||
|
Msg string `json:"msg"`
|
||||||
|
|
||||||
|
ErrorCode string `json:"errorCode"`
|
||||||
|
ErrorMsg string `json:"errorMsg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新session返回
|
||||||
|
type UserSessionResp struct {
|
||||||
|
ResCode int `json:"res_code"`
|
||||||
|
ResMessage string `json:"res_message"`
|
||||||
|
|
||||||
|
LoginName string `json:"loginName"`
|
||||||
|
|
||||||
|
KeepAlive int `json:"keepAlive"`
|
||||||
|
GetFileDiffSpan int `json:"getFileDiffSpan"`
|
||||||
|
GetUserInfoSpan int `json:"getUserInfoSpan"`
|
||||||
|
|
||||||
|
// 个人云
|
||||||
|
SessionKey string `json:"sessionKey"`
|
||||||
|
SessionSecret string `json:"sessionSecret"`
|
||||||
|
// 家庭云
|
||||||
|
FamilySessionKey string `json:"familySessionKey"`
|
||||||
|
FamilySessionSecret string `json:"familySessionSecret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//登录返回
|
||||||
|
type appSessionResp struct {
|
||||||
|
UserSessionResp
|
||||||
|
|
||||||
|
IsSaveName string `json:"isSaveName"`
|
||||||
|
|
||||||
|
// 会话刷新Token
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
|
//Token刷新
|
||||||
|
RefreshToken string `json:"refreshToken"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*文件部分*/
|
||||||
|
// 文件
|
||||||
|
type Cloud189File struct {
|
||||||
|
CreateDate string `json:"createDate"`
|
||||||
|
FileCata int64 `json:"fileCata"`
|
||||||
|
Icon struct {
|
||||||
|
//iconOption 5
|
||||||
|
SmallUrl string `json:"smallUrl"`
|
||||||
|
LargeUrl string `json:"largeUrl"`
|
||||||
|
|
||||||
|
// iconOption 10
|
||||||
|
Max600 string `json:"max600"`
|
||||||
|
MediumURL string `json:"mediumUrl"`
|
||||||
|
} `json:"icon"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
LastOpTime string `json:"lastOpTime"`
|
||||||
|
Md5 string `json:"md5"`
|
||||||
|
MediaType int `json:"mediaType"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Orientation int64 `json:"orientation"`
|
||||||
|
Rev string `json:"rev"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
StarLabel int64 `json:"starLabel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件夹
|
||||||
|
type Cloud189Folder struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
ParentID int64 `json:"parentId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
|
||||||
|
FileCata int64 `json:"fileCata"`
|
||||||
|
FileCount int64 `json:"fileCount"`
|
||||||
|
|
||||||
|
LastOpTime string `json:"lastOpTime"`
|
||||||
|
CreateDate string `json:"createDate"`
|
||||||
|
|
||||||
|
FileListSize int64 `json:"fileListSize"`
|
||||||
|
Rev string `json:"rev"`
|
||||||
|
StarLabel int64 `json:"starLabel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cloud189FilesResp struct {
|
||||||
|
//ResCode int `json:"res_code"`
|
||||||
|
//ResMessage string `json:"res_message"`
|
||||||
|
FileListAO struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
FileList []Cloud189File `json:"fileList"`
|
||||||
|
FolderList []Cloud189Folder `json:"folderList"`
|
||||||
|
} `json:"fileListAO"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskInfo 任务信息
|
||||||
|
type BatchTaskInfo struct {
|
||||||
|
// FileId 文件ID
|
||||||
|
FileId string `json:"fileId"`
|
||||||
|
// FileName 文件名
|
||||||
|
FileName string `json:"fileName"`
|
||||||
|
// IsFolder 是否是文件夹,0-否,1-是
|
||||||
|
IsFolder int `json:"isFolder"`
|
||||||
|
// SrcParentId 文件所在父目录ID
|
||||||
|
//SrcParentId string `json:"srcParentId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateUploadFileResult struct {
|
||||||
|
// UploadFileId 上传文件请求ID
|
||||||
|
UploadFileId int64 `json:"uploadFileId"`
|
||||||
|
// FileUploadUrl 上传文件数据的URL路径
|
||||||
|
FileUploadUrl string `json:"fileUploadUrl"`
|
||||||
|
// FileCommitUrl 上传文件完成后确认路径
|
||||||
|
FileCommitUrl string `json:"fileCommitUrl"`
|
||||||
|
// FileDataExists 文件是否已存在云盘中,0-未存在,1-已存在
|
||||||
|
FileDataExists int `json:"fileDataExists"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadFileStatusResult struct {
|
||||||
|
// 上传文件的ID
|
||||||
|
UploadFileId int64 `json:"uploadFileId"`
|
||||||
|
// 已上传的大小
|
||||||
|
DataSize int64 `json:"dataSize"`
|
||||||
|
FileUploadUrl string `json:"fileUploadUrl"`
|
||||||
|
FileCommitUrl string `json:"fileCommitUrl"`
|
||||||
|
FileDataExists int `json:"fileDataExists"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
type InitMultiUploadResp struct {
|
||||||
|
//Code string `json:"code"`
|
||||||
|
Data struct {
|
||||||
|
UploadType int `json:"uploadType"`
|
||||||
|
UploadHost string `json:"uploadHost"`
|
||||||
|
UploadFileID string `json:"uploadFileId"`
|
||||||
|
FileDataExists int `json:"fileDataExists"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
type UploadUrlsResp struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
UploadUrls map[string]Part `json:"uploadUrls"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Part struct {
|
||||||
|
RequestURL string `json:"requestURL"`
|
||||||
|
RequestHeader string `json:"requestHeader"`
|
||||||
|
}
|
||||||
|
*/
|
145
drivers/189pc/util.go
Normal file
145
drivers/189pc/util.go
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
package _189
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
rand2 "math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
APP_ID = "8025431004"
|
||||||
|
CLIENT_TYPE = "10020"
|
||||||
|
VERSION = "6.2"
|
||||||
|
|
||||||
|
WEB_URL = "https://cloud.189.cn"
|
||||||
|
AUTH_URL = "https://open.e.189.cn"
|
||||||
|
API_URL = "https://api.cloud.189.cn"
|
||||||
|
UPLOAD_URL = "https://upload.cloud.189.cn"
|
||||||
|
|
||||||
|
RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"
|
||||||
|
|
||||||
|
PC = "TELEPC"
|
||||||
|
MAC = "TELEMAC"
|
||||||
|
|
||||||
|
CHANNEL_ID = "web_cloud.189.cn"
|
||||||
|
)
|
||||||
|
|
||||||
|
func clientSuffix() map[string]string {
|
||||||
|
return map[string]string{
|
||||||
|
"clientType": PC,
|
||||||
|
"version": VERSION,
|
||||||
|
"channelId": CHANNEL_ID,
|
||||||
|
"rand": fmt.Sprintf("%d_%d", rand2.Int63n(1e5), rand2.Int63n(1e10)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 带params的SignatureOfHmac HMAC签名
|
||||||
|
func signatureOfHmac(sessionSecret, sessionKey, operate, fullUrl, dateOfGmt, param string) string {
|
||||||
|
u, _ := url.Parse(fullUrl)
|
||||||
|
mac := hmac.New(sha1.New, []byte(sessionSecret))
|
||||||
|
data := fmt.Sprintf("SessionKey=%s&Operate=%s&RequestURI=%s&Date=%s", sessionKey, operate, u.Path, dateOfGmt)
|
||||||
|
if param != "" {
|
||||||
|
data += fmt.Sprintf("¶ms=%s", param)
|
||||||
|
}
|
||||||
|
mac.Write([]byte(data))
|
||||||
|
return strings.ToUpper(hex.EncodeToString(mac.Sum(nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取http规范的时间
|
||||||
|
func getHttpDateStr() string {
|
||||||
|
return time.Now().UTC().Format(http.TimeFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RAS 加密用户名密码
|
||||||
|
func rsaEncrypt(publicKey, origData string) string {
|
||||||
|
block, _ := pem.Decode([]byte(publicKey))
|
||||||
|
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
data, _ := rsa.EncryptPKCS1v15(rand.Reader, pubInterface.(*rsa.PublicKey), []byte(origData))
|
||||||
|
return base64ToHex(base64.StdEncoding.EncodeToString(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// aes 加密params
|
||||||
|
func AesECBEncrypt(data, key string) string {
|
||||||
|
block, _ := aes.NewCipher([]byte(key))
|
||||||
|
paddingData := PKCS7Padding([]byte(data), block.BlockSize())
|
||||||
|
decrypted := make([]byte, len(paddingData))
|
||||||
|
size := block.BlockSize()
|
||||||
|
for src, dst := paddingData, decrypted; len(src) > 0; src, dst = src[size:], dst[size:] {
|
||||||
|
block.Encrypt(dst[:size], src[:size])
|
||||||
|
}
|
||||||
|
return strings.ToUpper(hex.EncodeToString(decrypted))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
|
||||||
|
padding := blockSize - len(ciphertext)%blockSize
|
||||||
|
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||||
|
return append(ciphertext, padtext...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间戳
|
||||||
|
func timestamp() int64 {
|
||||||
|
return time.Now().UTC().UnixNano() / 1e6
|
||||||
|
}
|
||||||
|
|
||||||
|
func base64ToHex(a string) string {
|
||||||
|
v, _ := base64.StdEncoding.DecodeString(a)
|
||||||
|
return strings.ToUpper(hex.EncodeToString(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFamily(account *model.Account) bool {
|
||||||
|
return account.InternalType == "Family"
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFamilyOrderBy(o string) string {
|
||||||
|
switch o {
|
||||||
|
case "filename":
|
||||||
|
return "1"
|
||||||
|
case "filesize":
|
||||||
|
return "2"
|
||||||
|
case "lastOpTime":
|
||||||
|
return "3"
|
||||||
|
default:
|
||||||
|
return "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapToUrlValues(m map[string]interface{}) url.Values {
|
||||||
|
url := make(url.Values, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
url.Add(k, fmt.Sprint(v))
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeURIComponent(str string) string {
|
||||||
|
r, _ := url.QueryUnescape(str)
|
||||||
|
//r, _ := url.PathUnescape(str)
|
||||||
|
//r = strings.ReplaceAll(r, " ", "+")
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustToBytes(b []byte, err error) []byte {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoolToNumber(b bool) int {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
@ -2,18 +2,25 @@ package alidrive
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/Xhofe/alist/conf"
|
"github.com/Xhofe/alist/conf"
|
||||||
"github.com/Xhofe/alist/drivers/base"
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
"github.com/Xhofe/alist/model"
|
"github.com/Xhofe/alist/model"
|
||||||
"github.com/Xhofe/alist/utils"
|
"github.com/Xhofe/alist/utils"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"io"
|
|
||||||
"math"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AliDrive struct{}
|
type AliDrive struct{}
|
||||||
@ -376,15 +383,16 @@ type UploadResp struct {
|
|||||||
PartInfoList []struct {
|
PartInfoList []struct {
|
||||||
UploadUrl string `json:"upload_url"`
|
UploadUrl string `json:"upload_url"`
|
||||||
} `json:"part_info_list"`
|
} `json:"part_info_list"`
|
||||||
|
|
||||||
|
RapidUpload bool `json:"rapid_upload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
|
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
if file == nil {
|
if file == nil {
|
||||||
return base.ErrEmptyFile
|
return base.ErrEmptyFile
|
||||||
}
|
}
|
||||||
const DEFAULT uint64 = 10485760
|
const DEFAULT int64 = 10485760
|
||||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||||
var finish uint64 = 0
|
|
||||||
parentFile, err := driver.File(file.ParentPath, account)
|
parentFile, err := driver.File(file.ParentPath, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -392,32 +400,38 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
|||||||
if !parentFile.IsDir() {
|
if !parentFile.IsDir() {
|
||||||
return base.ErrNotFolder
|
return base.ErrNotFolder
|
||||||
}
|
}
|
||||||
var resp UploadResp
|
|
||||||
var e AliRespError
|
partInfoList := make([]base.Json, 0, count)
|
||||||
partInfoList := make([]base.Json, 0)
|
|
||||||
var i int64
|
var i int64
|
||||||
for i = 0; i < count; i++ {
|
for i = 0; i < count; i++ {
|
||||||
partInfoList = append(partInfoList, base.Json{
|
partInfoList = append(partInfoList, base.Json{
|
||||||
"part_number": i + 1,
|
"part_number": i + 1,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_, err = aliClient.R().SetResult(&resp).SetError(&e).
|
|
||||||
SetHeader("authorization", "Bearer\t"+account.AccessToken).
|
buf := make([]byte, 1024)
|
||||||
SetBody(base.Json{
|
n, _ := file.Read(buf[:])
|
||||||
"check_name_mode": "auto_rename",
|
reqBody := base.Json{
|
||||||
// content_hash
|
"check_name_mode": "auto_rename",
|
||||||
"content_hash_name": "none",
|
"drive_id": account.DriveId,
|
||||||
"drive_id": account.DriveId,
|
"name": file.GetFileName(),
|
||||||
"name": file.GetFileName(),
|
"parent_file_id": parentFile.Id,
|
||||||
"parent_file_id": parentFile.Id,
|
"part_info_list": partInfoList,
|
||||||
"part_info_list": partInfoList,
|
"size": file.GetSize(),
|
||||||
//proof_code
|
"type": "file",
|
||||||
"proof_version": "v1",
|
"pre_hash": utils.GetSHA1Encode(string(buf[:n])),
|
||||||
"size": file.GetSize(),
|
}
|
||||||
"type": "file",
|
fileReader := io.MultiReader(bytes.NewReader(buf[:n]), file.File)
|
||||||
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders") // /v2/file/create_with_proof
|
|
||||||
//log.Debugf("%+v\n%+v", resp, e)
|
var resp UploadResp
|
||||||
if e.Code != "" {
|
var e AliRespError
|
||||||
|
client := aliClient.R().SetResult(&resp).SetError(&e).SetHeader("authorization", "Bearer\t"+account.AccessToken).SetBody(reqBody)
|
||||||
|
|
||||||
|
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Code != "" && e.Code != "PreHashMatched" {
|
||||||
if e.Code == "AccessTokenInvalid" {
|
if e.Code == "AccessTokenInvalid" {
|
||||||
err = driver.RefreshToken(account)
|
err = driver.RefreshToken(account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -429,26 +443,59 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
|||||||
}
|
}
|
||||||
return fmt.Errorf("%s", e.Message)
|
return fmt.Errorf("%s", e.Message)
|
||||||
}
|
}
|
||||||
var byteSize uint64
|
|
||||||
for i = 0; i < count; i++ {
|
if e.Code == "PreHashMatched" {
|
||||||
byteSize = file.GetSize() - finish
|
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
|
||||||
if DEFAULT < byteSize {
|
|
||||||
byteSize = DEFAULT
|
|
||||||
}
|
|
||||||
log.Debugf("%d,%d", byteSize, finish)
|
|
||||||
byteData := make([]byte, byteSize)
|
|
||||||
n, err := io.ReadFull(file, byteData)
|
|
||||||
//n, err := file.Read(byteData)
|
|
||||||
//byteData, err := io.ReadAll(file)
|
|
||||||
//n := len(byteData)
|
|
||||||
log.Debug(err, n)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
finish += uint64(n)
|
defer tempFile.Close()
|
||||||
|
defer os.Remove(tempFile.Name())
|
||||||
|
|
||||||
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, bytes.NewBuffer(byteData))
|
delete(reqBody, "pre_hash")
|
||||||
|
h := sha1.New()
|
||||||
|
if _, err = io.Copy(tempFile, io.TeeReader(fileReader, h)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
|
||||||
|
reqBody["content_hash_name"] = "sha1"
|
||||||
|
reqBody["proof_version"] = "v1"
|
||||||
|
|
||||||
|
/*
|
||||||
|
js 隐性转换太坑不知道有没有bug
|
||||||
|
var n = e.access_token,
|
||||||
|
r = new BigNumber('0x'.concat(md5(n).slice(0, 16))),
|
||||||
|
i = new BigNumber(t.file.size),
|
||||||
|
o = i ? r.mod(i) : new gt.BigNumber(0);
|
||||||
|
(t.file.slice(o.toNumber(), Math.min(o.plus(8).toNumber(), t.file.size)))
|
||||||
|
*/
|
||||||
|
r, _ := new(big.Int).SetString(utils.GetMD5Encode(account.AccessToken)[:16], 16)
|
||||||
|
i := new(big.Int).SetUint64(file.Size)
|
||||||
|
o := r.Mod(r, i)
|
||||||
|
n, _ = io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
|
||||||
|
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
|
||||||
|
|
||||||
|
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if e.Code != "" && e.Code != "PreHashMatched" {
|
||||||
|
return fmt.Errorf("%s", e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.RapidUpload {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fileReader = tempFile
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = 0; i < count; i++ {
|
||||||
|
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, io.LimitReader(fileReader, DEFAULT))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -473,6 +520,9 @@ func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) er
|
|||||||
"file_id": resp.FileId,
|
"file_id": resp.FileId,
|
||||||
"upload_id": resp.UploadId,
|
"upload_id": resp.UploadId,
|
||||||
}).Post("https://api.aliyundrive.com/v2/file/complete")
|
}).Post("https://api.aliyundrive.com/v2/file/complete")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if e.Code != "" {
|
if e.Code != "" {
|
||||||
//if e.Code == "AccessTokenInvalid" {
|
//if e.Code == "AccessTokenInvalid" {
|
||||||
// err = driver.RefreshToken(account)
|
// err = driver.RefreshToken(account)
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
_ "github.com/Xhofe/alist/drivers/123"
|
_ "github.com/Xhofe/alist/drivers/123"
|
||||||
_ "github.com/Xhofe/alist/drivers/139"
|
_ "github.com/Xhofe/alist/drivers/139"
|
||||||
_ "github.com/Xhofe/alist/drivers/189"
|
_ "github.com/Xhofe/alist/drivers/189"
|
||||||
|
_ "github.com/Xhofe/alist/drivers/189pc"
|
||||||
_ "github.com/Xhofe/alist/drivers/alidrive"
|
_ "github.com/Xhofe/alist/drivers/alidrive"
|
||||||
_ "github.com/Xhofe/alist/drivers/alist"
|
_ "github.com/Xhofe/alist/drivers/alist"
|
||||||
_ "github.com/Xhofe/alist/drivers/baidu"
|
_ "github.com/Xhofe/alist/drivers/baidu"
|
||||||
|
@ -29,12 +29,14 @@ func (driver GoogleDrive) Items() []base.Item {
|
|||||||
Label: "client id",
|
Label: "client id",
|
||||||
Type: base.TypeString,
|
Type: base.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
Default: "202264815644.apps.googleusercontent.com",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "client_secret",
|
Name: "client_secret",
|
||||||
Label: "client secret",
|
Label: "client secret",
|
||||||
Type: base.TypeString,
|
Type: base.TypeString,
|
||||||
Required: true,
|
Required: true,
|
||||||
|
Default: "X4Z3ca8xfWDb1Voo-F9a7ZxJ",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "refresh_token",
|
Name: "refresh_token",
|
||||||
|
@ -125,13 +125,19 @@ func (driver Lanzou) Link(args base.Args, account *model.Account) (*base.Link, e
|
|||||||
}
|
}
|
||||||
log.Debugf("down file: %+v", file)
|
log.Debugf("down file: %+v", file)
|
||||||
downId := file.Id
|
downId := file.Id
|
||||||
|
pwd := ""
|
||||||
if account.InternalType == "cookie" {
|
if account.InternalType == "cookie" {
|
||||||
downId, err = driver.GetDownPageId(file.Id, account)
|
downId, pwd, err = driver.GetDownPageId(file.Id, account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
url, err := driver.GetLink(downId, account)
|
var url string
|
||||||
|
//if pwd != "" {
|
||||||
|
//url, err = driver.GetLinkWithPassword(downId, pwd, account)
|
||||||
|
//} else {
|
||||||
|
url, err = driver.GetLink(downId, pwd, account)
|
||||||
|
//}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -153,6 +153,7 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
pg++
|
pg++
|
||||||
|
time.Sleep(time.Second)
|
||||||
files = append(files, resp.Text...)
|
files = append(files, resp.Text...)
|
||||||
}
|
}
|
||||||
return files, nil
|
return files, nil
|
||||||
@ -164,29 +165,21 @@ func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
// GetDownPageId 获取下载页面的ID
|
// GetDownPageId 获取下载页面的ID
|
||||||
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, error) {
|
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, string, error) {
|
||||||
var resp LanZouFilesResp
|
var resp DownPageResp
|
||||||
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
|
||||||
SetFormData(map[string]string{
|
SetFormData(map[string]string{
|
||||||
"task": "22",
|
"task": "22",
|
||||||
"file_id": fileId,
|
"file_id": fileId,
|
||||||
}).Post("https://pc.woozooo.com/doupload.php")
|
}).Post("https://pc.woozooo.com/doupload.php")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
log.Debug(res.String())
|
log.Debug(res.String())
|
||||||
if resp.Zt != 1 {
|
if resp.Zt != 1 {
|
||||||
return "", fmt.Errorf("%v", resp.Info)
|
return "", "", fmt.Errorf("%v", resp.Info)
|
||||||
}
|
}
|
||||||
info, ok := resp.Info.(map[string]interface{})
|
return resp.Info.FId, resp.Info.Pwd, nil
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("%v", resp.Info)
|
|
||||||
}
|
|
||||||
fid, ok := info["f_id"].(string)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("%v", info["f_id"])
|
|
||||||
}
|
|
||||||
return fid, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type LanzouLinkResp struct {
|
type LanzouLinkResp struct {
|
||||||
@ -195,19 +188,20 @@ type LanzouLinkResp struct {
|
|||||||
Zt int `json:"zt"`
|
Zt int `json:"zt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, error) {
|
func (driver *Lanzou) GetLink(downId string, pwd string, account *model.Account) (string, error) {
|
||||||
shareUrl := account.SiteUrl
|
shareUrl := account.SiteUrl
|
||||||
u, err := url.Parse(shareUrl)
|
u, err := url.Parse(shareUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
log.Debugln(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||||
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
iframe := regexp.MustCompile(`<iframe class="ifr2" name=".{2,20}" src="(.+?)"`).FindStringSubmatch(res.String())
|
iframe := regexp.MustCompile(`<iframe class="ifr2" name=".{2,20}" src="(.+?)"`).FindStringSubmatch(res.String())
|
||||||
if len(iframe) == 0 {
|
if len(iframe) == 0 {
|
||||||
return "", fmt.Errorf("get down empty page")
|
return driver.GetLinkWithPassword(downId, pwd, res.String(), account)
|
||||||
}
|
}
|
||||||
iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
|
iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
|
||||||
res, err = base.RestyClient.R().Get(iframeUrl)
|
res, err = base.RestyClient.R().Get(iframeUrl)
|
||||||
@ -223,8 +217,12 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
|
|||||||
//sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
//sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||||
sign := regexp.MustCompile(`'sign':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
sign := regexp.MustCompile(`'sign':'(.+?)',`).FindStringSubmatch(res.String())[1]
|
||||||
//websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
//websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
||||||
//websign := regexp.MustCompile(`var websign = '(.+?)'`).FindStringSubmatch(res.String())[1]
|
|
||||||
websign := ""
|
websign := ""
|
||||||
|
websignR := regexp.MustCompile(`var websign = '(.+?)'`).FindStringSubmatch(res.String())
|
||||||
|
if len(websignR) > 1 {
|
||||||
|
websign = websignR[1]
|
||||||
|
}
|
||||||
|
//websign := ""
|
||||||
//websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
//websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
|
||||||
websignkey := regexp.MustCompile(`var websignkey = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
websignkey := regexp.MustCompile(`var websignkey = '(.+?)';`).FindStringSubmatch(res.String())[1]
|
||||||
var resp LanzouLinkResp
|
var resp LanzouLinkResp
|
||||||
@ -248,7 +246,38 @@ func (driver *Lanzou) GetLink(downId string, account *model.Account) (string, er
|
|||||||
if resp.Zt == 1 {
|
if resp.Zt == 1 {
|
||||||
return resp.Dom + "/file/" + resp.Url, nil
|
return resp.Dom + "/file/" + resp.Url, nil
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("can't get link")
|
return "", fmt.Errorf("failed get link")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver *Lanzou) GetLinkWithPassword(downId string, pwd string, html string, account *model.Account) (string, error) {
|
||||||
|
shareUrl := account.SiteUrl
|
||||||
|
u, err := url.Parse(shareUrl)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if html == "" {
|
||||||
|
log.Debugln(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||||
|
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
html = res.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
data := regexp.MustCompile(`data : '(.+?)'\+pwd,`).FindStringSubmatch(html)[1] + pwd
|
||||||
|
var resp LanzouLinkResp
|
||||||
|
_, err = base.RestyClient.R().SetResult(&resp).SetHeaders(map[string]string{
|
||||||
|
"Referer": fmt.Sprintf("https://%s/%s", u.Host, downId),
|
||||||
|
"Origin": "https://" + u.Host,
|
||||||
|
"content-type": "application/x-www-form-urlencoded",
|
||||||
|
}).SetBody(data).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if resp.Zt == 1 {
|
||||||
|
return resp.Dom + "/file/" + resp.Url, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("failed get link with password")
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
13
drivers/lanzou/types.go
Normal file
13
drivers/lanzou/types.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package lanzou
|
||||||
|
|
||||||
|
type DownPageResp struct {
|
||||||
|
Zt int `json:"zt"`
|
||||||
|
Info struct {
|
||||||
|
Pwd string `json:"pwd"`
|
||||||
|
Onof string `json:"onof"`
|
||||||
|
FId string `json:"f_id"`
|
||||||
|
Taoc string `json:"taoc"`
|
||||||
|
IsNewd string `json:"is_newd"`
|
||||||
|
} `json:"info"`
|
||||||
|
Text interface{} `json:"text"`
|
||||||
|
}
|
@ -158,10 +158,6 @@ func (driver MediaTrack) Preview(path string, account *model.Account) (interface
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (driver MediaTrack) MakeDir(path string, account *model.Account) error {
|
func (driver MediaTrack) MakeDir(path string, account *model.Account) error {
|
||||||
_, err := driver.File(path, account)
|
|
||||||
if err != base.ErrPathNotFound {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
parentFile, err := driver.File(utils.Dir(path), account)
|
parentFile, err := driver.File(utils.Dir(path), account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -29,7 +29,11 @@ func File(driver base.Driver, account *model.Account, path string) (*model.File,
|
|||||||
|
|
||||||
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
|
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
|
||||||
log.Debugf("mkdir: %s", path)
|
log.Debugf("mkdir: %s", path)
|
||||||
err := driver.MakeDir(path, account)
|
_, err := Files(driver, account, path)
|
||||||
|
if err != base.ErrPathNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = driver.MakeDir(path, account)
|
||||||
if err == nil && clearCache {
|
if err == nil && clearCache {
|
||||||
_ = base.DeleteCache(utils.Dir(path), account)
|
_ = base.DeleteCache(utils.Dir(path), account)
|
||||||
}
|
}
|
||||||
|
@ -119,9 +119,14 @@ func (driver PikPak) Link(args base.Args, account *model.Account) (*base.Link, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &base.Link{
|
link := base.Link{
|
||||||
Url: resp.WebContentLink,
|
Url: resp.WebContentLink,
|
||||||
}, nil
|
}
|
||||||
|
if len(resp.Medias) > 0 && resp.Medias[0].Link.Url != "" {
|
||||||
|
log.Debugln("use media link")
|
||||||
|
link.Url = resp.Medias[0].Link.Url
|
||||||
|
}
|
||||||
|
return &link, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver PikPak) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
func (driver PikPak) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||||
|
@ -144,6 +144,37 @@ type File struct {
|
|||||||
Size string `json:"size"`
|
Size string `json:"size"`
|
||||||
ThumbnailLink string `json:"thumbnail_link"`
|
ThumbnailLink string `json:"thumbnail_link"`
|
||||||
WebContentLink string `json:"web_content_link"`
|
WebContentLink string `json:"web_content_link"`
|
||||||
|
Medias []Media `json:"medias"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Media struct {
|
||||||
|
MediaId string `json:"media_id"`
|
||||||
|
MediaName string `json:"media_name"`
|
||||||
|
Video struct {
|
||||||
|
Height int `json:"height"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
BitRate int `json:"bit_rate"`
|
||||||
|
FrameRate int `json:"frame_rate"`
|
||||||
|
VideoCodec string `json:"video_codec"`
|
||||||
|
AudioCodec string `json:"audio_codec"`
|
||||||
|
VideoType string `json:"video_type"`
|
||||||
|
} `json:"video"`
|
||||||
|
Link struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
Expire time.Time `json:"expire"`
|
||||||
|
} `json:"link"`
|
||||||
|
NeedMoreQuota bool `json:"need_more_quota"`
|
||||||
|
VipTypes []interface{} `json:"vip_types"`
|
||||||
|
RedirectLink string `json:"redirect_link"`
|
||||||
|
IconLink string `json:"icon_link"`
|
||||||
|
IsDefault bool `json:"is_default"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
IsOrigin bool `json:"is_origin"`
|
||||||
|
ResolutionName string `json:"resolution_name"`
|
||||||
|
IsVisible bool `json:"is_visible"`
|
||||||
|
Category string `json:"category"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver PikPak) FormatFile(file *File) *model.File {
|
func (driver PikPak) FormatFile(file *File) *model.File {
|
||||||
|
149
drivers/template/driver.go
Normal file
149
drivers/template/driver.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Template struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Config() base.DriverConfig {
|
||||||
|
return base.DriverConfig{
|
||||||
|
Name: "Template",
|
||||||
|
OnlyProxy: false,
|
||||||
|
OnlyLocal: false,
|
||||||
|
ApiProxy: false,
|
||||||
|
NoNeedSetLink: false,
|
||||||
|
NoCors: false,
|
||||||
|
LocalSort: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Items() []base.Item {
|
||||||
|
// TODO fill need info
|
||||||
|
return []base.Item{
|
||||||
|
{
|
||||||
|
Name: "refresh_token",
|
||||||
|
Label: "refresh token",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "root_folder",
|
||||||
|
Label: "root folder path",
|
||||||
|
Type: base.TypeString,
|
||||||
|
Default: "/",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Save(account *model.Account, old *model.Account) error {
|
||||||
|
// TODO test available or init
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) File(path string, account *model.Account) (*model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
if path == "/" {
|
||||||
|
return &model.File{
|
||||||
|
Id: account.RootFolder,
|
||||||
|
Name: account.Name,
|
||||||
|
Size: 0,
|
||||||
|
Type: conf.FOLDER,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: account.UpdatedAt,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
dir, name := filepath.Split(path)
|
||||||
|
files, err := driver.Files(dir, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if file.Name == name {
|
||||||
|
return &file, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, base.ErrPathNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Files(path string, account *model.Account) ([]model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
cache, err := base.GetCache(path, account)
|
||||||
|
if err == nil {
|
||||||
|
files, _ := cache.([]model.File)
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
var files []model.File
|
||||||
|
// TODO get files
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(files) > 0 {
|
||||||
|
_ = base.SetCache(path, files, account)
|
||||||
|
}
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Link(args base.Args, account *model.Account) (*base.Link, error) {
|
||||||
|
// TODO get file link
|
||||||
|
return nil, base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Path(path string, account *model.Account) (*model.File, []model.File, error) {
|
||||||
|
path = utils.ParsePath(path)
|
||||||
|
file, err := driver.File(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !file.IsDir() {
|
||||||
|
return file, nil, nil
|
||||||
|
}
|
||||||
|
files, err := driver.Files(path, account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return nil, files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Preview(path string, account *model.Account) (interface{}, error) {
|
||||||
|
//TODO preview interface if driver support
|
||||||
|
return nil, base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) MakeDir(path string, account *model.Account) error {
|
||||||
|
//TODO make dir
|
||||||
|
return base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Move(src string, dst string, account *model.Account) error {
|
||||||
|
//TODO move file/dir
|
||||||
|
return base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Rename(src string, dst string, account *model.Account) error {
|
||||||
|
//TODO rename file/dir
|
||||||
|
return base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Copy(src string, dst string, account *model.Account) error {
|
||||||
|
//TODO copy file/dir
|
||||||
|
return base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Delete(path string, account *model.Account) error {
|
||||||
|
//TODO delete file/dir
|
||||||
|
return base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) Upload(file *model.FileStream, account *model.Account) error {
|
||||||
|
//TODO upload file
|
||||||
|
return base.ErrNotImplement
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ base.Driver = (*Template)(nil)
|
90
drivers/template/template.go
Normal file
90
drivers/template/template.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/Xhofe/alist/conf"
|
||||||
|
"github.com/Xhofe/alist/drivers/base"
|
||||||
|
"github.com/Xhofe/alist/model"
|
||||||
|
"github.com/Xhofe/alist/utils"
|
||||||
|
"github.com/go-resty/resty/v2"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoginOrRefreshToken(account *model.Account) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Request(u string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
|
||||||
|
req := base.RestyClient.R()
|
||||||
|
req.SetHeaders(map[string]string{
|
||||||
|
"Authorization": "Bearer" + account.AccessToken,
|
||||||
|
"Accept": "application/json, text/plain, */*",
|
||||||
|
})
|
||||||
|
if headers != nil {
|
||||||
|
req.SetHeaders(headers)
|
||||||
|
}
|
||||||
|
if query != nil {
|
||||||
|
req.SetQueryParams(query)
|
||||||
|
}
|
||||||
|
if form != nil {
|
||||||
|
req.SetFormData(form)
|
||||||
|
}
|
||||||
|
if data != nil {
|
||||||
|
req.SetBody(data)
|
||||||
|
}
|
||||||
|
if resp != nil {
|
||||||
|
req.SetResult(resp)
|
||||||
|
}
|
||||||
|
var e Resp
|
||||||
|
var err error
|
||||||
|
var res *resty.Response
|
||||||
|
req.SetError(&e)
|
||||||
|
switch method {
|
||||||
|
case base.Get:
|
||||||
|
res, err = req.Get(u)
|
||||||
|
case base.Post:
|
||||||
|
res, err = req.Post(u)
|
||||||
|
case base.Delete:
|
||||||
|
res, err = req.Delete(u)
|
||||||
|
case base.Patch:
|
||||||
|
res, err = req.Patch(u)
|
||||||
|
case base.Put:
|
||||||
|
res, err = req.Put(u)
|
||||||
|
default:
|
||||||
|
return nil, base.ErrNotSupport
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if e.Code >= 400 {
|
||||||
|
if e.Code == 401 {
|
||||||
|
err = LoginOrRefreshToken(account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return Request(u, method, headers, query, form, data, resp, account)
|
||||||
|
}
|
||||||
|
return nil, errors.New(e.Message)
|
||||||
|
}
|
||||||
|
return res.Body(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (driver Template) formatFile(f *File) *model.File {
|
||||||
|
file := model.File{
|
||||||
|
Id: f.Id,
|
||||||
|
Name: f.FileName,
|
||||||
|
Size: f.Size,
|
||||||
|
Driver: driver.Config().Name,
|
||||||
|
UpdatedAt: f.UpdatedAt,
|
||||||
|
}
|
||||||
|
if f.File {
|
||||||
|
file.Type = utils.GetFileType(path.Ext(f.FileName))
|
||||||
|
} else {
|
||||||
|
file.Type = conf.FOLDER
|
||||||
|
}
|
||||||
|
return &file
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
base.RegisterDriver(&Template{})
|
||||||
|
}
|
18
drivers/template/types.go
Normal file
18
drivers/template/types.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// write all struct here
|
||||||
|
|
||||||
|
type Resp struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
FileName string `json:"file_name"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
File bool `json:"file"`
|
||||||
|
UpdatedAt *time.Time `json:"updated_at"`
|
||||||
|
}
|
3
drivers/template/util.go
Normal file
3
drivers/template/util.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
// write util func here, such as cal sign
|
@ -60,7 +60,13 @@ func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error
|
|||||||
if account == nil {
|
if account == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return GetState(account).Login(account)
|
state := GetState(account)
|
||||||
|
if state.isTokensExpires() {
|
||||||
|
return state.Login(account)
|
||||||
|
}
|
||||||
|
account.Status = "work"
|
||||||
|
model.SaveAccount(account)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (driver XunLeiCloud) File(path string, account *model.Account) (*model.File, error) {
|
func (driver XunLeiCloud) File(path string, account *model.Account) (*model.File, error) {
|
||||||
@ -99,16 +105,20 @@ func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.Fi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileList FileList
|
files := make([]model.File, 0)
|
||||||
u := fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files?parent_id=%s&page_token=%s&with_audit=true&filters=%s", file.Id, "", url.QueryEscape(`{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`))
|
for {
|
||||||
if err = GetState(account).Request("GET", u, nil, &fileList, account); err != nil {
|
var fileList FileList
|
||||||
return nil, err
|
u := fmt.Sprintf("https://api-pan.xunlei.com/drive/v1/files?parent_id=%s&page_token=%s&with_audit=true&filters=%s", file.Id, fileList.NextPageToken, url.QueryEscape(`{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`))
|
||||||
}
|
if err = GetState(account).Request("GET", u, nil, &fileList, account); err != nil {
|
||||||
|
return nil, err
|
||||||
files := make([]model.File, 0, len(fileList.Files))
|
}
|
||||||
for _, file := range fileList.Files {
|
for _, file := range fileList.Files {
|
||||||
if file.Kind == FOLDER || (file.Kind == FILE && file.Audit.Status == "STATUS_OK") {
|
if file.Kind == FOLDER || (file.Kind == FILE && file.Audit.Status == "STATUS_OK") {
|
||||||
files = append(files, *driver.formatFile(&file))
|
files = append(files, *driver.formatFile(&file))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if fileList.NextPageToken == "" {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(files) > 0 {
|
if len(files) > 0 {
|
||||||
|
3
go.mod
3
go.mod
@ -4,6 +4,7 @@ go 1.17
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go v1.27.0
|
github.com/aws/aws-sdk-go v1.27.0
|
||||||
|
github.com/caarlos0/env/v6 v6.9.1
|
||||||
github.com/eko/gocache/v2 v2.1.0
|
github.com/eko/gocache/v2 v2.1.0
|
||||||
github.com/gin-contrib/cors v1.3.1
|
github.com/gin-contrib/cors v1.3.1
|
||||||
github.com/gin-gonic/gin v1.7.4
|
github.com/gin-gonic/gin v1.7.4
|
||||||
@ -25,6 +26,8 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
|
||||||
|
github.com/fatih/color v1.13.0
|
||||||
|
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
6
go.sum
6
go.sum
@ -43,6 +43,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
|||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||||
|
github.com/caarlos0/env/v6 v6.9.1 h1:zOkkjM0F6ltnQ5eBX6IPI41UP/KDGEK7rRPwGCNos8k=
|
||||||
|
github.com/caarlos0/env/v6 v6.9.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
|
||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
@ -94,6 +96,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
|
|||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
|
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||||
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||||
@ -329,6 +333,8 @@ github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN
|
|||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||||
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
@ -6,11 +6,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
ID uint `json:"id" gorm:"primaryKey"`
|
ID uint `json:"id" gorm:"primaryKey"`
|
||||||
Path string `json:"path" gorm:"unique" binding:"required"`
|
Path string `json:"path" gorm:"unique" binding:"required"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Hide string `json:"hide"`
|
Hide string `json:"hide"`
|
||||||
Upload bool `json:"upload"`
|
Upload bool `json:"upload"`
|
||||||
|
OnlyShows string `json:"only_shows"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetMetaByPath(path string) (*Meta, error) {
|
func GetMetaByPath(path string) (*Meta, error) {
|
||||||
|
@ -88,8 +88,10 @@ func SuccessResp(c *gin.Context, data ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Hide(meta *model.Meta, files []model.File) []model.File {
|
func Hide(meta *model.Meta, files []model.File) []model.File {
|
||||||
//meta, _ := model.GetMetaByPath(path)
|
if meta == nil {
|
||||||
if meta != nil && meta.Hide != "" {
|
return files
|
||||||
|
}
|
||||||
|
if meta.Hide != "" {
|
||||||
tmpFiles := make([]model.File, 0)
|
tmpFiles := make([]model.File, 0)
|
||||||
hideFiles := strings.Split(meta.Hide, ",")
|
hideFiles := strings.Split(meta.Hide, ",")
|
||||||
for _, item := range files {
|
for _, item := range files {
|
||||||
@ -99,5 +101,15 @@ func Hide(meta *model.Meta, files []model.File) []model.File {
|
|||||||
}
|
}
|
||||||
files = tmpFiles
|
files = tmpFiles
|
||||||
}
|
}
|
||||||
|
if meta.OnlyShows != "" {
|
||||||
|
tmpFiles := make([]model.File, 0)
|
||||||
|
showFiles := strings.Split(meta.OnlyShows, ",")
|
||||||
|
for _, item := range files {
|
||||||
|
if utils.IsContain(showFiles, item.Name) {
|
||||||
|
tmpFiles = append(tmpFiles, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files = tmpFiles
|
||||||
|
}
|
||||||
return files
|
return files
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,14 @@ func Plist(c *gin.Context) {
|
|||||||
name := utils.Base(u)
|
name := utils.Base(u)
|
||||||
u = uUrl.String()
|
u = uUrl.String()
|
||||||
ipaIndex := strings.Index(name, ".ipa")
|
ipaIndex := strings.Index(name, ".ipa")
|
||||||
|
decodeName := name
|
||||||
if ipaIndex != -1 {
|
if ipaIndex != -1 {
|
||||||
name = name[:ipaIndex]
|
name = name[:ipaIndex]
|
||||||
|
decodeName = name
|
||||||
|
tmp, err := url.PathUnescape(name)
|
||||||
|
if err == nil {
|
||||||
|
decodeName = tmp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
name = strings.ReplaceAll(name, "<", "[")
|
name = strings.ReplaceAll(name, "<", "[")
|
||||||
name = strings.ReplaceAll(name, ">", "]")
|
name = strings.ReplaceAll(name, ">", "]")
|
||||||
@ -67,7 +73,7 @@ func Plist(c *gin.Context) {
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>`, u, name, name)
|
</plist>`, u, name, decodeName)
|
||||||
c.Header("Content-Type", "application/xml;charset=utf-8")
|
c.Header("Content-Type", "application/xml;charset=utf-8")
|
||||||
c.Status(200)
|
c.Status(200)
|
||||||
_, _ = c.Writer.WriteString(plist)
|
_, _ = c.Writer.WriteString(plist)
|
||||||
|
@ -2,10 +2,17 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetSHA1Encode(data string) string {
|
||||||
|
h := sha1.New()
|
||||||
|
h.Write([]byte(data))
|
||||||
|
return hex.EncodeToString(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
// GetMD5Encode
|
// GetMD5Encode
|
||||||
func GetMD5Encode(data string) string {
|
func GetMD5Encode(data string) string {
|
||||||
h := md5.New()
|
h := md5.New()
|
||||||
|
Reference in New Issue
Block a user