├── .air.toml ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── config.yml ├── stale.yml └── workflows │ ├── auto_lang.yml │ ├── build-freebsd.yml │ ├── build.yml │ ├── build_docker.yml │ ├── changelog.yml │ ├── issue_close_question.yml │ ├── issue_close_stale.yml │ ├── issue_duplicate.yml │ ├── issue_invalid.yml │ ├── issue_on_close.yml │ ├── issue_question.yml │ ├── issue_similarity.yml │ ├── issue_translate.yml │ ├── issue_wontfix.yml │ ├── releas_all.yml │ ├── release-freebsd.yml │ ├── release.yml │ ├── release_android.yml │ ├── release_docker.yml │ ├── release_linux_musl.yml │ └── release_linux_musl_arm.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.ci ├── Dockerfile.ffmpeg ├── LICENSE ├── README.md ├── README_cn.md ├── README_ja.md ├── alwaysdata.sh ├── build-freebsd.sh ├── build.sh ├── cmd ├── admin.go ├── cancel2FA.go ├── common.go ├── flags │ └── config.go ├── lang.go ├── restart.go ├── root.go ├── server.go ├── start.go ├── stop.go ├── storage.go ├── user.go └── version.go ├── docker-compose.yml ├── drivers ├── 115 │ ├── appver.go │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── 123 │ ├── driver.go │ ├── meta.go │ ├── types.go │ ├── upload.go │ └── util.go ├── 139 │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── 189 │ ├── driver.go │ ├── help.go │ ├── login.go │ ├── meta.go │ ├── types.go │ └── util.go ├── 115_share │ ├── driver.go │ ├── meta.go │ └── utils.go ├── 123_link │ ├── driver.go │ ├── meta.go │ ├── parse.go │ ├── types.go │ └── util.go ├── 123_share │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── 189pc │ ├── driver.go │ ├── help.go │ ├── meta.go │ ├── types.go │ └── utils.go ├── alias │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── alist_v2 │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── alist_v3 │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── aliyundrive │ ├── driver.go │ ├── global.go │ ├── help.go │ ├── meta.go │ ├── types.go │ └── util.go ├── aliyundrive_open │ ├── driver.go │ ├── meta.go │ ├── types.go │ ├── upload.go │ └── util.go ├── aliyundrive_share │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── all.go ├── baidu_netdisk │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── baidu_photo │ ├── driver.go │ ├── help.go │ ├── meta.go │ ├── types.go │ └── utils.go ├── baidu_share │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── base │ ├── client.go │ ├── types.go │ ├── upload.go │ └── util.go ├── chaoxing │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── chunker │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── cloudreve │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── crypt │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── doubao │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── doubao_share │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── dropbox │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── fastwebdav │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── febbox │ ├── driver.go │ ├── meta.go │ ├── oauth2.go │ ├── types.go │ └── util.go ├── ftp │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── google_drive │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── google_photo │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── halalcloud │ ├── driver.go │ ├── meta.go │ ├── options.go │ ├── types.go │ └── util.go ├── homecloud │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── ilanzou │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── ipfs_api │ ├── driver.go │ └── meta.go ├── kodbox │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── lanzou │ ├── driver.go │ ├── help.go │ ├── meta.go │ ├── types.go │ └── util.go ├── lark.go ├── lark │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── lenovonas_share │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── local │ ├── driver.go │ ├── meta.go │ ├── token_bucket.go │ └── util.go ├── mediatrack │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── mega │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── mopan │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── netease_music │ ├── crypto.go │ ├── driver.go │ ├── meta.go │ ├── types.go │ ├── upload.go │ └── util.go ├── onedrive │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── onedrive_app │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── onedrive_sharelink │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── pikpak │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── pikpak_proxy │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── pikpak_share │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── quark_uc │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── quark_uc_tv │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── quqi │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── s3 │ ├── doge.go │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── seafile │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── sftp │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── smb │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── teambition │ ├── driver.go │ ├── help.go │ ├── meta.go │ ├── types.go │ └── util.go ├── template │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── terabox │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── thunder │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── thunder_browser │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── thunderx │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── trainbit │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── url_tree │ ├── driver.go │ ├── meta.go │ ├── types.go │ ├── urls_test.go │ └── util.go ├── uss │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── virtual │ ├── driver.go │ ├── meta.go │ └── util.go ├── vtencent │ ├── drive.go │ ├── meta.go │ ├── signature.go │ ├── types.go │ └── util.go ├── webdav │ ├── driver.go │ ├── meta.go │ ├── odrvcookie │ │ ├── cookie.go │ │ └── fetch.go │ ├── types.go │ └── util.go ├── weiyun │ ├── driver.go │ ├── meta.go │ └── types.go ├── wopan │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go └── yandex_disk │ ├── driver.go │ ├── meta.go │ ├── types.go │ └── util.go ├── entrypoint.sh ├── go.mod ├── go.sum ├── internal ├── authn │ └── authn.go ├── bootstrap │ ├── config.go │ ├── data │ │ ├── data.go │ │ ├── dev.go │ │ ├── setting.go │ │ ├── task.go │ │ └── user.go │ ├── db.go │ ├── index.go │ ├── log.go │ ├── offline_download.go │ ├── storage.go │ └── task.go ├── conf │ ├── config.go │ ├── const.go │ └── var.go ├── db │ ├── db.go │ ├── meta.go │ ├── searchnode.go │ ├── settingitem.go │ ├── storage.go │ ├── tasks.go │ ├── user.go │ └── util.go ├── driver │ ├── config.go │ ├── driver.go │ ├── item.go │ └── utils.go ├── errs │ ├── driver.go │ ├── errors.go │ ├── errors_test.go │ ├── object.go │ ├── operate.go │ ├── search.go │ └── user.go ├── fs │ ├── copy.go │ ├── fs.go │ ├── get.go │ ├── link.go │ ├── list.go │ ├── other.go │ ├── put.go │ └── walk.go ├── fuse │ ├── fs.go │ └── mount.go ├── message │ ├── http.go │ ├── message.go │ └── ws.go ├── model │ ├── args.go │ ├── file.go │ ├── meta.go │ ├── notify.go │ ├── obj.go │ ├── object.go │ ├── req.go │ ├── search.go │ ├── setting.go │ ├── storage.go │ ├── task.go │ └── user.go ├── net │ ├── request.go │ ├── request_test.go │ ├── serve.go │ └── util.go ├── offline_download │ ├── all.go │ ├── aria2 │ │ ├── aria2.go │ │ └── notify.go │ ├── http │ │ ├── client.go │ │ └── util.go │ ├── qbit │ │ └── qbit.go │ ├── storage │ │ └── storage.go │ └── tool │ │ ├── add.go │ │ ├── all_test.go │ │ ├── base.go │ │ ├── download.go │ │ ├── tools.go │ │ ├── transfer.go │ │ └── util.go ├── op │ ├── const.go │ ├── driver.go │ ├── driver_test.go │ ├── fs.go │ ├── hook.go │ ├── meta.go │ ├── notify.go │ ├── path.go │ ├── setting.go │ ├── storage.go │ ├── storage_test.go │ └── user.go ├── search │ ├── bleve │ │ ├── init.go │ │ └── search.go │ ├── build.go │ ├── db │ │ ├── init.go │ │ └── search.go │ ├── db_non_full_text │ │ ├── init.go │ │ └── search.go │ ├── import.go │ ├── meilisearch │ │ ├── init.go │ │ └── search.go │ ├── search.go │ ├── searcher │ │ ├── manage.go │ │ └── searcher.go │ └── util.go ├── setting │ └── setting.go ├── sign │ └── sign.go └── stream │ ├── limit.go │ ├── stream.go │ └── util.go ├── linux.sh ├── main.go ├── pkg ├── aria2 │ └── rpc │ │ ├── README.md │ │ ├── call.go │ │ ├── call_test.go │ │ ├── client.go │ │ ├── client_test.go │ │ ├── const.go │ │ ├── json2.go │ │ ├── notification.go │ │ ├── proc.go │ │ ├── proto.go │ │ └── resp.go ├── chanio │ └── chanio.go ├── cookie │ └── cookie.go ├── cron │ ├── cron.go │ └── cron_test.go ├── errgroup │ └── errgroup.go ├── generic │ └── queue.go ├── generic_sync │ ├── map.go │ └── map_test.go ├── gowebdav │ ├── .gitignore │ ├── .travis.yml │ ├── LICENSE │ ├── Makefile │ ├── README.md │ ├── basicAuth.go │ ├── client.go │ ├── cmd │ │ └── gowebdav │ │ │ ├── README.md │ │ │ └── main.go │ ├── digestAuth.go │ ├── doc.go │ ├── errors.go │ ├── file.go │ ├── netrc.go │ ├── requests.go │ ├── utils.go │ └── utils_test.go ├── http_range │ └── range.go ├── mq │ └── mq.go ├── qbittorrent │ └── client.go ├── sign │ ├── hmac.go │ └── sign.go ├── singleflight │ ├── signleflight_test.go │ └── singleflight.go ├── tache │ ├── .gitattributes │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── able.go │ ├── base.go │ ├── errors.go │ ├── examples │ │ └── base │ │ │ └── main.go │ ├── hook.go │ ├── manager.go │ ├── manager_test.go │ ├── option.go │ ├── state.go │ ├── task.go │ ├── utils.go │ └── worker.go ├── task │ ├── errors.go │ ├── manager.go │ ├── task.go │ └── task_test.go └── utils │ ├── balance.go │ ├── bool.go │ ├── ctx.go │ ├── email.go │ ├── file.go │ ├── hash.go │ ├── hash │ └── gcid.go │ ├── hash_test.go │ ├── io.go │ ├── ip.go │ ├── json.go │ ├── log.go │ ├── map.go │ ├── oauth2.go │ ├── path.go │ ├── path_test.go │ ├── random │ └── random.go │ ├── slice.go │ ├── str.go │ ├── time.go │ └── url.go ├── public ├── dist │ └── README.md └── public.go ├── renovate.json ├── serv00.sh ├── server ├── common │ ├── auth.go │ ├── base.go │ ├── check.go │ ├── check_test.go │ ├── common.go │ ├── hide_privacy_test.go │ ├── proxy.go │ ├── resp.go │ └── sign.go ├── debug.go ├── handles │ ├── auth.go │ ├── down.go │ ├── driver.go │ ├── fsbatch.go │ ├── fsmanage.go │ ├── fsread.go │ ├── fsup.go │ ├── helper.go │ ├── index.go │ ├── ldap_login.go │ ├── meta.go │ ├── offline_download.go │ ├── search.go │ ├── setting.go │ ├── ssologin.go │ ├── storage.go │ ├── task.go │ ├── user.go │ └── webauthn.go ├── middlewares │ ├── auth.go │ ├── check.go │ ├── down.go │ ├── fsup.go │ ├── https.go │ ├── limit.go │ └── search.go ├── router.go ├── s3.go ├── s3 │ ├── backend.go │ ├── ioutils.go │ ├── list.go │ ├── logger.go │ ├── pager.go │ ├── server.go │ └── utils.go ├── static │ ├── config.go │ └── static.go ├── webdav.go └── webdav │ ├── buffered_response_writer.go │ ├── file.go │ ├── if.go │ ├── internal │ └── xml │ │ ├── README │ │ ├── atom_test.go │ │ ├── example_test.go │ │ ├── marshal.go │ │ ├── marshal_test.go │ │ ├── read.go │ │ ├── read_test.go │ │ ├── typeinfo.go │ │ ├── xml.go │ │ └── xml_test.go │ ├── litmus_test_server.go │ ├── lock.go │ ├── lock_test.go │ ├── prop.go │ ├── util.go │ ├── webdav.go │ ├── xml.go │ └── xml_test.go ├── termux.sh └── wrapper ├── zcc-arm64 └── zcxx-arm64 /.air.toml: -------------------------------------------------------------------------------- 1 | root = "." 2 | testdata_dir = "testdata" 3 | tmp_dir = "tmp" 4 | 5 | [build] 6 | args_bin = ["server"] 7 | bin = "./tmp/main" 8 | cmd = "go build -o ./tmp/main ." 9 | delay = 0 10 | exclude_dir = ["assets", "tmp", "vendor", "testdata"] 11 | exclude_file = [] 12 | exclude_regex = ["_test.go"] 13 | exclude_unchanged = false 14 | follow_symlink = false 15 | full_bin = "" 16 | include_dir = [] 17 | include_ext = ["go", "tpl", "tmpl", "html"] 18 | include_file = [] 19 | kill_delay = "0s" 20 | log = "build-errors.log" 21 | poll = false 22 | poll_interval = 0 23 | rerun = false 24 | rerun_delay = 500 25 | send_interrupt = false 26 | stop_on_error = false 27 | 28 | [color] 29 | app = "" 30 | build = "yellow" 31 | main = "magenta" 32 | runner = "green" 33 | watcher = "cyan" 34 | 35 | [log] 36 | main_only = false 37 | time = false 38 | 39 | [misc] 40 | clean_on_exit = false 41 | 42 | [screen] 43 | clear_on_rebuild = false 44 | keep_scroll = true 45 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .github -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: xhofe # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://alist.nn.ci/guide/sponsor.html'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Questions & Discussions 4 | url: https://github.com/Xhofe/alist/discussions 5 | about: Use GitHub discussions for message-board style questions and discussions. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature request" 2 | description: Feature request 3 | labels: [enhancement] 4 | body: 5 | - type: checkboxes 6 | attributes: 7 | label: Please make sure of the following things 8 | description: You may select more than one, even select all. 9 | options: 10 | - label: I have read the [documentation](https://alist.nn.ci). 11 | - label: I'm sure there are no duplicate issues or discussions. 12 | - label: I'm sure this feature is not implemented. 13 | - label: I'm sure it's a reasonable and popular requirement. 14 | - type: textarea 15 | id: feature-description 16 | attributes: 17 | label: Description of the feature / 需求描述 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: suggested-solution 22 | attributes: 23 | label: Suggested solution / 实现思路 24 | description: | 25 | Solutions to achieve this requirement. 26 | 实现此需求的解决思路。 27 | - type: textarea 28 | id: additional-context 29 | attributes: 30 | label: Additional context / 附件 31 | description: | 32 | Any other context or screenshots about the feature request here, or information you find helpful. 33 | 相关的任何其他上下文或截图,或者你觉得有帮助的信息 -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for welcome - https://github.com/behaviorbot/welcome 2 | 3 | # Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome 4 | 5 | # Comment to be posted to on first time issues 6 | newIssueWelcomeComment: > 7 | Thanks for opening your first issue here! Be sure to follow the issue template! 8 | 9 | # Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome 10 | 11 | # Comment to be posted to on PRs from first time contributors in your repository 12 | newPRWelcomeComment: > 13 | Thanks for opening this pull request! Please check out our contributing guidelines. 14 | 15 | # Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge 16 | 17 | # Comment to be posted to on pull requests merged by a first time user 18 | firstPRMergeComment: > 19 | Congrats on merging your first pull request! We here at behavior bot are proud of you! 20 | 21 | # It is recommend to include as many gifs and emojis as possible -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 44 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 20 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - accepted 8 | - security 9 | - working 10 | - pr-welcome 11 | # Label to use when marking an issue as stale 12 | staleLabel: stale 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. Thank you 17 | for your contributions. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: > 20 | This issue was closed due to inactive more than 52 days. You can reopen or 21 | recreate it if you think it should continue. Thank you for your contributions again. 22 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | repository_dispatch: 5 | workflow_dispatch: 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | platform: [ubuntu-latest] 16 | go-version: [ '1.21' ] 17 | name: Build 18 | runs-on: ${{ matrix.platform }} 19 | steps: 20 | - name: Setup Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | 28 | - uses: benjlevesque/short-sha@v2.2 29 | id: short-sha 30 | 31 | - name: Install dependencies 32 | run: | 33 | sudo snap install zig --classic --beta 34 | docker pull crazymax/xgo:latest 35 | go install github.com/crazy-max/xgo@latest 36 | sudo apt install upx 37 | 38 | - name: Build 39 | run: | 40 | bash build.sh dev 41 | 42 | - name: Upload artifact 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: alist_${{ env.SHA }} 46 | path: dist -------------------------------------------------------------------------------- /.github/workflows/changelog.yml: -------------------------------------------------------------------------------- 1 | name: auto changelog 2 | 3 | on: 4 | repository_dispatch: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | changelog: 9 | name: Create Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout code 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - run: npx changelogithub # or changelogithub@0.12 if ensure the stable result 17 | env: 18 | GITHUB_TOKEN: ${{secrets.MY_TOKEN}} 19 | -------------------------------------------------------------------------------- /.github/workflows/issue_close_question.yml: -------------------------------------------------------------------------------- 1 | name: Close need info 2 | 3 | # on: 4 | # schedule: 5 | # - cron: "0 0 */1 * *" 6 | # workflow_dispatch: 7 | 8 | on: 9 | repository_dispatch: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | close-need-info: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: close-issues 17 | uses: actions-cool/issues-helper@v3 18 | with: 19 | actions: 'close-issues' 20 | token: ${{ secrets.GITHUB_TOKEN }} 21 | labels: 'question' 22 | inactive-day: 3 23 | close-reason: 'not_planned' 24 | body: | 25 | Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 3 days. 26 | 你好 @${{ github.event.issue.user.login }},此issue因超过3天未回复被关闭。 -------------------------------------------------------------------------------- /.github/workflows/issue_close_stale.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive 2 | 3 | on: 4 | repository_dispatch: 5 | workflow_dispatch: 6 | 7 | jobs: 8 | close-inactive: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: close-issues 12 | uses: actions-cool/issues-helper@v3 13 | with: 14 | actions: 'close-issues' 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | labels: 'stale' 17 | inactive-day: 8 18 | close-reason: 'not_planned' 19 | body: | 20 | Hello @${{ github.event.issue.user.login }}, this issue was closed due to inactive more than 52 days. You can reopen or recreate it if you think it should continue. Thank you for your contributions again. -------------------------------------------------------------------------------- /.github/workflows/issue_duplicate.yml: -------------------------------------------------------------------------------- 1 | name: Issue Duplicate 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | create-comment: 9 | runs-on: ubuntu-latest 10 | if: github.event.label.name == 'duplicate' 11 | steps: 12 | - name: Create comment 13 | uses: actions-cool/issues-helper@v3 14 | with: 15 | actions: 'create-comment' 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | issue-number: ${{ github.event.issue.number }} 18 | body: | 19 | Hello @${{ github.event.issue.user.login }}, your issue is a duplicate and will be closed. 20 | 你好 @${{ github.event.issue.user.login }},你的issue是重复的,将被关闭。 21 | - name: Close issue 22 | uses: actions-cool/issues-helper@v3 23 | with: 24 | actions: 'close-issue' 25 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/issue_invalid.yml: -------------------------------------------------------------------------------- 1 | name: Issue Invalid 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | create-comment: 9 | runs-on: ubuntu-latest 10 | if: github.event.label.name == 'invalid' 11 | steps: 12 | - name: Create comment 13 | uses: actions-cool/issues-helper@v3 14 | with: 15 | actions: 'create-comment' 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | issue-number: ${{ github.event.issue.number }} 18 | body: | 19 | Hello @${{ github.event.issue.user.login }}, your issue is invalid and will be closed. 20 | 你好 @${{ github.event.issue.user.login }},你的issue无效,将被关闭。 21 | - name: Close issue 22 | uses: actions-cool/issues-helper@v3 23 | with: 24 | actions: 'close-issue' 25 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/issue_on_close.yml: -------------------------------------------------------------------------------- 1 | name: Remove working label when issue closed 2 | 3 | on: 4 | issues: 5 | types: [closed] 6 | 7 | jobs: 8 | rm-working: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Remove working label 12 | uses: actions-cool/issues-helper@v3 13 | with: 14 | actions: 'remove-labels' 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | issue-number: ${{ github.event.issue.number }} 17 | labels: 'working,pr-welcome' 18 | -------------------------------------------------------------------------------- /.github/workflows/issue_question.yml: -------------------------------------------------------------------------------- 1 | name: Issue Question 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | create-comment: 9 | runs-on: ubuntu-latest 10 | if: github.event.label.name == 'question' 11 | steps: 12 | - name: Create comment 13 | uses: actions-cool/issues-helper@v3.5.2 14 | with: 15 | actions: 'create-comment' 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | issue-number: ${{ github.event.issue.number }} 18 | body: | 19 | Hello @${{ github.event.issue.user.login }}, please input issue by template and add detail. Issues labeled by `question` will be closed if no activities in 3 days. 20 | 你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/日志记录/复现步骤/复现链接/实现思路或提供更多信息等, 3天内未回复issue自动关闭。 -------------------------------------------------------------------------------- /.github/workflows/issue_similarity.yml: -------------------------------------------------------------------------------- 1 | name: Issues Similarity Analysis 2 | 3 | on: 4 | issues: 5 | types: [opened, edited] 6 | 7 | jobs: 8 | similarity-analysis: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: analysis 12 | uses: actions-cool/issues-similarity-analysis@v1 13 | with: 14 | filter-threshold: 0.5 15 | comment-title: '### See' 16 | comment-body: '${index}. ${similarity} #${number}' 17 | show-footer: false 18 | show-mentioned: true 19 | since-days: 730 -------------------------------------------------------------------------------- /.github/workflows/issue_translate.yml: -------------------------------------------------------------------------------- 1 | name: Translation Helper 2 | 3 | on: 4 | pull_request_target: 5 | types: [opened] 6 | issues: 7 | types: [opened] 8 | 9 | jobs: 10 | translate: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions-cool/translation-helper@v1.2.0 -------------------------------------------------------------------------------- /.github/workflows/issue_wontfix.yml: -------------------------------------------------------------------------------- 1 | name: Issue Wontfix 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | lock-issue: 9 | runs-on: ubuntu-latest 10 | if: github.event.label.name == 'wontfix' 11 | steps: 12 | - name: Create comment 13 | uses: actions-cool/issues-helper@v3 14 | with: 15 | actions: 'create-comment' 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | issue-number: ${{ github.event.issue.number }} 18 | body: | 19 | Hello @${{ github.event.issue.user.login }}, this issue will not be worked on and will be closed. 20 | 你好 @${{ github.event.issue.user.login }},这不会被处理,将被关闭。 21 | - name: Close issue 22 | uses: actions-cool/issues-helper@v3 23 | with: 24 | actions: 'close-issue' 25 | token: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release_android.yml: -------------------------------------------------------------------------------- 1 | name: release_android 2 | 3 | # on: 4 | # release: 5 | # types: [ published ] 6 | on: 7 | repository_dispatch: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | release_android: 12 | strategy: 13 | matrix: 14 | platform: [ ubuntu-latest ] 15 | go-version: [ '1.21' ] 16 | name: Release 17 | runs-on: ${{ matrix.platform }} 18 | steps: 19 | 20 | - name: Setup Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Build 31 | run: | 32 | bash build.sh release android 33 | 34 | - name: Upload assets 35 | uses: softprops/action-gh-release@v1 36 | with: 37 | files: build/compress/* 38 | -------------------------------------------------------------------------------- /.github/workflows/release_linux_musl.yml: -------------------------------------------------------------------------------- 1 | name: release_linux_musl 2 | 3 | # on: 4 | # release: 5 | # types: [ published ] 6 | 7 | on: 8 | repository_dispatch: 9 | workflow_dispatch: 10 | inputs: 11 | tag: 12 | description: 'Tag to release' 13 | required: true 14 | default: 'latest' 15 | 16 | jobs: 17 | release_linux_musl: 18 | strategy: 19 | matrix: 20 | platform: [ ubuntu-latest ] 21 | go-version: [ '1.21' ] 22 | name: Release 23 | runs-on: ${{ matrix.platform }} 24 | steps: 25 | 26 | - name: Setup Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: ${{ matrix.go-version }} 30 | 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 35 | 36 | - name: Build 37 | run: | 38 | bash build.sh release linux_musl 39 | 40 | - name: Upload binary to GitHub Release 41 | uses: svenstaro/upload-release-action@v2 42 | with: 43 | repo_token: ${{ secrets.MY_TOKEN }} 44 | file: build/compress/* 45 | file_glob: true 46 | overwrite: true 47 | tag: "refs/tags/${{ github.event.inputs.tag }}" 48 | -------------------------------------------------------------------------------- /.github/workflows/release_linux_musl_arm.yml: -------------------------------------------------------------------------------- 1 | name: release_linux_musl_arm 2 | 3 | # on: 4 | # release: 5 | # types: [ published ] 6 | 7 | on: 8 | repository_dispatch: 9 | workflow_dispatch: 10 | inputs: 11 | tag: 12 | description: 'Tag to release' 13 | required: true 14 | default: 'latest' 15 | 16 | jobs: 17 | release_linux_musl_arm: 18 | strategy: 19 | matrix: 20 | platform: [ ubuntu-latest ] 21 | go-version: [ '1.21' ] 22 | name: Release 23 | runs-on: ${{ matrix.platform }} 24 | steps: 25 | 26 | - name: Setup Go 27 | uses: actions/setup-go@v5 28 | with: 29 | go-version: ${{ matrix.go-version }} 30 | 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | with: 34 | fetch-depth: 0 35 | 36 | - name: Build 37 | run: | 38 | bash build.sh release linux_musl_arm 39 | 40 | - name: Upload binary to GitHub Release 41 | uses: svenstaro/upload-release-action@v2 42 | with: 43 | repo_token: ${{ secrets.MY_TOKEN }} 44 | file: build/compress/* 45 | file_glob: true 46 | overwrite: true 47 | tag: "refs/tags/${{ github.event.inputs.tag }}" 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | output/ 4 | /dist/ 5 | 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | *.db 13 | *.bin 14 | 15 | # Test binary, built with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | # Dependency directories (remove the comment below to include it) 22 | # vendor/ 23 | /bin/* 24 | *.json 25 | /build 26 | /data/ 27 | /tmp/ 28 | /log/ 29 | /lang/ 30 | /daemon/ 31 | /public/dist/* 32 | !/public/dist/README.md 33 | 34 | .VSCodeCounter -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:edge as builder 2 | LABEL stage=go-builder 3 | WORKDIR /app/ 4 | RUN apk add --no-cache bash curl gcc git go musl-dev 5 | COPY go.mod go.sum ./ 6 | RUN go mod download 7 | COPY ./ ./ 8 | RUN bash build.sh release docker 9 | 10 | FROM alpine:edge 11 | LABEL MAINTAINER="i@nn.ci" 12 | VOLUME /opt/alist/data/ 13 | WORKDIR /opt/alist/ 14 | COPY --from=builder /app/bin/alist ./ 15 | COPY entrypoint.sh /entrypoint.sh 16 | RUN apk update && \ 17 | apk upgrade --no-cache && \ 18 | apk add --no-cache bash ca-certificates su-exec tzdata; \ 19 | chmod +x /entrypoint.sh && \ 20 | rm -rf /var/cache/apk/* 21 | ENV PUID=0 PGID=0 UMASK=022 22 | EXPOSE 5244 5245 23 | CMD [ "/entrypoint.sh" ] -------------------------------------------------------------------------------- /Dockerfile.ffmpeg: -------------------------------------------------------------------------------- 1 | FROM ykxvk8yl5l/alist:latest 2 | RUN apk update && \ 3 | apk add --no-cache ffmpeg \ 4 | rm -rf /var/cache/apk/* -------------------------------------------------------------------------------- /alwaysdata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Declare site in YAML, as documented on the documentation: https://help.alwaysdata.com/en/marketplace/build-application-script/ 4 | # site: 5 | # type: user_program 6 | # working_directory: '{INSTALL_PATH_RELATIVE}' 7 | # command: './alist server' 8 | # requirements: 9 | # disk: 30 10 | # form: 11 | # password: 12 | # type: password 13 | # label: 14 | # en: Password 15 | # fr: Mot de passe 16 | # max_length: 255 17 | 18 | set -e 19 | cd $INSTALL_PATH 20 | wget -O- --no-hsts https://github.com/ykxVK8yL5L/alist/releases/download/latest/alist-linux-amd64.tar.gz | tar -xz --strip-components=0 21 | 22 | ./alist admin set $FORM_PASSWORD 23 | sed -i "s/5244/$PORT/g" data/config.json 24 | -------------------------------------------------------------------------------- /build-freebsd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | appName="alist" 4 | builtAt="$(date +'%F %T %z')" 5 | goVersion=$(go version | sed 's/go version //') 6 | gitAuthor="Xhofe " 7 | gitCommit=$(git log --pretty=format:"%h" -1) 8 | version=$(git describe --long --tags --dirty --always) 9 | webVersion=$(wget -qO- -t1 -T2 "https://api.github.com/repos/ykxVK8yL5L/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g') 10 | 11 | ldflags="\ 12 | -w -s \ 13 | -X 'github.com/alist-org/alist/v3/internal/conf.BuiltAt=$builtAt' \ 14 | -X 'github.com/alist-org/alist/v3/internal/conf.GoVersion=$goVersion' \ 15 | -X 'github.com/alist-org/alist/v3/internal/conf.GitAuthor=$gitAuthor' \ 16 | -X 'github.com/alist-org/alist/v3/internal/conf.GitCommit=$gitCommit' \ 17 | -X 'github.com/alist-org/alist/v3/internal/conf.Version=$version' \ 18 | -X 'github.com/alist-org/alist/v3/internal/conf.WebVersion=$webVersion' \ 19 | " 20 | 21 | go build -ldflags="$ldflags" -tags=jsoniter . 22 | -------------------------------------------------------------------------------- /cmd/cancel2FA.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "github.com/alist-org/alist/v3/internal/op" 8 | "github.com/alist-org/alist/v3/pkg/utils" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | // Cancel2FACmd represents the delete2fa command 13 | var Cancel2FACmd = &cobra.Command{ 14 | Use: "cancel2fa", 15 | Short: "Delete 2FA of admin user", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | Init() 18 | defer Release() 19 | admin, err := op.GetAdmin() 20 | if err != nil { 21 | utils.Log.Errorf("failed to get admin user: %+v", err) 22 | } else { 23 | err := op.Cancel2FAByUser(admin) 24 | if err != nil { 25 | utils.Log.Errorf("failed to cancel 2FA: %+v", err) 26 | } else { 27 | utils.Log.Info("2FA canceled") 28 | DelAdminCacheOnline() 29 | } 30 | } 31 | }, 32 | } 33 | 34 | func init() { 35 | RootCmd.AddCommand(Cancel2FACmd) 36 | 37 | // Here you will define your flags and configuration settings. 38 | 39 | // Cobra supports Persistent Flags which will work for this command 40 | // and all subcommands, e.g.: 41 | // cancel2FACmd.PersistentFlags().String("foo", "", "A help for foo") 42 | 43 | // Cobra supports local flags which will only run when this command 44 | // is called directly, e.g.: 45 | // cancel2FACmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 46 | } 47 | -------------------------------------------------------------------------------- /cmd/common.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strconv" 7 | 8 | "github.com/alist-org/alist/v3/internal/bootstrap" 9 | "github.com/alist-org/alist/v3/internal/bootstrap/data" 10 | "github.com/alist-org/alist/v3/internal/db" 11 | "github.com/alist-org/alist/v3/pkg/utils" 12 | log "github.com/sirupsen/logrus" 13 | ) 14 | 15 | func Init() { 16 | bootstrap.InitConfig() 17 | bootstrap.Log() 18 | bootstrap.InitDB() 19 | data.InitData() 20 | bootstrap.InitIndex() 21 | } 22 | 23 | func Release() { 24 | db.Close() 25 | } 26 | 27 | var pid = -1 28 | var pidFile string 29 | 30 | func initDaemon() { 31 | ex, err := os.Executable() 32 | if err != nil { 33 | log.Fatal(err) 34 | } 35 | exPath := filepath.Dir(ex) 36 | _ = os.MkdirAll(filepath.Join(exPath, "daemon"), 0700) 37 | pidFile = filepath.Join(exPath, "daemon/pid") 38 | if utils.Exists(pidFile) { 39 | bytes, err := os.ReadFile(pidFile) 40 | if err != nil { 41 | log.Fatal("failed to read pid file", err) 42 | } 43 | id, err := strconv.Atoi(string(bytes)) 44 | if err != nil { 45 | log.Fatal("failed to parse pid data", err) 46 | } 47 | pid = id 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cmd/flags/config.go: -------------------------------------------------------------------------------- 1 | package flags 2 | 3 | var ( 4 | DataDir string 5 | Debug bool 6 | NoPrefix bool 7 | Dev bool 8 | ForceBinDir bool 9 | LogStd bool 10 | ) 11 | -------------------------------------------------------------------------------- /cmd/restart.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | // RestartCmd represents the restart command 11 | var RestartCmd = &cobra.Command{ 12 | Use: "restart", 13 | Short: "Restart alist server by daemon/pid file", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | stop() 16 | start() 17 | }, 18 | } 19 | 20 | func init() { 21 | RootCmd.AddCommand(RestartCmd) 22 | 23 | // Here you will define your flags and configuration settings. 24 | 25 | // Cobra supports Persistent Flags which will work for this command 26 | // and all subcommands, e.g.: 27 | // restartCmd.PersistentFlags().String("foo", "", "A help for foo") 28 | 29 | // Cobra supports local flags which will only run when this command 30 | // is called directly, e.g.: 31 | // restartCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 32 | } 33 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/alist-org/alist/v3/cmd/flags" 8 | _ "github.com/alist-org/alist/v3/drivers" 9 | _ "github.com/alist-org/alist/v3/internal/offline_download" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var RootCmd = &cobra.Command{ 14 | Use: "alist", 15 | Short: "A file list program that supports multiple storage.", 16 | Long: `A file list program that supports multiple storage, 17 | built with love by Xhofe and friends in Go/Solid.js. 18 | Complete documentation is available at https://alist.nn.ci/`, 19 | } 20 | 21 | func Execute() { 22 | if err := RootCmd.Execute(); err != nil { 23 | fmt.Fprintln(os.Stderr, err) 24 | os.Exit(1) 25 | } 26 | } 27 | 28 | func init() { 29 | RootCmd.PersistentFlags().StringVar(&flags.DataDir, "data", "data", "data folder") 30 | RootCmd.PersistentFlags().BoolVar(&flags.Debug, "debug", false, "start with debug mode") 31 | RootCmd.PersistentFlags().BoolVar(&flags.NoPrefix, "no-prefix", false, "disable env prefix") 32 | RootCmd.PersistentFlags().BoolVar(&flags.Dev, "dev", false, "start with dev mode") 33 | RootCmd.PersistentFlags().BoolVar(&flags.ForceBinDir, "force-bin-dir", false, "Force to use the directory where the binary file is located as data directory") 34 | RootCmd.PersistentFlags().BoolVar(&flags.LogStd, "log-std", false, "Force to log to std") 35 | } 36 | -------------------------------------------------------------------------------- /cmd/stop.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "os" 8 | 9 | log "github.com/sirupsen/logrus" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | // StopCmd represents the stop command 14 | var StopCmd = &cobra.Command{ 15 | Use: "stop", 16 | Short: "Stop alist server by daemon/pid file", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | stop() 19 | }, 20 | } 21 | 22 | func stop() { 23 | initDaemon() 24 | if pid == -1 { 25 | log.Info("Seems not have been started. Try use `alist start` to start server.") 26 | return 27 | } 28 | process, err := os.FindProcess(pid) 29 | if err != nil { 30 | log.Errorf("failed to find process by pid: %d, reason: %v", pid, process) 31 | return 32 | } 33 | err = process.Kill() 34 | if err != nil { 35 | log.Errorf("failed to kill process %d: %v", pid, err) 36 | } else { 37 | log.Info("killed process: ", pid) 38 | } 39 | err = os.Remove(pidFile) 40 | if err != nil { 41 | log.Errorf("failed to remove pid file") 42 | } 43 | pid = -1 44 | } 45 | 46 | func init() { 47 | RootCmd.AddCommand(StopCmd) 48 | 49 | // Here you will define your flags and configuration settings. 50 | 51 | // Cobra supports Persistent Flags which will work for this command 52 | // and all subcommands, e.g.: 53 | // stopCmd.PersistentFlags().String("foo", "", "A help for foo") 54 | 55 | // Cobra supports local flags which will only run when this command 56 | // is called directly, e.g.: 57 | // stopCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 58 | } 59 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright © 2022 NAME HERE 3 | */ 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "github.com/alist-org/alist/v3/internal/conf" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | // VersionCmd represents the version command 15 | var VersionCmd = &cobra.Command{ 16 | Use: "version", 17 | Short: "Show current version of AList", 18 | Run: func(cmd *cobra.Command, args []string) { 19 | fmt.Printf(`Built At: %s 20 | Go Version: %s 21 | Author: %s 22 | Commit ID: %s 23 | Version: %s 24 | WebVersion: %s 25 | `, 26 | conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.Version, conf.WebVersion) 27 | os.Exit(0) 28 | }, 29 | } 30 | 31 | func init() { 32 | RootCmd.AddCommand(VersionCmd) 33 | 34 | // Here you will define your flags and configuration settings. 35 | 36 | // Cobra supports Persistent Flags which will work for this command 37 | // and all subcommands, e.g.: 38 | // versionCmd.PersistentFlags().String("foo", "", "A help for foo") 39 | 40 | // Cobra supports local flags which will only run when this command 41 | // is called directly, e.g.: 42 | // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 43 | } 44 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | alist: 4 | restart: always 5 | volumes: 6 | - '/etc/alist:/opt/alist/data' 7 | ports: 8 | - '5244:5244' 9 | - '5245:5245' 10 | environment: 11 | - PUID=0 12 | - PGID=0 13 | - UMASK=022 14 | - TZ=UTC 15 | container_name: alist 16 | image: 'xhofe/alist:latest' 17 | -------------------------------------------------------------------------------- /drivers/115/appver.go: -------------------------------------------------------------------------------- 1 | package _115 2 | 3 | import ( 4 | driver115 "github.com/SheltonZhu/115driver/pkg/driver" 5 | "github.com/alist-org/alist/v3/drivers/base" 6 | log "github.com/sirupsen/logrus" 7 | ) 8 | 9 | var ( 10 | md5Salt = "Qclm8MGWUv59TnrR0XPg" 11 | appVer = "27.0.5.7" 12 | ) 13 | 14 | func (d *Pan115) getAppVersion() ([]driver115.AppVersion, error) { 15 | result := driver115.VersionResp{} 16 | resp, err := base.RestyClient.R().Get(driver115.ApiGetVersion) 17 | 18 | err = driver115.CheckErr(err, &result, resp) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | return result.Data.GetAppVersions(), nil 24 | } 25 | 26 | func (d *Pan115) getAppVer() string { 27 | // todo add some cache? 28 | vers, err := d.getAppVersion() 29 | if err != nil { 30 | log.Warnf("[115] get app version failed: %v", err) 31 | return appVer 32 | } 33 | for _, ver := range vers { 34 | if ver.AppName == "win" { 35 | return ver.Version 36 | } 37 | } 38 | return appVer 39 | } 40 | 41 | func (d *Pan115) initAppVer() { 42 | appVer = d.getAppVer() 43 | } 44 | -------------------------------------------------------------------------------- /drivers/115/meta.go: -------------------------------------------------------------------------------- 1 | package _115 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"` 10 | QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"` 11 | QRCodeSource string `json:"qrcode_source" type:"select" options:"web,android,ios,tv,alipaymini,wechatmini,qandroid" default:"linux" help:"select the QR code device, default linux"` 12 | PageSize int64 `json:"page_size" type:"number" default:"1000" help:"list api per page size of 115 driver"` 13 | LimitRate float64 `json:"limit_rate" type:"float" default:"2" help:"limit all api request rate ([limit]r/1s)"` 14 | driver.RootID 15 | } 16 | 17 | var config = driver.Config{ 18 | Name: "115 Cloud", 19 | DefaultRoot: "0", 20 | // OnlyProxy: true, 21 | // OnlyLocal: true, 22 | // NoOverwriteUpload: true, 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &Pan115{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/115/types.go: -------------------------------------------------------------------------------- 1 | package _115 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/SheltonZhu/115driver/pkg/driver" 7 | "github.com/alist-org/alist/v3/internal/model" 8 | "github.com/alist-org/alist/v3/pkg/utils" 9 | ) 10 | 11 | var _ model.Obj = (*FileObj)(nil) 12 | 13 | type FileObj struct { 14 | driver.File 15 | } 16 | 17 | func (f *FileObj) CreateTime() time.Time { 18 | return f.File.CreateTime 19 | } 20 | 21 | func (f *FileObj) GetHash() utils.HashInfo { 22 | return utils.NewHashInfo(utils.SHA1, f.Sha1) 23 | } 24 | 25 | type UploadResult struct { 26 | driver.BasicResp 27 | Data struct { 28 | PickCode string `json:"pick_code"` 29 | FileSize int `json:"file_size"` 30 | FileID string `json:"file_id"` 31 | ThumbURL string `json:"thumb_url"` 32 | Sha1 string `json:"sha1"` 33 | Aid int `json:"aid"` 34 | FileName string `json:"file_name"` 35 | Cid string `json:"cid"` 36 | IsVideo int `json:"is_video"` 37 | } `json:"data"` 38 | } 39 | -------------------------------------------------------------------------------- /drivers/115_share/meta.go: -------------------------------------------------------------------------------- 1 | package _115_share 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Cookie string `json:"cookie" type:"text" help:"one of QR code token and cookie required"` 10 | QRCodeToken string `json:"qrcode_token" type:"text" help:"one of QR code token and cookie required"` 11 | QRCodeSource string `json:"qrcode_source" type:"select" options:"web,android,ios,tv,alipaymini,wechatmini,qandroid" default:"linux" help:"select the QR code device, default linux"` 12 | PageSize int64 `json:"page_size" type:"number" default:"1000" help:"list api per page size of 115 driver"` 13 | LimitRate float64 `json:"limit_rate" type:"float" default:"2" help:"limit all api request rate (1r/[limit_rate]s)"` 14 | ShareCode string `json:"share_code" type:"text" required:"true" help:"share code of 115 share link"` 15 | ReceiveCode string `json:"receive_code" type:"text" required:"true" help:"receive code of 115 share link"` 16 | driver.RootID 17 | } 18 | 19 | var config = driver.Config{ 20 | Name: "115 Share", 21 | DefaultRoot: "", 22 | // OnlyProxy: true, 23 | // OnlyLocal: true, 24 | CheckStatus: false, 25 | Alert: "", 26 | NoOverwriteUpload: true, 27 | NoUpload: true, 28 | } 29 | 30 | func init() { 31 | op.RegisterDriver(func() driver.Driver { 32 | return &Pan115Share{} 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /drivers/123/meta.go: -------------------------------------------------------------------------------- 1 | package _123 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Username string `json:"username" required:"true"` 10 | Password string `json:"password" required:"true"` 11 | driver.RootID 12 | //OrderBy string `json:"order_by" type:"select" options:"file_id,file_name,size,update_at" default:"file_name"` 13 | //OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` 14 | AccessToken string 15 | } 16 | 17 | var config = driver.Config{ 18 | Name: "123Pan", 19 | DefaultRoot: "0", 20 | LocalSort: true, 21 | } 22 | 23 | func init() { 24 | op.RegisterDriver(func() driver.Driver { 25 | return &Pan123{} 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /drivers/123_link/meta.go: -------------------------------------------------------------------------------- 1 | package _123Link 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | OriginURLs string `json:"origin_urls" type:"text" required:"true" default:"https://vip.123pan.com/29/folder/file.mp3" help:"structure:FolderName:\n [FileSize:][Modified:]Url"` 10 | PrivateKey string `json:"private_key"` 11 | UID uint64 `json:"uid" type:"number"` 12 | ValidDuration int64 `json:"valid_duration" type:"number" default:"30" help:"minutes"` 13 | } 14 | 15 | var config = driver.Config{ 16 | Name: "123PanLink", 17 | } 18 | 19 | func init() { 20 | op.RegisterDriver(func() driver.Driver { 21 | return &Pan123Link{} 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /drivers/123_link/types.go: -------------------------------------------------------------------------------- 1 | package _123Link 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/alist-org/alist/v3/internal/errs" 7 | "github.com/alist-org/alist/v3/internal/model" 8 | ) 9 | 10 | // Node is a node in the folder tree 11 | type Node struct { 12 | Url string 13 | Name string 14 | Level int 15 | Modified int64 16 | Size int64 17 | Children []*Node 18 | } 19 | 20 | func (node *Node) getByPath(paths []string) *Node { 21 | if len(paths) == 0 || node == nil { 22 | return nil 23 | } 24 | if node.Name != paths[0] { 25 | return nil 26 | } 27 | if len(paths) == 1 { 28 | return node 29 | } 30 | for _, child := range node.Children { 31 | tmp := child.getByPath(paths[1:]) 32 | if tmp != nil { 33 | return tmp 34 | } 35 | } 36 | return nil 37 | } 38 | 39 | func (node *Node) isFile() bool { 40 | return node.Url != "" 41 | } 42 | 43 | func (node *Node) calSize() int64 { 44 | if node.isFile() { 45 | return node.Size 46 | } 47 | var size int64 = 0 48 | for _, child := range node.Children { 49 | size += child.calSize() 50 | } 51 | node.Size = size 52 | return size 53 | } 54 | 55 | func nodeToObj(node *Node, path string) (model.Obj, error) { 56 | if node == nil { 57 | return nil, errs.ObjectNotFound 58 | } 59 | return &model.Object{ 60 | Name: node.Name, 61 | Size: node.Size, 62 | Modified: time.Unix(node.Modified, 0), 63 | IsFolder: !node.isFile(), 64 | Path: path, 65 | }, nil 66 | } 67 | -------------------------------------------------------------------------------- /drivers/123_link/util.go: -------------------------------------------------------------------------------- 1 | package _123Link 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "math/rand" 7 | "net/url" 8 | "time" 9 | ) 10 | 11 | func SignURL(originURL, privateKey string, uid uint64, validDuration time.Duration) (newURL string, err error) { 12 | if privateKey == "" { 13 | return originURL, nil 14 | } 15 | var ( 16 | ts = time.Now().Add(validDuration).Unix() // 有效时间戳 17 | rInt = rand.Int() // 随机正整数 18 | objURL *url.URL 19 | ) 20 | objURL, err = url.Parse(originURL) 21 | if err != nil { 22 | return "", err 23 | } 24 | authKey := fmt.Sprintf("%d-%d-%d-%x", ts, rInt, uid, md5.Sum([]byte(fmt.Sprintf("%s-%d-%d-%d-%s", 25 | objURL.Path, ts, rInt, uid, privateKey)))) 26 | v := objURL.Query() 27 | v.Add("auth_key", authKey) 28 | objURL.RawQuery = v.Encode() 29 | return objURL.String(), nil 30 | } 31 | -------------------------------------------------------------------------------- /drivers/123_share/meta.go: -------------------------------------------------------------------------------- 1 | package _123Share 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | ShareKey string `json:"sharekey" required:"true"` 10 | SharePwd string `json:"sharepassword"` 11 | driver.RootID 12 | //OrderBy string `json:"order_by" type:"select" options:"file_name,size,update_at" default:"file_name"` 13 | //OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` 14 | AccessToken string `json:"accesstoken" type:"text"` 15 | } 16 | 17 | var config = driver.Config{ 18 | Name: "123PanShare", 19 | LocalSort: true, 20 | OnlyLocal: false, 21 | OnlyProxy: false, 22 | NoCache: false, 23 | NoUpload: true, 24 | NeedMs: false, 25 | DefaultRoot: "0", 26 | CheckStatus: false, 27 | Alert: "", 28 | NoOverwriteUpload: false, 29 | } 30 | 31 | func init() { 32 | op.RegisterDriver(func() driver.Driver { 33 | return &Pan123Share{} 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /drivers/139/meta.go: -------------------------------------------------------------------------------- 1 | package _139 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | //Account string `json:"account" required:"true"` 10 | Authorization string `json:"authorization" type:"text" required:"true"` 11 | driver.RootID 12 | Type string `json:"type" type:"select" options:"personal_new,family,group,personal" default:"personal_new"` 13 | CloudID string `json:"cloud_id"` 14 | CustomUploadPartSize int64 `json:"custom_upload_part_size" type:"number" default:"0" help:"0 for auto"` 15 | ReportRealSize bool `json:"report_real_size" type:"bool" default:"true" help:"Enable to report the real file size during upload"` 16 | UseLargeThumbnail bool `json:"use_large_thumbnail" type:"bool" default:"false" help:"Enable to use large thumbnail for images"` 17 | } 18 | 19 | var config = driver.Config{ 20 | Name: "139Yun", 21 | LocalSort: true, 22 | ProxyRangeOption: true, 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | d := &Yun139{} 28 | d.ProxyRange = true 29 | return d 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /drivers/189/meta.go: -------------------------------------------------------------------------------- 1 | package _189 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Username string `json:"username" required:"true"` 10 | Password string `json:"password" required:"true"` 11 | Cookie string `json:"cookie" help:"Fill in the cookie if need captcha"` 12 | driver.RootID 13 | } 14 | 15 | var config = driver.Config{ 16 | Name: "189Cloud", 17 | LocalSort: true, 18 | DefaultRoot: "-11", 19 | Alert: `info|You can try to use 189PC driver if this driver does not work.`, 20 | } 21 | 22 | func init() { 23 | op.RegisterDriver(func() driver.Driver { 24 | return &Cloud189{} 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /drivers/189pc/meta.go: -------------------------------------------------------------------------------- 1 | package _189pc 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Username string `json:"username" required:"true"` 10 | Password string `json:"password" required:"true"` 11 | VCode string `json:"validate_code"` 12 | driver.RootID 13 | OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"` 14 | OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` 15 | Type string `json:"type" type:"select" options:"personal,family" default:"personal"` 16 | FamilyID string `json:"family_id"` 17 | UploadMethod string `json:"upload_method" type:"select" options:"stream,rapid,old" default:"stream"` 18 | UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"` 19 | FamilyTransfer bool `json:"family_transfer"` 20 | RapidUpload bool `json:"rapid_upload"` 21 | NoUseOcr bool `json:"no_use_ocr"` 22 | } 23 | 24 | var config = driver.Config{ 25 | Name: "189CloudPC", 26 | DefaultRoot: "-11", 27 | CheckStatus: true, 28 | } 29 | 30 | func init() { 31 | op.RegisterDriver(func() driver.Driver { 32 | return &Cloud189PC{} 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /drivers/alias/meta.go: -------------------------------------------------------------------------------- 1 | package alias 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | // driver.RootPath 11 | // define other 12 | Paths string `json:"paths" required:"true" type:"text"` 13 | ProtectSameName bool `json:"protect_same_name" default:"true" required:"false" help:"Protects same-name files from Delete or Rename"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "Alias", 18 | LocalSort: true, 19 | NoCache: true, 20 | NoUpload: true, 21 | DefaultRoot: "/", 22 | ProxyRangeOption: true, 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &Alias{ 28 | Addition: Addition{ 29 | ProtectSameName: true, 30 | }, 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /drivers/alias/types.go: -------------------------------------------------------------------------------- 1 | package alias 2 | -------------------------------------------------------------------------------- /drivers/alist_v2/meta.go: -------------------------------------------------------------------------------- 1 | package alist_v2 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Address string `json:"url" required:"true"` 11 | Password string `json:"password"` 12 | AccessToken string `json:"access_token"` 13 | } 14 | 15 | var config = driver.Config{ 16 | Name: "AList V2", 17 | LocalSort: true, 18 | NoUpload: true, 19 | DefaultRoot: "/", 20 | } 21 | 22 | func init() { 23 | op.RegisterDriver(func() driver.Driver { 24 | return &AListV2{} 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /drivers/alist_v2/types.go: -------------------------------------------------------------------------------- 1 | package alist_v2 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type File struct { 8 | Id string `json:"-"` 9 | Name string `json:"name"` 10 | Size int64 `json:"size"` 11 | Type int `json:"type"` 12 | Driver string `json:"driver"` 13 | UpdatedAt *time.Time `json:"updated_at"` 14 | Thumbnail string `json:"thumbnail"` 15 | Url string `json:"url"` 16 | SizeStr string `json:"size_str"` 17 | TimeStr string `json:"time_str"` 18 | } 19 | 20 | type PathResp struct { 21 | Type string `json:"type"` 22 | //Meta Meta `json:"meta"` 23 | Files []File `json:"files"` 24 | } 25 | 26 | type PathReq struct { 27 | PageNum int `json:"page_num"` 28 | PageSize int `json:"page_size"` 29 | Password string `json:"password"` 30 | Path string `json:"path"` 31 | } 32 | -------------------------------------------------------------------------------- /drivers/alist_v2/util.go: -------------------------------------------------------------------------------- 1 | package alist_v2 2 | -------------------------------------------------------------------------------- /drivers/alist_v3/meta.go: -------------------------------------------------------------------------------- 1 | package alist_v3 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Address string `json:"url" required:"true"` 11 | MetaPassword string `json:"meta_password"` 12 | Username string `json:"username"` 13 | Password string `json:"password"` 14 | Token string `json:"token"` 15 | PassUAToUpsteam bool `json:"pass_ua_to_upsteam" default:"true"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "AList V3", 20 | LocalSort: true, 21 | DefaultRoot: "/", 22 | CheckStatus: true, 23 | ProxyRangeOption: true, 24 | } 25 | 26 | func init() { 27 | op.RegisterDriver(func() driver.Driver { 28 | return &AListV3{} 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /drivers/aliyundrive/global.go: -------------------------------------------------------------------------------- 1 | package aliyundrive 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | 6 | "github.com/alist-org/alist/v3/pkg/generic_sync" 7 | ) 8 | 9 | type State struct { 10 | deviceID string 11 | signature string 12 | retry int 13 | privateKey *ecdsa.PrivateKey 14 | } 15 | 16 | var global = generic_sync.MapOf[string, *State]{} 17 | -------------------------------------------------------------------------------- /drivers/aliyundrive/meta.go: -------------------------------------------------------------------------------- 1 | package aliyundrive 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | RefreshToken string `json:"refresh_token" required:"true"` 11 | //DeviceID string `json:"device_id" required:"true"` 12 | OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"` 13 | OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"` 14 | RapidUpload bool `json:"rapid_upload"` 15 | InternalUpload bool `json:"internal_upload"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "Aliyundrive", 20 | DefaultRoot: "root", 21 | Alert: `warning|There may be an infinite loop bug in this driver. 22 | Deprecated, no longer maintained and will be removed in a future version. 23 | We recommend using the official driver AliyundriveOpen.`, 24 | } 25 | 26 | func init() { 27 | op.RegisterDriver(func() driver.Driver { 28 | return &AliDrive{} 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /drivers/aliyundrive_share/meta.go: -------------------------------------------------------------------------------- 1 | package aliyundrive_share 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | RefreshToken string `json:"refresh_token" required:"true"` 10 | ShareId string `json:"share_id" required:"true"` 11 | SharePwd string `json:"share_pwd"` 12 | driver.RootID 13 | OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"` 14 | OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"` 15 | } 16 | 17 | var config = driver.Config{ 18 | Name: "AliyundriveShare", 19 | LocalSort: false, 20 | OnlyProxy: false, 21 | NoUpload: true, 22 | DefaultRoot: "root", 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &AliyundriveShare{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/baidu_netdisk/meta.go: -------------------------------------------------------------------------------- 1 | package baidu_netdisk 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | RefreshToken string `json:"refresh_token" required:"true"` 10 | driver.RootPath 11 | OrderBy string `json:"order_by" type:"select" options:"name,time,size" default:"name"` 12 | OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` 13 | DownloadAPI string `json:"download_api" type:"select" options:"official,crack" default:"official"` 14 | ClientID string `json:"client_id" required:"true" default:"iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v"` 15 | ClientSecret string `json:"client_secret" required:"true" default:"jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG"` 16 | CustomCrackUA string `json:"custom_crack_ua" required:"true" default:"netdisk"` 17 | AccessToken string 18 | UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"` 19 | UploadAPI string `json:"upload_api" default:"https://d.pcs.baidu.com"` 20 | CustomUploadPartSize int64 `json:"custom_upload_part_size" type:"number" default:"0" help:"0 for auto"` 21 | } 22 | 23 | var config = driver.Config{ 24 | Name: "BaiduNetdisk", 25 | DefaultRoot: "/", 26 | } 27 | 28 | func init() { 29 | op.RegisterDriver(func() driver.Driver { 30 | return &BaiduNetdisk{} 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /drivers/baidu_photo/meta.go: -------------------------------------------------------------------------------- 1 | package baiduphoto 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // RefreshToken string `json:"refresh_token" required:"true"` 10 | Cookie string `json:"cookie" required:"true"` 11 | ShowType string `json:"show_type" type:"select" options:"root,root_only_album,root_only_file" default:"root"` 12 | AlbumID string `json:"album_id"` 13 | //AlbumPassword string `json:"album_password"` 14 | DeleteOrigin bool `json:"delete_origin"` 15 | // ClientID string `json:"client_id" required:"true" default:"iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v"` 16 | // ClientSecret string `json:"client_secret" required:"true" default:"jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG"` 17 | UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"` 18 | } 19 | 20 | var config = driver.Config{ 21 | Name: "BaiduPhoto", 22 | LocalSort: true, 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &BaiduPhoto{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/baidu_share/meta.go: -------------------------------------------------------------------------------- 1 | package baidu_share 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | driver.RootPath 11 | // driver.RootID 12 | // define other 13 | // Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"` 14 | Surl string `json:"surl"` 15 | Pwd string `json:"pwd"` 16 | BDUSS string `json:"BDUSS"` 17 | } 18 | 19 | var config = driver.Config{ 20 | Name: "BaiduShare", 21 | LocalSort: true, 22 | OnlyLocal: false, 23 | OnlyProxy: false, 24 | NoCache: false, 25 | NoUpload: true, 26 | NeedMs: false, 27 | DefaultRoot: "/", 28 | CheckStatus: false, 29 | Alert: "", 30 | NoOverwriteUpload: false, 31 | } 32 | 33 | func init() { 34 | op.RegisterDriver(func() driver.Driver { 35 | return &BaiduShare{} 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /drivers/baidu_share/types.go: -------------------------------------------------------------------------------- 1 | package baidu_share 2 | -------------------------------------------------------------------------------- /drivers/baidu_share/util.go: -------------------------------------------------------------------------------- 1 | package baidu_share 2 | 3 | // do others that not defined in Driver interface 4 | -------------------------------------------------------------------------------- /drivers/base/types.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import "github.com/go-resty/resty/v2" 4 | 5 | type Json map[string]interface{} 6 | 7 | type TokenResp struct { 8 | AccessToken string `json:"access_token"` 9 | RefreshToken string `json:"refresh_token"` 10 | } 11 | 12 | type ReqCallback func(req *resty.Request) 13 | -------------------------------------------------------------------------------- /drivers/base/upload.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "time" 7 | 8 | "github.com/Xhofe/go-cache" 9 | "github.com/alist-org/alist/v3/internal/driver" 10 | ) 11 | 12 | // storage upload progress, for upload recovery 13 | var UploadStateCache = cache.NewMemCache(cache.WithShards[any](32)) 14 | 15 | // Save upload progress for 20 minutes 16 | func SaveUploadProgress(driver driver.Driver, state any, keys ...string) bool { 17 | return UploadStateCache.Set( 18 | fmt.Sprint(driver.Config().Name, "-upload-", strings.Join(keys, "-")), 19 | state, 20 | cache.WithEx[any](time.Minute*20)) 21 | } 22 | 23 | // An upload progress can only be made by one process alone, 24 | // so here you need to get it and then delete it. 25 | func GetUploadProgress[T any](driver driver.Driver, keys ...string) (state T, ok bool) { 26 | v, ok := UploadStateCache.GetDel(fmt.Sprint(driver.Config().Name, "-upload-", strings.Join(keys, "-"))) 27 | if ok { 28 | state, ok = v.(T) 29 | } 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /drivers/base/util.go: -------------------------------------------------------------------------------- 1 | package base 2 | -------------------------------------------------------------------------------- /drivers/chaoxing/meta.go: -------------------------------------------------------------------------------- 1 | package chaoxing 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | // 此程序挂载的是超星小组网盘,需要代理才能使用; 9 | // 登录超星后进入个人空间,进入小组,新建小组,点击进去。 10 | // url中就有bbsid的参数,系统限制单文件大小2G,没有总容量限制 11 | type Addition struct { 12 | // 超星用户名及密码 13 | UserName string `json:"user_name" required:"true"` 14 | Password string `json:"password" required:"true"` 15 | // 从自己新建的小组url里获取 16 | Bbsid string `json:"bbsid" required:"true"` 17 | driver.RootID 18 | // 可不填,程序会自动登录获取 19 | Cookie string `json:"cookie"` 20 | } 21 | 22 | type Conf struct { 23 | ua string 24 | referer string 25 | api string 26 | DowloadApi string 27 | } 28 | 29 | func init() { 30 | op.RegisterDriver(func() driver.Driver { 31 | return &ChaoXing{ 32 | config: driver.Config{ 33 | Name: "ChaoXingGroupDrive", 34 | OnlyProxy: true, 35 | OnlyLocal: false, 36 | DefaultRoot: "-1", 37 | NoOverwriteUpload: true, 38 | }, 39 | conf: Conf{ 40 | ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch", 41 | referer: "https://chaoxing.com/", 42 | api: "https://groupweb.chaoxing.com", 43 | DowloadApi: "https://noteyd.chaoxing.com", 44 | }, 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /drivers/chunker/meta.go: -------------------------------------------------------------------------------- 1 | package chunker 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | RemotePath string `json:"remote_path" required:"true" help:"This is where the encrypted data stores"` 10 | ChunkSize int64 `json:"chunk_size" type:"number" default:"50" help:"chunk size while uploading (unit: MB)"` 11 | } 12 | 13 | var config = driver.Config{ 14 | Name: "Chunker", 15 | LocalSort: true, 16 | OnlyLocal: false, 17 | OnlyProxy: true, 18 | NoCache: true, 19 | NoUpload: false, 20 | NeedMs: false, 21 | DefaultRoot: "/", 22 | CheckStatus: false, 23 | Alert: "", 24 | NoOverwriteUpload: false, 25 | } 26 | 27 | func init() { 28 | op.RegisterDriver(func() driver.Driver { 29 | return &Chunker{} 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /drivers/chunker/types.go: -------------------------------------------------------------------------------- 1 | package chunker 2 | -------------------------------------------------------------------------------- /drivers/cloudreve/meta.go: -------------------------------------------------------------------------------- 1 | package cloudreve 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | driver.RootPath 11 | // define other 12 | Address string `json:"address" required:"true"` 13 | Username string `json:"username"` 14 | Password string `json:"password"` 15 | Cookie string `json:"cookie"` 16 | CustomUA string `json:"custom_ua"` 17 | EnableThumbAndFolderSize bool `json:"enable_thumb_and_folder_size"` 18 | } 19 | 20 | var config = driver.Config{ 21 | Name: "Cloudreve", 22 | DefaultRoot: "/", 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &Cloudreve{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/crypt/types.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | -------------------------------------------------------------------------------- /drivers/crypt/util.go: -------------------------------------------------------------------------------- 1 | package crypt 2 | 3 | import ( 4 | stdpath "path" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/alist-org/alist/v3/internal/op" 9 | ) 10 | 11 | // will give the best guessing based on the path 12 | func guessPath(path string) (isFolder, secondTry bool) { 13 | if strings.HasSuffix(path, "/") { 14 | //confirmed a folder 15 | return true, false 16 | } 17 | lastSlash := strings.LastIndex(path, "/") 18 | if strings.Index(path[lastSlash:], ".") < 0 { 19 | //no dot, try folder then try file 20 | return true, true 21 | } 22 | return false, true 23 | } 24 | 25 | func (d *Crypt) getPathForRemote(path string, isFolder bool) (remoteFullPath string) { 26 | if isFolder && !strings.HasSuffix(path, "/") { 27 | path = path + "/" 28 | } 29 | dir, fileName := filepath.Split(path) 30 | 31 | remoteDir := d.cipher.EncryptDirName(dir) 32 | remoteFileName := "" 33 | if len(strings.TrimSpace(fileName)) > 0 { 34 | remoteFileName = d.cipher.EncryptFileName(fileName) 35 | } 36 | return stdpath.Join(d.RemotePath, remoteDir, remoteFileName) 37 | 38 | } 39 | 40 | // actual path is used for internal only. any link for user should come from remoteFullPath 41 | func (d *Crypt) getActualPathForRemote(path string, isFolder bool) (string, error) { 42 | _, remoteActualPath, err := op.GetStorageAndActualPath(d.getPathForRemote(path, isFolder)) 43 | return remoteActualPath, err 44 | } 45 | -------------------------------------------------------------------------------- /drivers/doubao/meta.go: -------------------------------------------------------------------------------- 1 | package doubao 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | // driver.RootPath 11 | driver.RootID 12 | // define other 13 | Cookie string `json:"cookie" type:"text"` 14 | UploadThread string `json:"upload_thread" default:"3"` 15 | DownloadApi string `json:"download_api" type:"select" options:"get_file_url,get_download_info" default:"get_file_url"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "Doubao", 20 | LocalSort: true, 21 | OnlyLocal: false, 22 | OnlyProxy: false, 23 | NoCache: false, 24 | NoUpload: false, 25 | NeedMs: false, 26 | DefaultRoot: "0", 27 | CheckStatus: false, 28 | Alert: "", 29 | NoOverwriteUpload: false, 30 | } 31 | 32 | func init() { 33 | op.RegisterDriver(func() driver.Driver { 34 | return &Doubao{} 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /drivers/doubao_share/meta.go: -------------------------------------------------------------------------------- 1 | package doubao_share 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Cookie string `json:"cookie" type:"text"` 11 | ShareIds string `json:"share_ids" type:"text" required:"true"` 12 | } 13 | 14 | var config = driver.Config{ 15 | Name: "DoubaoShare", 16 | LocalSort: true, 17 | OnlyLocal: false, 18 | OnlyProxy: false, 19 | NoCache: false, 20 | NoUpload: true, 21 | NeedMs: false, 22 | DefaultRoot: "/", 23 | CheckStatus: false, 24 | Alert: "", 25 | NoOverwriteUpload: false, 26 | } 27 | 28 | func init() { 29 | op.RegisterDriver(func() driver.Driver { 30 | return &DoubaoShare{} 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /drivers/dropbox/meta.go: -------------------------------------------------------------------------------- 1 | package dropbox 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | const ( 9 | DefaultClientID = "76lrwrklhdn1icb" 10 | ) 11 | 12 | type Addition struct { 13 | RefreshToken string `json:"refresh_token" required:"true"` 14 | driver.RootPath 15 | 16 | OauthTokenURL string `json:"oauth_token_url" default:"https://api.xhofe.top/alist/dropbox/token"` 17 | ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"` 18 | ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"` 19 | 20 | AccessToken string 21 | RootNamespaceId string 22 | } 23 | 24 | var config = driver.Config{ 25 | Name: "Dropbox", 26 | LocalSort: false, 27 | OnlyLocal: false, 28 | OnlyProxy: false, 29 | NoCache: false, 30 | NoUpload: false, 31 | NeedMs: false, 32 | DefaultRoot: "", 33 | NoOverwriteUpload: true, 34 | } 35 | 36 | func init() { 37 | op.RegisterDriver(func() driver.Driver { 38 | return &Dropbox{ 39 | base: "https://api.dropboxapi.com", 40 | contentBase: "https://content.dropboxapi.com", 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /drivers/fastwebdav/meta.go: -------------------------------------------------------------------------------- 1 | package fastwebdav 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Address string `json:"address" required:"true"` 11 | APIKey string `json:"password" required:"true"` 12 | } 13 | 14 | var config = driver.Config{ 15 | Name: "FastWebdav", 16 | DefaultRoot: "/", 17 | NoUpload: true, 18 | Alert: "warning|只支持读文件不能进行其它操作,如:复制,移动,上传等", 19 | } 20 | 21 | func init() { 22 | op.RegisterDriver(func() driver.Driver { 23 | return &FastWebdav{} 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /drivers/febbox/meta.go: -------------------------------------------------------------------------------- 1 | package febbox 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | ClientID string `json:"client_id" required:"true" default:""` 11 | ClientSecret string `json:"client_secret" required:"true" default:""` 12 | RefreshToken string 13 | SortRule string `json:"sort_rule" required:"true" type:"select" options:"size_asc,size_desc,name_asc,name_desc,update_asc,update_desc,ext_asc,ext_desc" default:"name_asc"` 14 | PageSize int64 `json:"page_size" required:"true" type:"number" default:"100" help:"list api per page size of FebBox driver"` 15 | UserIP string `json:"user_ip" default:"" help:"user ip address for download link which can speed up the download"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "FebBox", 20 | LocalSort: false, 21 | OnlyLocal: false, 22 | OnlyProxy: false, 23 | NoCache: false, 24 | NoUpload: true, 25 | NeedMs: false, 26 | DefaultRoot: "0", 27 | CheckStatus: false, 28 | Alert: "", 29 | NoOverwriteUpload: false, 30 | } 31 | 32 | func init() { 33 | op.RegisterDriver(func() driver.Driver { 34 | return &FebBox{} 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /drivers/ftp/meta.go: -------------------------------------------------------------------------------- 1 | package ftp 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | "github.com/axgle/mahonia" 7 | ) 8 | 9 | func encode(str string, encoding string) string { 10 | if encoding == "" { 11 | return str 12 | } 13 | encoder := mahonia.NewEncoder(encoding) 14 | return encoder.ConvertString(str) 15 | } 16 | 17 | func decode(str string, encoding string) string { 18 | if encoding == "" { 19 | return str 20 | } 21 | decoder := mahonia.NewDecoder(encoding) 22 | return decoder.ConvertString(str) 23 | } 24 | 25 | type Addition struct { 26 | Address string `json:"address" required:"true"` 27 | Encoding string `json:"encoding" required:"true"` 28 | Username string `json:"username" required:"true"` 29 | Password string `json:"password" required:"true"` 30 | driver.RootPath 31 | } 32 | 33 | var config = driver.Config{ 34 | Name: "FTP", 35 | LocalSort: true, 36 | OnlyLocal: true, 37 | DefaultRoot: "/", 38 | } 39 | 40 | func init() { 41 | op.RegisterDriver(func() driver.Driver { 42 | return &FTP{} 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /drivers/ftp/types.go: -------------------------------------------------------------------------------- 1 | package ftp 2 | -------------------------------------------------------------------------------- /drivers/google_drive/meta.go: -------------------------------------------------------------------------------- 1 | package google_drive 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | RefreshToken string `json:"refresh_token" required:"true"` 11 | OrderBy string `json:"order_by" type:"string" help:"such as: folder,name,modifiedTime"` 12 | OrderDirection string `json:"order_direction" type:"select" options:"asc,desc"` 13 | ClientID string `json:"client_id" required:"true" default:"202264815644.apps.googleusercontent.com"` 14 | ClientSecret string `json:"client_secret" required:"true" default:"X4Z3ca8xfWDb1Voo-F9a7ZxJ"` 15 | ChunkSize int64 `json:"chunk_size" type:"number" default:"5" help:"chunk size while uploading (unit: MB)"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "GoogleDrive", 20 | OnlyProxy: true, 21 | DefaultRoot: "root", 22 | } 23 | 24 | func init() { 25 | op.RegisterDriver(func() driver.Driver { 26 | return &GoogleDrive{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /drivers/google_photo/meta.go: -------------------------------------------------------------------------------- 1 | package google_photo 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | RefreshToken string `json:"refresh_token" required:"true"` 11 | ClientID string `json:"client_id" required:"true" default:"202264815644.apps.googleusercontent.com"` 12 | ClientSecret string `json:"client_secret" required:"true" default:"X4Z3ca8xfWDb1Voo-F9a7ZxJ"` 13 | ShowArchive bool `json:"show_archive"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "GooglePhoto", 18 | OnlyProxy: true, 19 | DefaultRoot: "root", 20 | NoUpload: true, 21 | LocalSort: true, 22 | } 23 | 24 | func init() { 25 | op.RegisterDriver(func() driver.Driver { 26 | return &GooglePhoto{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /drivers/halalcloud/meta.go: -------------------------------------------------------------------------------- 1 | package halalcloud 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | driver.RootPath 11 | // define other 12 | RefreshToken string `json:"refresh_token" required:"true" help:"login type is refresh_token,this is required"` 13 | UploadThread string `json:"upload_thread" default:"3" help:"1 <= thread <= 32"` 14 | UseDavMode bool `json:"use_webdav" default:"false" help:""` 15 | WebDavUserName string `json:"webdav_username" default:"" help:"auto fetch"` 16 | WebDavPassWord string `json:"webdav_password" default:"" help:"auto fetch"` 17 | 18 | AppID string `json:"app_id" required:"true" default:"alist/10001"` 19 | AppVersion string `json:"app_version" required:"true" default:"1.0.0"` 20 | AppSecret string `json:"app_secret" required:"true" default:"bR4SJwOkvnG5WvVJ"` 21 | } 22 | 23 | var config = driver.Config{ 24 | Name: "HalalCloud", 25 | LocalSort: false, 26 | OnlyLocal: false, 27 | OnlyProxy: false, 28 | NoCache: false, 29 | NoUpload: false, 30 | NeedMs: false, 31 | DefaultRoot: "/", 32 | CheckStatus: false, 33 | Alert: "", 34 | NoOverwriteUpload: false, 35 | DeafultProxy: true, 36 | DeafultWebDavPolicy: "native_proxy", 37 | } 38 | 39 | func init() { 40 | op.RegisterDriver(func() driver.Driver { 41 | return &HalalCloud{} 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /drivers/homecloud/meta.go: -------------------------------------------------------------------------------- 1 | package homecloud 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | // https://homecloud.komect.com/ 9 | type Addition struct { 10 | //Account string `json:"account" required:"true"` 11 | RefreshToken string `json:"refresh_token" required:"true"` 12 | driver.RootID 13 | GroupID string `json:"groupId" required:"true"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "homecloud", 18 | LocalSort: true, 19 | } 20 | 21 | func init() { 22 | op.RegisterDriver(func() driver.Driver { 23 | return &HomeCloud{} 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /drivers/ipfs_api/meta.go: -------------------------------------------------------------------------------- 1 | package ipfs 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | driver.RootPath 11 | Endpoint string `json:"endpoint" default:"http://127.0.0.1:5001"` 12 | Gateway string `json:"gateway" default:"https://ipfs.io"` 13 | } 14 | 15 | var config = driver.Config{ 16 | Name: "IPFS API", 17 | DefaultRoot: "/", 18 | LocalSort: true, 19 | } 20 | 21 | func init() { 22 | op.RegisterDriver(func() driver.Driver { 23 | return &IPFS{} 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /drivers/kodbox/meta.go: -------------------------------------------------------------------------------- 1 | package kodbox 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | 11 | Address string `json:"address" required:"true"` 12 | UserName string `json:"username" required:"false"` 13 | Password string `json:"password" required:"false"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "KodBox", 18 | DefaultRoot: "", 19 | } 20 | 21 | func init() { 22 | op.RegisterDriver(func() driver.Driver { 23 | return &KodBox{} 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /drivers/kodbox/types.go: -------------------------------------------------------------------------------- 1 | package kodbox 2 | 3 | type CommonResp struct { 4 | Code any `json:"code"` 5 | TimeUse string `json:"timeUse"` 6 | TimeNow string `json:"timeNow"` 7 | Data any `json:"data"` 8 | Info any `json:"info"` 9 | } 10 | 11 | type ListPathData struct { 12 | FolderList []FolderOrFile `json:"folderList"` 13 | FileList []FolderOrFile `json:"fileList"` 14 | } 15 | 16 | type FolderOrFile struct { 17 | Name string `json:"name"` 18 | Path string `json:"path"` 19 | Type string `json:"type"` 20 | Ext string `json:"ext,omitempty"` // 文件特有字段 21 | Size int64 `json:"size"` 22 | CreateTime int64 `json:"createTime"` 23 | ModifyTime int64 `json:"modifyTime"` 24 | } 25 | -------------------------------------------------------------------------------- /drivers/lanzou/meta.go: -------------------------------------------------------------------------------- 1 | package lanzou 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Type string `json:"type" type:"select" options:"account,cookie,url" default:"cookie"` 10 | 11 | Account string `json:"account"` 12 | Password string `json:"password"` 13 | 14 | Cookie string `json:"cookie" help:"about 15 days valid, ignore if shareUrl is used"` 15 | 16 | driver.RootID 17 | SharePassword string `json:"share_password"` 18 | BaseUrl string `json:"baseUrl" required:"true" default:"https://pc.woozooo.com" help:"basic URL for file operation"` 19 | ShareUrl string `json:"shareUrl" required:"true" default:"https://pan.lanzoui.com" help:"used to get the sharing page"` 20 | UserAgent string `json:"user_agent" required:"true" default:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.39 (KHTML, like Gecko) Chrome/89.0.4389.111 Safari/537.39"` 21 | RepairFileInfo bool `json:"repair_file_info" help:"To use webdav, you need to enable it"` 22 | } 23 | 24 | func (a *Addition) IsCookie() bool { 25 | return a.Type == "cookie" 26 | } 27 | 28 | func (a *Addition) IsAccount() bool { 29 | return a.Type == "account" 30 | } 31 | 32 | var config = driver.Config{ 33 | Name: "Lanzou", 34 | LocalSort: true, 35 | DefaultRoot: "-1", 36 | } 37 | 38 | func init() { 39 | op.RegisterDriver(func() driver.Driver { 40 | return &LanZou{} 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /drivers/lark.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin windows 2 | // +build amd64 arm64 3 | 4 | package drivers 5 | 6 | import ( 7 | _ "github.com/alist-org/alist/v3/drivers/lark" 8 | ) 9 | -------------------------------------------------------------------------------- /drivers/lark/meta.go: -------------------------------------------------------------------------------- 1 | package lark 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | driver.RootPath 11 | // define other 12 | AppId string `json:"app_id" type:"text" help:"app id"` 13 | AppSecret string `json:"app_secret" type:"text" help:"app secret"` 14 | ExternalMode bool `json:"external_mode" type:"bool" help:"external mode"` 15 | TenantUrlPrefix string `json:"tenant_url_prefix" type:"text" help:"tenant url prefix"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "Lark", 20 | LocalSort: false, 21 | OnlyLocal: false, 22 | OnlyProxy: false, 23 | NoCache: false, 24 | NoUpload: false, 25 | NeedMs: false, 26 | DefaultRoot: "/", 27 | CheckStatus: false, 28 | Alert: "", 29 | NoOverwriteUpload: true, 30 | } 31 | 32 | func init() { 33 | op.RegisterDriver(func() driver.Driver { 34 | return &Lark{} 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /drivers/lark/types.go: -------------------------------------------------------------------------------- 1 | package lark 2 | 3 | import ( 4 | "context" 5 | "github.com/Xhofe/go-cache" 6 | "time" 7 | ) 8 | 9 | type TokenCache struct { 10 | cache.ICache[string] 11 | } 12 | 13 | func (t *TokenCache) Set(_ context.Context, key string, value string, expireTime time.Duration) error { 14 | t.ICache.Set(key, value, cache.WithEx[string](expireTime)) 15 | 16 | return nil 17 | } 18 | 19 | func (t *TokenCache) Get(_ context.Context, key string) (string, error) { 20 | v, ok := t.ICache.Get(key) 21 | if ok { 22 | return v, nil 23 | } 24 | 25 | return "", nil 26 | } 27 | 28 | func newTokenCache() *TokenCache { 29 | c := cache.NewMemCache[string]() 30 | 31 | return &TokenCache{c} 32 | } 33 | -------------------------------------------------------------------------------- /drivers/lenovonas_share/meta.go: -------------------------------------------------------------------------------- 1 | package LenovoNasShare 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | ShareId string `json:"share_id" required:"true" help:"The part after the last / in the shared link"` 11 | SharePwd string `json:"share_pwd" required:"true" help:"The password of the shared link"` 12 | Host string `json:"host" required:"true" default:"https://siot-share.lenovo.com.cn" help:"You can change it to your local area network"` 13 | } 14 | 15 | var config = driver.Config{ 16 | Name: "LenovoNasShare", 17 | LocalSort: true, 18 | OnlyLocal: false, 19 | OnlyProxy: false, 20 | NoCache: false, 21 | NoUpload: true, 22 | NeedMs: false, 23 | DefaultRoot: "", 24 | CheckStatus: false, 25 | Alert: "", 26 | NoOverwriteUpload: false, 27 | } 28 | 29 | func init() { 30 | op.RegisterDriver(func() driver.Driver { 31 | return &LenovoNasShare{} 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /drivers/lenovonas_share/util.go: -------------------------------------------------------------------------------- 1 | package LenovoNasShare 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/alist-org/alist/v3/drivers/base" 7 | "github.com/alist-org/alist/v3/pkg/utils" 8 | jsoniter "github.com/json-iterator/go" 9 | ) 10 | 11 | func (d *LenovoNasShare) request(url string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) { 12 | req := base.RestyClient.R() 13 | req.SetHeaders(map[string]string{ 14 | "origin": "https://siot-share.lenovo.com.cn", 15 | "referer": "https://siot-share.lenovo.com.cn/", 16 | "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) alist-client", 17 | "platform": "web", 18 | "app-version": "3", 19 | }) 20 | if callback != nil { 21 | callback(req) 22 | } 23 | if resp != nil { 24 | req.SetResult(resp) 25 | } 26 | res, err := req.Execute(method, url) 27 | if err != nil { 28 | return nil, err 29 | } 30 | body := res.Body() 31 | result := utils.Json.Get(body, "result").ToBool() 32 | if !result { 33 | return nil, errors.New(jsoniter.Get(body, "error", "msg").ToString()) 34 | } 35 | return body, nil 36 | } 37 | -------------------------------------------------------------------------------- /drivers/local/meta.go: -------------------------------------------------------------------------------- 1 | package local 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Thumbnail bool `json:"thumbnail" required:"true" help:"enable thumbnail"` 11 | ThumbCacheFolder string `json:"thumb_cache_folder"` 12 | ThumbConcurrency string `json:"thumb_concurrency" default:"16" required:"false" help:"Number of concurrent thumbnail generation goroutines. This controls how many thumbnails can be generated in parallel."` 13 | ShowHidden bool `json:"show_hidden" default:"true" required:"false" help:"show hidden directories and files"` 14 | MkdirPerm string `json:"mkdir_perm" default:"777"` 15 | RecycleBinPath string `json:"recycle_bin_path" default:"delete permanently" help:"path to recycle bin, delete permanently if empty or keep 'delete permanently'"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "Local", 20 | OnlyLocal: true, 21 | LocalSort: true, 22 | NoCache: true, 23 | DefaultRoot: "/", 24 | } 25 | 26 | func init() { 27 | op.RegisterDriver(func() driver.Driver { 28 | return &Local{} 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /drivers/mediatrack/meta.go: -------------------------------------------------------------------------------- 1 | package mediatrack 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | AccessToken string `json:"access_token" required:"true"` 10 | ProjectID string `json:"project_id"` 11 | driver.RootID 12 | OrderBy string `json:"order_by" type:"select" options:"updated_at,title,size" default:"title"` 13 | OrderDesc bool `json:"order_desc"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "MediaTrack", 18 | } 19 | 20 | func init() { 21 | op.RegisterDriver(func() driver.Driver { 22 | return &MediaTrack{} 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /drivers/mega/meta.go: -------------------------------------------------------------------------------- 1 | package mega 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | //driver.RootPath 11 | //driver.RootID 12 | Email string `json:"email" required:"true"` 13 | Password string `json:"password" required:"true"` 14 | TwoFACode string `json:"two_fa_code" required:"false" help:"2FA 6-digit code, filling in the 2FA code alone will not support reloading driver"` 15 | TwoFASecret string `json:"two_fa_secret" required:"false" help:"2FA secret"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "Mega_nz", 20 | LocalSort: true, 21 | OnlyLocal: true, 22 | } 23 | 24 | func init() { 25 | op.RegisterDriver(func() driver.Driver { 26 | return &Mega{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /drivers/mega/types.go: -------------------------------------------------------------------------------- 1 | package mega 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/pkg/utils" 5 | "time" 6 | 7 | "github.com/alist-org/alist/v3/internal/model" 8 | "github.com/t3rm1n4l/go-mega" 9 | ) 10 | 11 | type MegaNode struct { 12 | n *mega.Node 13 | } 14 | 15 | func (m *MegaNode) GetSize() int64 { 16 | return m.n.GetSize() 17 | } 18 | 19 | func (m *MegaNode) GetName() string { 20 | return m.n.GetName() 21 | } 22 | 23 | func (m *MegaNode) CreateTime() time.Time { 24 | return m.n.GetTimeStamp() 25 | } 26 | 27 | func (m *MegaNode) GetHash() utils.HashInfo { 28 | //Meganz use md5, but can't get the original file hash, due to it's encrypted in the cloud 29 | return utils.HashInfo{} 30 | } 31 | 32 | func (m *MegaNode) ModTime() time.Time { 33 | return m.n.GetTimeStamp() 34 | } 35 | 36 | func (m *MegaNode) IsDir() bool { 37 | return m.n.GetType() == mega.FOLDER || m.n.GetType() == mega.ROOT 38 | } 39 | 40 | func (m *MegaNode) GetID() string { 41 | return m.n.GetHash() 42 | } 43 | 44 | func (m *MegaNode) GetPath() string { 45 | return "" 46 | } 47 | 48 | var _ model.Obj = (*MegaNode)(nil) 49 | -------------------------------------------------------------------------------- /drivers/mopan/meta.go: -------------------------------------------------------------------------------- 1 | package mopan 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Phone string `json:"phone" required:"true"` 10 | Password string `json:"password" required:"true"` 11 | SMSCode string `json:"sms_code" help:"input 'send' send sms "` 12 | 13 | RootFolderID string `json:"root_folder_id" default:""` 14 | 15 | CloudID string `json:"cloud_id"` 16 | 17 | OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"` 18 | OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` 19 | 20 | DeviceInfo string `json:"device_info"` 21 | 22 | UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"` 23 | } 24 | 25 | func (a *Addition) GetRootId() string { 26 | return a.RootFolderID 27 | } 28 | 29 | var config = driver.Config{ 30 | Name: "MoPan", 31 | // DefaultRoot: "root, / or other", 32 | CheckStatus: true, 33 | Alert: "warning|This network disk may store your password in clear text. Please set your password carefully", 34 | } 35 | 36 | func init() { 37 | op.RegisterDriver(func() driver.Driver { 38 | return &MoPan{} 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /drivers/mopan/types.go: -------------------------------------------------------------------------------- 1 | package mopan 2 | -------------------------------------------------------------------------------- /drivers/netease_music/meta.go: -------------------------------------------------------------------------------- 1 | package netease_music 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/alist-org/alist/v3/internal/driver" 7 | "github.com/alist-org/alist/v3/internal/op" 8 | ) 9 | 10 | type Addition struct { 11 | Cookie string `json:"cookie" type:"text" required:"true" help:""` 12 | SongLimit uint64 `json:"song_limit" default:"200" type:"number" help:"only get 200 songs by default"` 13 | } 14 | 15 | func (ad *Addition) getCookie(name string) string { 16 | re := regexp.MustCompile(name + "=([^(;|$)]+)") 17 | matches := re.FindStringSubmatch(ad.Cookie) 18 | if len(matches) < 2 { 19 | return "" 20 | } 21 | return matches[1] 22 | } 23 | 24 | var config = driver.Config{ 25 | Name: "NeteaseMusic", 26 | } 27 | 28 | func init() { 29 | op.RegisterDriver(func() driver.Driver { 30 | return &NeteaseMusic{} 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /drivers/onedrive/meta.go: -------------------------------------------------------------------------------- 1 | package onedrive 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"` 11 | IsSharepoint bool `json:"is_sharepoint"` 12 | ClientID string `json:"client_id" required:"true"` 13 | ClientSecret string `json:"client_secret" required:"true"` 14 | RedirectUri string `json:"redirect_uri" required:"true" default:"https://alist.nn.ci/tool/onedrive/callback"` 15 | RefreshToken string `json:"refresh_token" required:"true"` 16 | SiteId string `json:"site_id"` 17 | ChunkSize int64 `json:"chunk_size" type:"number" default:"5"` 18 | CustomHost string `json:"custom_host" help:"Custom host for onedrive download link"` 19 | ProxyUrl string `json:"proxy_url" help:"ProxyUrl for onedrive download link like pikpak"` 20 | } 21 | 22 | var config = driver.Config{ 23 | Name: "Onedrive", 24 | LocalSort: true, 25 | DefaultRoot: "/", 26 | } 27 | 28 | func init() { 29 | op.RegisterDriver(func() driver.Driver { 30 | return &Onedrive{} 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /drivers/onedrive_app/meta.go: -------------------------------------------------------------------------------- 1 | package onedrive_app 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Region string `json:"region" type:"select" required:"true" options:"global,cn,us,de" default:"global"` 11 | ClientID string `json:"client_id" required:"true"` 12 | ClientSecret string `json:"client_secret" required:"true"` 13 | TenantID string `json:"tenant_id"` 14 | Email string `json:"email"` 15 | ChunkSize int64 `json:"chunk_size" type:"number" default:"5"` 16 | CustomHost string `json:"custom_host" help:"Custom host for onedrive download link"` 17 | } 18 | 19 | var config = driver.Config{ 20 | Name: "OnedriveAPP", 21 | LocalSort: true, 22 | DefaultRoot: "/", 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &OnedriveAPP{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/onedrive_sharelink/meta.go: -------------------------------------------------------------------------------- 1 | package onedrive_sharelink 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/alist-org/alist/v3/internal/driver" 7 | "github.com/alist-org/alist/v3/internal/op" 8 | ) 9 | 10 | type Addition struct { 11 | driver.RootPath 12 | ShareLinkURL string `json:"url" required:"true"` 13 | ShareLinkPassword string `json:"password"` 14 | IsSharepoint bool 15 | downloadLinkPrefix string 16 | Headers http.Header 17 | HeaderTime int64 18 | } 19 | 20 | var config = driver.Config{ 21 | Name: "Onedrive Sharelink", 22 | OnlyProxy: true, 23 | NoUpload: true, 24 | DefaultRoot: "/", 25 | CheckStatus: false, 26 | } 27 | 28 | func init() { 29 | op.RegisterDriver(func() driver.Driver { 30 | return &OnedriveSharelink{} 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /drivers/pikpak/meta.go: -------------------------------------------------------------------------------- 1 | package pikpak 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | Username string `json:"username" required:"true"` 11 | Password string `json:"password" required:"true"` 12 | Platform string `json:"platform" required:"true" default:"web" type:"select" options:"android,web,pc"` 13 | RefreshToken string `json:"refresh_token" required:"true" default:""` 14 | CaptchaToken string `json:"captcha_token" default:""` 15 | DeviceID string `json:"device_id" required:"false" default:""` 16 | DisableMediaLink bool `json:"disable_media_link" default:"true"` 17 | } 18 | 19 | var config = driver.Config{ 20 | Name: "PikPak", 21 | LocalSort: true, 22 | DefaultRoot: "", 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &PikPak{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/pikpak_proxy/meta.go: -------------------------------------------------------------------------------- 1 | package PikPakProxy 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | Username string `json:"username" required:"true"` 11 | Password string `json:"password" required:"true"` 12 | Platform string `json:"platform" required:"true" default:"web" type:"select" options:"android,web,pc"` 13 | RefreshToken string `json:"refresh_token" required:"true" default:""` 14 | CaptchaToken string `json:"captcha_token" default:""` 15 | DeviceID string `json:"device_id" required:"false" default:""` 16 | DisableMediaLink bool `json:"disable_media_link" default:"true"` 17 | //是否使用代理 18 | UseProxy bool `json:"use_proxy"` 19 | //下代理地址 20 | ProxyUrl string `json:"proxy_url" default:""` 21 | } 22 | 23 | var config = driver.Config{ 24 | Name: "PikPakProxy", 25 | LocalSort: true, 26 | DefaultRoot: "", 27 | } 28 | 29 | func init() { 30 | op.RegisterDriver(func() driver.Driver { 31 | return &PikPakProxy{} 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /drivers/pikpak_share/meta.go: -------------------------------------------------------------------------------- 1 | package pikpak_share 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | ShareId string `json:"share_id" required:"true"` 11 | SharePwd string `json:"share_pwd"` 12 | Platform string `json:"platform" default:"web" required:"true" type:"select" options:"android,web,pc"` 13 | DeviceID string `json:"device_id" required:"false" default:""` 14 | UseTransCodingAddress bool `json:"use_transcoding_address" required:"true" default:"false"` 15 | //是否使用代理 16 | UseProxy bool `json:"use_proxy"` 17 | //下代理地址 18 | ProxyUrl string `json:"proxy_url" default:""` 19 | } 20 | 21 | var config = driver.Config{ 22 | Name: "PikPakShare", 23 | LocalSort: true, 24 | NoUpload: true, 25 | DefaultRoot: "", 26 | } 27 | 28 | func init() { 29 | op.RegisterDriver(func() driver.Driver { 30 | return &PikPakShare{} 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /drivers/quqi/meta.go: -------------------------------------------------------------------------------- 1 | package quqi 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | Phone string `json:"phone"` 11 | Password string `json:"password"` 12 | Cookie string `json:"cookie" help:"Cookie can be used on multiple clients at the same time"` 13 | CDN bool `json:"cdn" help:"If you enable this option, the download speed can be increased, but there will be some performance loss"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "Quqi", 18 | OnlyLocal: true, 19 | LocalSort: true, 20 | //NoUpload: true, 21 | DefaultRoot: "0", 22 | } 23 | 24 | func init() { 25 | op.RegisterDriver(func() driver.Driver { 26 | return &Quqi{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /drivers/s3/types.go: -------------------------------------------------------------------------------- 1 | package s3 2 | -------------------------------------------------------------------------------- /drivers/seafile/meta.go: -------------------------------------------------------------------------------- 1 | package seafile 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | 11 | Address string `json:"address" required:"true"` 12 | UserName string `json:"username" required:"false"` 13 | Password string `json:"password" required:"false"` 14 | Token string `json:"token" required:"false"` 15 | RepoId string `json:"repoId" required:"false"` 16 | RepoPwd string `json:"repoPwd" required:"false"` 17 | } 18 | 19 | var config = driver.Config{ 20 | Name: "Seafile", 21 | DefaultRoot: "/", 22 | } 23 | 24 | func init() { 25 | op.RegisterDriver(func() driver.Driver { 26 | return &Seafile{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /drivers/seafile/types.go: -------------------------------------------------------------------------------- 1 | package seafile 2 | 3 | import "time" 4 | 5 | type AuthTokenResp struct { 6 | Token string `json:"token"` 7 | } 8 | 9 | type RepoItemResp struct { 10 | Id string `json:"id"` 11 | Type string `json:"type"` // repo, dir, file 12 | Name string `json:"name"` 13 | Size int64 `json:"size"` 14 | Modified int64 `json:"mtime"` 15 | Permission string `json:"permission"` 16 | } 17 | 18 | type LibraryItemResp struct { 19 | RepoItemResp 20 | OwnerContactEmail string `json:"owner_contact_email"` 21 | OwnerName string `json:"owner_name"` 22 | Owner string `json:"owner"` 23 | ModifierEmail string `json:"modifier_email"` 24 | ModifierContactEmail string `json:"modifier_contact_email"` 25 | ModifierName string `json:"modifier_name"` 26 | Virtual bool `json:"virtual"` 27 | MtimeRelative string `json:"mtime_relative"` 28 | Encrypted bool `json:"encrypted"` 29 | Version int `json:"version"` 30 | HeadCommitId string `json:"head_commit_id"` 31 | Root string `json:"root"` 32 | Salt string `json:"salt"` 33 | SizeFormatted string `json:"size_formatted"` 34 | } 35 | 36 | type RepoDirItemResp struct { 37 | RepoItemResp 38 | } 39 | 40 | type LibraryInfo struct { 41 | LibraryItemResp 42 | decryptedTime time.Time 43 | decryptedSuccess bool 44 | } -------------------------------------------------------------------------------- /drivers/sftp/meta.go: -------------------------------------------------------------------------------- 1 | package sftp 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Address string `json:"address" required:"true"` 10 | Username string `json:"username" required:"true"` 11 | PrivateKey string `json:"private_key" type:"text"` 12 | Password string `json:"password"` 13 | Passphrase string `json:"passphrase"` 14 | driver.RootPath 15 | IgnoreSymlinkError bool `json:"ignore_symlink_error" default:"false" info:"Ignore symlink error"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "SFTP", 20 | LocalSort: true, 21 | OnlyLocal: true, 22 | DefaultRoot: "/", 23 | CheckStatus: true, 24 | } 25 | 26 | func init() { 27 | op.RegisterDriver(func() driver.Driver { 28 | return &SFTP{} 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /drivers/sftp/types.go: -------------------------------------------------------------------------------- 1 | package sftp 2 | 3 | import ( 4 | "os" 5 | stdpath "path" 6 | "strings" 7 | 8 | "github.com/alist-org/alist/v3/internal/model" 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | func (d *SFTP) fileToObj(f os.FileInfo, dir string) (model.Obj, error) { 13 | symlink := f.Mode()&os.ModeSymlink != 0 14 | if !symlink { 15 | return &model.Object{ 16 | Name: f.Name(), 17 | Size: f.Size(), 18 | Modified: f.ModTime(), 19 | IsFolder: f.IsDir(), 20 | }, nil 21 | } 22 | path := stdpath.Join(dir, f.Name()) 23 | // set target path 24 | target, err := d.client.ReadLink(path) 25 | if err != nil { 26 | return nil, err 27 | } 28 | if !strings.HasPrefix(target, "/") { 29 | target = stdpath.Join(dir, target) 30 | } 31 | _f, err := d.client.Stat(target) 32 | if err != nil { 33 | if d.IgnoreSymlinkError { 34 | return &model.Object{ 35 | Name: f.Name(), 36 | Size: f.Size(), 37 | Modified: f.ModTime(), 38 | IsFolder: f.IsDir(), 39 | }, nil 40 | } 41 | return nil, err 42 | } 43 | // set basic info 44 | obj := &model.Object{ 45 | Name: f.Name(), 46 | Size: _f.Size(), 47 | Modified: _f.ModTime(), 48 | IsFolder: _f.IsDir(), 49 | Path: target, 50 | } 51 | log.Debugf("[sftp] obj: %+v, is symlink: %v", obj, symlink) 52 | return obj, nil 53 | } 54 | -------------------------------------------------------------------------------- /drivers/smb/meta.go: -------------------------------------------------------------------------------- 1 | package smb 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Address string `json:"address" required:"true"` 11 | Username string `json:"username" required:"true"` 12 | Password string `json:"password"` 13 | ShareName string `json:"share_name" required:"true"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "SMB", 18 | LocalSort: true, 19 | OnlyLocal: true, 20 | DefaultRoot: ".", 21 | NoCache: true, 22 | } 23 | 24 | func init() { 25 | op.RegisterDriver(func() driver.Driver { 26 | return &SMB{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /drivers/smb/types.go: -------------------------------------------------------------------------------- 1 | package smb 2 | -------------------------------------------------------------------------------- /drivers/teambition/help.go: -------------------------------------------------------------------------------- 1 | package teambition 2 | 3 | import "strings" 4 | 5 | func getBetweenStr(str, start, end string) string { 6 | n := strings.Index(str, start) 7 | if n == -1 { 8 | return "" 9 | } 10 | n = n + len(start) 11 | str = string([]byte(str)[n:]) 12 | m := strings.Index(str, end) 13 | if m == -1 { 14 | return "" 15 | } 16 | str = string([]byte(str)[:m]) 17 | return str 18 | } 19 | -------------------------------------------------------------------------------- /drivers/teambition/meta.go: -------------------------------------------------------------------------------- 1 | package teambition 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Region string `json:"region" type:"select" options:"china,international" required:"true"` 10 | Cookie string `json:"cookie" required:"true"` 11 | ProjectID string `json:"project_id" required:"true"` 12 | driver.RootID 13 | OrderBy string `json:"order_by" type:"select" options:"fileName,fileSize,updated,created" default:"fileName"` 14 | OrderDirection string `json:"order_direction" type:"select" options:"Asc,Desc" default:"Asc"` 15 | UseS3UploadMethod bool `json:"use_s3_upload_method" default:"true"` 16 | } 17 | 18 | var config = driver.Config{ 19 | Name: "Teambition", 20 | } 21 | 22 | func init() { 23 | op.RegisterDriver(func() driver.Driver { 24 | return &Teambition{} 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /drivers/template/meta.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | driver.RootPath 11 | driver.RootID 12 | // define other 13 | Field string `json:"field" type:"select" required:"true" options:"a,b,c" default:"a"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "Template", 18 | LocalSort: false, 19 | OnlyLocal: false, 20 | OnlyProxy: false, 21 | NoCache: false, 22 | NoUpload: false, 23 | NeedMs: false, 24 | DefaultRoot: "root, / or other", 25 | CheckStatus: false, 26 | Alert: "", 27 | NoOverwriteUpload: false, 28 | } 29 | 30 | func init() { 31 | op.RegisterDriver(func() driver.Driver { 32 | return &Template{} 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /drivers/template/types.go: -------------------------------------------------------------------------------- 1 | package template 2 | -------------------------------------------------------------------------------- /drivers/template/util.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | // do others that not defined in Driver interface 4 | -------------------------------------------------------------------------------- /drivers/terabox/meta.go: -------------------------------------------------------------------------------- 1 | package terabox 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Cookie string `json:"cookie" required:"true"` 11 | //JsToken string `json:"js_token" type:"string" required:"true"` 12 | DownloadAPI string `json:"download_api" type:"select" options:"official,crack" default:"official"` 13 | OrderBy string `json:"order_by" type:"select" options:"name,time,size" default:"name"` 14 | OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` 15 | } 16 | 17 | var config = driver.Config{ 18 | Name: "Terabox", 19 | DefaultRoot: "/", 20 | } 21 | 22 | func init() { 23 | op.RegisterDriver(func() driver.Driver { 24 | return &Terabox{} 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /drivers/trainbit/meta.go: -------------------------------------------------------------------------------- 1 | package trainbit 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | AUSHELLPORTAL string `json:"AUSHELLPORTAL" required:"true"` 11 | ApiKey string `json:"apikey" required:"true"` 12 | } 13 | 14 | var config = driver.Config{ 15 | Name: "Trainbit", 16 | LocalSort: false, 17 | OnlyLocal: false, 18 | OnlyProxy: false, 19 | NoCache: false, 20 | NoUpload: false, 21 | NeedMs: false, 22 | DefaultRoot: "0_000", 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &Trainbit{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/trainbit/types.go: -------------------------------------------------------------------------------- 1 | package trainbit -------------------------------------------------------------------------------- /drivers/url_tree/meta.go: -------------------------------------------------------------------------------- 1 | package url_tree 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | // driver.RootPath 11 | // driver.RootID 12 | // define other 13 | UrlStructure string `json:"url_structure" type:"text" required:"true" default:"https://jsd.nn.ci/gh/alist-org/alist/README.md\nhttps://jsd.nn.ci/gh/alist-org/alist/README_cn.md\nfolder:\n CONTRIBUTING.md:1635:https://jsd.nn.ci/gh/alist-org/alist/CONTRIBUTING.md\n CODE_OF_CONDUCT.md:2093:https://jsd.nn.ci/gh/alist-org/alist/CODE_OF_CONDUCT.md" help:"structure:FolderName:\n [FileName:][FileSize:][Modified:]Url"` 14 | HeadSize bool `json:"head_size" type:"bool" default:"false" help:"Use head method to get file size, but it may be failed."` 15 | } 16 | 17 | var config = driver.Config{ 18 | Name: "UrlTree", 19 | LocalSort: true, 20 | OnlyLocal: false, 21 | OnlyProxy: false, 22 | NoCache: true, 23 | NoUpload: true, 24 | NeedMs: false, 25 | DefaultRoot: "", 26 | CheckStatus: true, 27 | Alert: "", 28 | NoOverwriteUpload: false, 29 | } 30 | 31 | func init() { 32 | op.RegisterDriver(func() driver.Driver { 33 | return &Urls{} 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /drivers/url_tree/types.go: -------------------------------------------------------------------------------- 1 | package url_tree 2 | 3 | // Node is a node in the folder tree 4 | type Node struct { 5 | Url string 6 | Name string 7 | Level int 8 | Modified int64 9 | Size int64 10 | Children []*Node 11 | } 12 | 13 | func (node *Node) getByPath(paths []string) *Node { 14 | if len(paths) == 0 || node == nil { 15 | return nil 16 | } 17 | if node.Name != paths[0] { 18 | return nil 19 | } 20 | if len(paths) == 1 { 21 | return node 22 | } 23 | for _, child := range node.Children { 24 | tmp := child.getByPath(paths[1:]) 25 | if tmp != nil { 26 | return tmp 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | func (node *Node) isFile() bool { 33 | return node.Url != "" 34 | } 35 | 36 | func (node *Node) calSize() int64 { 37 | if node.isFile() { 38 | return node.Size 39 | } 40 | var size int64 = 0 41 | for _, child := range node.Children { 42 | size += child.calSize() 43 | } 44 | node.Size = size 45 | return size 46 | } 47 | -------------------------------------------------------------------------------- /drivers/url_tree/urls_test.go: -------------------------------------------------------------------------------- 1 | package url_tree_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/alist-org/alist/v3/drivers/url_tree" 7 | ) 8 | 9 | func testTree() (*url_tree.Node, error) { 10 | text := `folder1: 11 | name1:https://url1 12 | http://url2 13 | folder2: 14 | http://url3 15 | http://url4 16 | http://url5 17 | folder3: 18 | http://url6 19 | http://url7 20 | http://url8` 21 | return url_tree.BuildTree(text, false) 22 | } 23 | 24 | func TestBuildTree(t *testing.T) { 25 | node, err := testTree() 26 | if err != nil { 27 | t.Errorf("failed to build tree: %+v", err) 28 | } else { 29 | t.Logf("tree: %+v", node) 30 | } 31 | } 32 | 33 | func TestGetNode(t *testing.T) { 34 | root, err := testTree() 35 | if err != nil { 36 | t.Errorf("failed to build tree: %+v", err) 37 | return 38 | } 39 | node := url_tree.GetNodeFromRootByPath(root, "/") 40 | if node != root { 41 | t.Errorf("got wrong node: %+v", node) 42 | } 43 | url3 := url_tree.GetNodeFromRootByPath(root, "/folder1/folder2/url3") 44 | if url3 != root.Children[0].Children[2].Children[0] { 45 | t.Errorf("got wrong node: %+v", url3) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /drivers/uss/meta.go: -------------------------------------------------------------------------------- 1 | package uss 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | Bucket string `json:"bucket" required:"true"` 11 | Endpoint string `json:"endpoint" required:"true"` 12 | OperatorName string `json:"operator_name" required:"true"` 13 | OperatorPassword string `json:"operator_password" required:"true"` 14 | AntiTheftChainToken string `json:"anti_theft_chain_token" required:"false" default:""` 15 | //CustomHost string `json:"custom_host"` //Endpoint与CustomHost作用相同,去除 16 | SignURLExpire int `json:"sign_url_expire" type:"number" default:"4"` 17 | } 18 | 19 | var config = driver.Config{ 20 | Name: "USS", 21 | LocalSort: true, 22 | DefaultRoot: "/", 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &USS{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/uss/types.go: -------------------------------------------------------------------------------- 1 | package uss 2 | -------------------------------------------------------------------------------- /drivers/uss/util.go: -------------------------------------------------------------------------------- 1 | package uss 2 | 3 | import "strings" 4 | 5 | // do others that not defined in Driver interface 6 | 7 | func getKey(path string, dir bool) string { 8 | path = strings.TrimPrefix(path, "/") 9 | if dir { 10 | path += "/" 11 | } 12 | return path 13 | } 14 | -------------------------------------------------------------------------------- /drivers/virtual/meta.go: -------------------------------------------------------------------------------- 1 | package virtual 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootPath 10 | NumFile int `json:"num_file" type:"number" default:"30" required:"true"` 11 | NumFolder int `json:"num_folder" type:"number" default:"30" required:"true"` 12 | MaxFileSize int64 `json:"max_file_size" type:"number" default:"1073741824" required:"true"` 13 | MinFileSize int64 `json:"min_file_size" type:"number" default:"1048576" required:"true"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "Virtual", 18 | OnlyLocal: true, 19 | LocalSort: true, 20 | NeedMs: true, 21 | //NoCache: true, 22 | } 23 | 24 | func init() { 25 | op.RegisterDriver(func() driver.Driver { 26 | return &Virtual{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /drivers/virtual/util.go: -------------------------------------------------------------------------------- 1 | package virtual 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/alist-org/alist/v3/internal/model" 7 | "github.com/alist-org/alist/v3/pkg/utils/random" 8 | ) 9 | 10 | func (d *Virtual) genObj(dir bool) model.Obj { 11 | obj := &model.Object{ 12 | Name: random.String(10), 13 | Size: 0, 14 | IsFolder: true, 15 | Modified: time.Now(), 16 | } 17 | if !dir { 18 | obj.Size = random.RangeInt64(d.MinFileSize, d.MaxFileSize) 19 | obj.IsFolder = false 20 | } 21 | return obj 22 | } 23 | -------------------------------------------------------------------------------- /drivers/vtencent/meta.go: -------------------------------------------------------------------------------- 1 | package vtencent 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | driver.RootID 10 | Cookie string `json:"cookie" required:"true"` 11 | TfUid string `json:"tf_uid"` 12 | OrderBy string `json:"order_by" type:"select" options:"Name,Size,UpdateTime,CreatTime"` 13 | OrderDirection string `json:"order_direction" type:"select" options:"Asc,Desc"` 14 | } 15 | 16 | type Conf struct { 17 | ua string 18 | referer string 19 | origin string 20 | } 21 | 22 | func init() { 23 | op.RegisterDriver(func() driver.Driver { 24 | return &Vtencent{ 25 | config: driver.Config{ 26 | Name: "VTencent", 27 | OnlyProxy: true, 28 | OnlyLocal: false, 29 | DefaultRoot: "9", 30 | NoOverwriteUpload: true, 31 | }, 32 | conf: Conf{ 33 | ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch", 34 | referer: "https://app.v.tencent.com/", 35 | origin: "https://app.v.tencent.com", 36 | }, 37 | } 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /drivers/vtencent/signature.go: -------------------------------------------------------------------------------- 1 | package vtencent 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "encoding/hex" 7 | ) 8 | 9 | func QSignatureKey(timeKey string, signPath string, key string) string { 10 | signKey := hmac.New(sha1.New, []byte(key)) 11 | signKey.Write([]byte(timeKey)) 12 | signKeyBytes := signKey.Sum(nil) 13 | signKeyHex := hex.EncodeToString(signKeyBytes) 14 | sha := sha1.New() 15 | sha.Write([]byte(signPath)) 16 | shaBytes := sha.Sum(nil) 17 | shaHex := hex.EncodeToString(shaBytes) 18 | 19 | O := "sha1\n" + timeKey + "\n" + shaHex + "\n" 20 | dataSignKey := hmac.New(sha1.New, []byte(signKeyHex)) 21 | dataSignKey.Write([]byte(O)) 22 | dataSignKeyBytes := dataSignKey.Sum(nil) 23 | dataSignKeyHex := hex.EncodeToString(dataSignKeyBytes) 24 | return dataSignKeyHex 25 | } 26 | 27 | func QTwoSignatureKey(timeKey string, key string) string { 28 | signKey := hmac.New(sha1.New, []byte(key)) 29 | signKey.Write([]byte(timeKey)) 30 | signKeyBytes := signKey.Sum(nil) 31 | signKeyHex := hex.EncodeToString(signKeyBytes) 32 | return signKeyHex 33 | } 34 | -------------------------------------------------------------------------------- /drivers/webdav/meta.go: -------------------------------------------------------------------------------- 1 | package webdav 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | Vendor string `json:"vendor" type:"select" options:"sharepoint,other" default:"other"` 10 | Address string `json:"address" required:"true"` 11 | Username string `json:"username" required:"true"` 12 | Password string `json:"password" required:"true"` 13 | driver.RootPath 14 | TlsInsecureSkipVerify bool `json:"tls_insecure_skip_verify" default:"false"` 15 | } 16 | 17 | var config = driver.Config{ 18 | Name: "WebDav", 19 | LocalSort: true, 20 | OnlyProxy: true, 21 | DefaultRoot: "/", 22 | } 23 | 24 | func init() { 25 | op.RegisterDriver(func() driver.Driver { 26 | return &WebDav{} 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /drivers/webdav/odrvcookie/cookie.go: -------------------------------------------------------------------------------- 1 | package odrvcookie 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/alist-org/alist/v3/pkg/cookie" 7 | ) 8 | 9 | //type SpCookie struct { 10 | // Cookie string 11 | // expire time.Time 12 | //} 13 | // 14 | //func (sp SpCookie) IsExpire() bool { 15 | // return time.Now().After(sp.expire) 16 | //} 17 | // 18 | //var cookiesMap = struct { 19 | // sync.Mutex 20 | // m map[string]*SpCookie 21 | //}{m: make(map[string]*SpCookie)} 22 | 23 | func GetCookie(username, password, siteUrl string) (string, error) { 24 | //cookiesMap.Lock() 25 | //defer cookiesMap.Unlock() 26 | //spCookie, ok := cookiesMap.m[username] 27 | //if ok { 28 | // if !spCookie.IsExpire() { 29 | // log.Debugln("sp use old cookie.") 30 | // return spCookie.Cookie, nil 31 | // } 32 | //} 33 | //log.Debugln("fetch new cookie") 34 | ca := New(username, password, siteUrl) 35 | tokenConf, err := ca.Cookies() 36 | if err != nil { 37 | return "", err 38 | } 39 | return cookie.ToString([]*http.Cookie{&tokenConf.RtFa, &tokenConf.FedAuth}), nil 40 | //spCookie = &SpCookie{ 41 | // Cookie: cookie.ToString([]*http.Cookie{&tokenConf.RtFa, &tokenConf.FedAuth}), 42 | // expire: time.Now().Add(time.Hour * 12), 43 | //} 44 | //cookiesMap.m[username] = spCookie 45 | //return spCookie.Cookie, nil 46 | } 47 | -------------------------------------------------------------------------------- /drivers/webdav/types.go: -------------------------------------------------------------------------------- 1 | package webdav 2 | -------------------------------------------------------------------------------- /drivers/webdav/util.go: -------------------------------------------------------------------------------- 1 | package webdav 2 | 3 | import ( 4 | "crypto/tls" 5 | "net/http" 6 | "net/http/cookiejar" 7 | 8 | "github.com/alist-org/alist/v3/drivers/webdav/odrvcookie" 9 | "github.com/alist-org/alist/v3/internal/model" 10 | "github.com/alist-org/alist/v3/pkg/gowebdav" 11 | ) 12 | 13 | // do others that not defined in Driver interface 14 | 15 | func (d *WebDav) isSharepoint() bool { 16 | return d.Vendor == "sharepoint" 17 | } 18 | 19 | func (d *WebDav) setClient() error { 20 | c := gowebdav.NewClient(d.Address, d.Username, d.Password) 21 | c.SetTransport(&http.Transport{ 22 | Proxy: http.ProxyFromEnvironment, 23 | TLSClientConfig: &tls.Config{InsecureSkipVerify: d.TlsInsecureSkipVerify}, 24 | }) 25 | if d.isSharepoint() { 26 | cookie, err := odrvcookie.GetCookie(d.Username, d.Password, d.Address) 27 | if err == nil { 28 | c.SetInterceptor(func(method string, rq *http.Request) { 29 | rq.Header.Del("Authorization") 30 | rq.Header.Set("Cookie", cookie) 31 | }) 32 | } else { 33 | return err 34 | } 35 | } else { 36 | cookieJar, err := cookiejar.New(nil) 37 | if err == nil { 38 | c.SetJar(cookieJar) 39 | } else { 40 | return err 41 | } 42 | } 43 | d.client = c 44 | return nil 45 | } 46 | 47 | func getPath(obj model.Obj) string { 48 | if obj.IsDir() { 49 | return obj.GetPath() + "/" 50 | } 51 | return obj.GetPath() 52 | } 53 | -------------------------------------------------------------------------------- /drivers/weiyun/meta.go: -------------------------------------------------------------------------------- 1 | package weiyun 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | RootFolderID string `json:"root_folder_id"` 10 | Cookies string `json:"cookies" required:"true"` 11 | OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at" default:"name"` 12 | OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` 13 | UploadThread string `json:"upload_thread" default:"4" help:"4<=thread<=32"` 14 | } 15 | 16 | var config = driver.Config{ 17 | Name: "WeiYun", 18 | LocalSort: false, 19 | OnlyProxy: true, 20 | CheckStatus: true, 21 | Alert: "", 22 | NoOverwriteUpload: false, 23 | } 24 | 25 | func init() { 26 | op.RegisterDriver(func() driver.Driver { 27 | return &WeiYun{} 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /drivers/wopan/meta.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | // Usually one of two 10 | driver.RootID 11 | // define other 12 | RefreshToken string `json:"refresh_token" required:"true"` 13 | FamilyID string `json:"family_id" help:"Keep it empty if you want to use your personal drive"` 14 | SortRule string `json:"sort_rule" type:"select" options:"name_asc,name_desc,time_asc,time_desc,size_asc,size_desc" default:"name_asc"` 15 | 16 | AccessToken string `json:"access_token"` 17 | } 18 | 19 | var config = driver.Config{ 20 | Name: "WoPan", 21 | LocalSort: false, 22 | OnlyLocal: false, 23 | OnlyProxy: false, 24 | NoCache: false, 25 | NoUpload: false, 26 | NeedMs: false, 27 | DefaultRoot: "0", 28 | CheckStatus: false, 29 | Alert: "", 30 | NoOverwriteUpload: true, 31 | } 32 | 33 | func init() { 34 | op.RegisterDriver(func() driver.Driver { 35 | return &Wopan{} 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /drivers/wopan/types.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/model" 5 | "github.com/xhofe/wopan-sdk-go" 6 | ) 7 | 8 | type Object struct { 9 | model.ObjThumb 10 | FID string 11 | } 12 | 13 | func fileToObj(file wopan.File) (model.Obj, error) { 14 | t, err := getTime(file.CreateTime) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return &Object{ 19 | ObjThumb: model.ObjThumb{ 20 | Object: model.Object{ 21 | ID: file.Id, 22 | //Path: "", 23 | Name: file.Name, 24 | Size: file.Size, 25 | Modified: t, 26 | IsFolder: file.Type == 0, 27 | }, 28 | Thumbnail: model.Thumbnail{ 29 | Thumbnail: file.ThumbUrl, 30 | }, 31 | }, 32 | FID: file.Fid, 33 | }, nil 34 | } 35 | -------------------------------------------------------------------------------- /drivers/wopan/util.go: -------------------------------------------------------------------------------- 1 | package template 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/xhofe/wopan-sdk-go" 7 | ) 8 | 9 | // do others that not defined in Driver interface 10 | 11 | func (d *Wopan) getSortRule() int { 12 | switch d.SortRule { 13 | case "name_asc": 14 | return wopan.SortNameAsc 15 | case "name_desc": 16 | return wopan.SortNameDesc 17 | case "time_asc": 18 | return wopan.SortTimeAsc 19 | case "time_desc": 20 | return wopan.SortTimeDesc 21 | case "size_asc": 22 | return wopan.SortSizeAsc 23 | case "size_desc": 24 | return wopan.SortSizeDesc 25 | default: 26 | return wopan.SortNameAsc 27 | } 28 | } 29 | 30 | func (d *Wopan) getSpaceType() string { 31 | if d.FamilyID == "" { 32 | return wopan.SpaceTypePersonal 33 | } 34 | return wopan.SpaceTypeFamily 35 | } 36 | 37 | // 20230607214351 38 | func getTime(str string) (time.Time, error) { 39 | return time.Parse("20060102150405", str) 40 | } 41 | -------------------------------------------------------------------------------- /drivers/yandex_disk/meta.go: -------------------------------------------------------------------------------- 1 | package yandex_disk 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/driver" 5 | "github.com/alist-org/alist/v3/internal/op" 6 | ) 7 | 8 | type Addition struct { 9 | RefreshToken string `json:"refresh_token" required:"true"` 10 | OrderBy string `json:"order_by" type:"select" options:"name,path,created,modified,size" default:"name"` 11 | OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"` 12 | driver.RootPath 13 | ClientID string `json:"client_id" required:"true" default:"a78d5a69054042fa936f6c77f9a0ae8b"` 14 | ClientSecret string `json:"client_secret" required:"true" default:"9c119bbb04b346d2a52aa64401936b2b"` 15 | } 16 | 17 | var config = driver.Config{ 18 | Name: "YandexDisk", 19 | DefaultRoot: "/", 20 | } 21 | 22 | func init() { 23 | op.RegisterDriver(func() driver.Driver { 24 | return &YandexDisk{} 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | chown -R ${PUID}:${PGID} /opt/alist/ 4 | 5 | umask ${UMASK} 6 | 7 | if [ "$1" = "version" ]; then 8 | ./alist version 9 | else 10 | 11 | if [ "$RUN_ARIA2" = "true" ]; then 12 | chown -R ${PUID}:${PGID} /opt/aria2/ 13 | exec su-exec ${PUID}:${PGID} nohup aria2c \ 14 | --enable-rpc \ 15 | --rpc-allow-origin-all \ 16 | --conf-path=/opt/aria2/.aria2/aria2.conf \ 17 | >/dev/null 2>&1 & 18 | fi 19 | exec su-exec ${PUID}:${PGID} ./alist server --no-prefix 20 | fi 21 | -------------------------------------------------------------------------------- /internal/authn/authn.go: -------------------------------------------------------------------------------- 1 | package authn 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "net/url" 7 | 8 | "github.com/alist-org/alist/v3/internal/conf" 9 | "github.com/alist-org/alist/v3/internal/setting" 10 | "github.com/alist-org/alist/v3/server/common" 11 | "github.com/go-webauthn/webauthn/webauthn" 12 | ) 13 | 14 | func NewAuthnInstance(r *http.Request) (*webauthn.WebAuthn, error) { 15 | siteUrl, err := url.Parse(common.GetApiUrl(r)) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return webauthn.New(&webauthn.Config{ 20 | RPDisplayName: setting.GetStr(conf.SiteTitle), 21 | RPID: siteUrl.Hostname(), 22 | //RPOrigin: siteUrl.String(), 23 | RPOrigins: []string{fmt.Sprintf("%s://%s", siteUrl.Scheme, siteUrl.Host)}, 24 | // RPOrigin: "http://localhost:5173" 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /internal/bootstrap/data/data.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "github.com/alist-org/alist/v3/cmd/flags" 4 | 5 | func InitData() { 6 | initUser() 7 | initSettings() 8 | initTasks() 9 | if flags.Dev { 10 | initDevData() 11 | initDevDo() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /internal/bootstrap/data/dev.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/alist-org/alist/v3/cmd/flags" 7 | "github.com/alist-org/alist/v3/internal/db" 8 | "github.com/alist-org/alist/v3/internal/message" 9 | "github.com/alist-org/alist/v3/internal/model" 10 | "github.com/alist-org/alist/v3/internal/op" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | func initDevData() { 15 | _, err := op.CreateStorage(context.Background(), model.Storage{ 16 | MountPath: "/", 17 | Order: 0, 18 | Driver: "Local", 19 | Status: "", 20 | Addition: `{"root_folder_path":"."}`, 21 | }) 22 | if err != nil { 23 | log.Fatalf("failed to create storage: %+v", err) 24 | } 25 | err = db.CreateUser(&model.User{ 26 | Username: "Noah", 27 | Password: "hsu", 28 | BasePath: "/data", 29 | Role: 0, 30 | Permission: 512, 31 | }) 32 | if err != nil { 33 | log.Fatalf("failed to create user: %+v", err) 34 | } 35 | } 36 | 37 | func initDevDo() { 38 | if flags.Dev { 39 | go func() { 40 | err := message.GetMessenger().WaitSend(message.Message{ 41 | Type: "string", 42 | Content: "dev mode", 43 | }, 10) 44 | if err != nil { 45 | log.Debugf("%+v", err) 46 | } 47 | m, err := message.GetMessenger().WaitReceive(10) 48 | if err != nil { 49 | log.Debugf("%+v", err) 50 | } else { 51 | log.Debugf("received: %+v", m) 52 | } 53 | }() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /internal/bootstrap/data/task.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/db" 5 | "github.com/alist-org/alist/v3/internal/model" 6 | ) 7 | 8 | var initialTaskItems []model.TaskItem 9 | 10 | func initTasks() { 11 | InitialTasks() 12 | 13 | for i := range initialTaskItems { 14 | item := &initialTaskItems[i] 15 | taskitem, _ := db.GetTaskDataByType(item.Key) 16 | if taskitem == nil { 17 | db.CreateTaskData(item) 18 | } 19 | } 20 | } 21 | 22 | func InitialTasks() []model.TaskItem { 23 | initialTaskItems = []model.TaskItem{ 24 | {Key: "copy", PersistData: "[]"}, 25 | {Key: "download", PersistData: "[]"}, 26 | {Key: "transfer", PersistData: "[]"}, 27 | } 28 | return initialTaskItems 29 | } 30 | -------------------------------------------------------------------------------- /internal/bootstrap/index.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/search" 5 | log "github.com/sirupsen/logrus" 6 | ) 7 | 8 | func InitIndex() { 9 | progress, err := search.Progress() 10 | if err != nil { 11 | log.Errorf("init index error: %+v", err) 12 | return 13 | } 14 | if !progress.IsDone { 15 | progress.IsDone = true 16 | search.WriteProgress(progress) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/bootstrap/offline_download.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/offline_download/tool" 5 | "github.com/alist-org/alist/v3/pkg/utils" 6 | ) 7 | 8 | func InitOfflineDownloadTools() { 9 | for k, v := range tool.Tools { 10 | res, err := v.Init() 11 | if err != nil { 12 | utils.Log.Warnf("init tool %s failed: %s", k, err) 13 | } else { 14 | utils.Log.Infof("init tool %s success: %s", k, res) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/bootstrap/storage.go: -------------------------------------------------------------------------------- 1 | package bootstrap 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/alist-org/alist/v3/internal/conf" 7 | "github.com/alist-org/alist/v3/internal/db" 8 | "github.com/alist-org/alist/v3/internal/model" 9 | "github.com/alist-org/alist/v3/internal/op" 10 | "github.com/alist-org/alist/v3/pkg/utils" 11 | ) 12 | 13 | func LoadStorages() { 14 | storages, err := db.GetEnabledStorages() 15 | if err != nil { 16 | utils.Log.Fatalf("failed get enabled storages: %+v", err) 17 | } 18 | go func(storages []model.Storage) { 19 | for i := range storages { 20 | err := op.LoadStorage(context.Background(), storages[i]) 21 | if err != nil { 22 | utils.Log.Errorf("failed get enabled storages: %+v", err) 23 | } else { 24 | utils.Log.Infof("success load storage: [%s], driver: [%s]", 25 | storages[i].MountPath, storages[i].Driver) 26 | } 27 | } 28 | conf.StoragesLoaded = true 29 | }(storages) 30 | } 31 | -------------------------------------------------------------------------------- /internal/conf/var.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "net/url" 5 | "regexp" 6 | ) 7 | 8 | var ( 9 | BuiltAt string 10 | GoVersion string 11 | GitAuthor string 12 | GitCommit string 13 | Version string = "dev" 14 | WebVersion string 15 | ) 16 | 17 | var ( 18 | Conf *Config 19 | URL *url.URL 20 | ) 21 | 22 | var SlicesMap = make(map[string][]string) 23 | var FilenameCharMap = make(map[string]string) 24 | var PrivacyReg []*regexp.Regexp 25 | 26 | var ( 27 | // StoragesLoaded loaded success if empty 28 | StoragesLoaded = false 29 | ) 30 | var ( 31 | RawIndexHtml string 32 | ManageHtml string 33 | IndexHtml string 34 | ) 35 | -------------------------------------------------------------------------------- /internal/db/db.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | 6 | "github.com/alist-org/alist/v3/internal/conf" 7 | "github.com/alist-org/alist/v3/internal/model" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | var db *gorm.DB 12 | 13 | func Init(d *gorm.DB) { 14 | db = d 15 | err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem)) 16 | if err != nil { 17 | log.Fatalf("failed migrate database: %s", err.Error()) 18 | } 19 | } 20 | 21 | func AutoMigrate(dst ...interface{}) error { 22 | var err error 23 | if conf.Conf.Database.Type == "mysql" { 24 | err = db.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").AutoMigrate(dst...) 25 | } else { 26 | err = db.AutoMigrate(dst...) 27 | } 28 | return err 29 | } 30 | 31 | func GetDb() *gorm.DB { 32 | return db 33 | } 34 | 35 | func Close() { 36 | log.Info("closing db") 37 | sqlDB, err := db.DB() 38 | if err != nil { 39 | log.Errorf("failed to get db: %s", err.Error()) 40 | return 41 | } 42 | err = sqlDB.Close() 43 | if err != nil { 44 | log.Errorf("failed to close db: %s", err.Error()) 45 | return 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /internal/db/meta.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/model" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | func GetMetaByPath(path string) (*model.Meta, error) { 9 | meta := model.Meta{Path: path} 10 | if err := db.Where(meta).First(&meta).Error; err != nil { 11 | return nil, errors.Wrapf(err, "failed select meta") 12 | } 13 | return &meta, nil 14 | } 15 | 16 | func GetMetaById(id uint) (*model.Meta, error) { 17 | var u model.Meta 18 | if err := db.First(&u, id).Error; err != nil { 19 | return nil, errors.Wrapf(err, "failed get old meta") 20 | } 21 | return &u, nil 22 | } 23 | 24 | func CreateMeta(u *model.Meta) error { 25 | return errors.WithStack(db.Create(u).Error) 26 | } 27 | 28 | func UpdateMeta(u *model.Meta) error { 29 | return errors.WithStack(db.Save(u).Error) 30 | } 31 | 32 | func GetMetas(pageIndex, pageSize int) (metas []model.Meta, count int64, err error) { 33 | metaDB := db.Model(&model.Meta{}) 34 | if err = metaDB.Count(&count).Error; err != nil { 35 | return nil, 0, errors.Wrapf(err, "failed get metas count") 36 | } 37 | if err = metaDB.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&metas).Error; err != nil { 38 | return nil, 0, errors.Wrapf(err, "failed get find metas") 39 | } 40 | return metas, count, nil 41 | } 42 | 43 | func DeleteMetaById(id uint) error { 44 | return errors.WithStack(db.Delete(&model.Meta{}, id).Error) 45 | } 46 | -------------------------------------------------------------------------------- /internal/db/tasks.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/model" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | func GetTaskDataByType(type_s string) (*model.TaskItem, error) { 9 | task := model.TaskItem{Key: type_s} 10 | if err := db.Where(task).First(&task).Error; err != nil { 11 | return nil, errors.Wrapf(err, "failed find task") 12 | } 13 | return &task, nil 14 | } 15 | 16 | func UpdateTaskData(t *model.TaskItem) error { 17 | return errors.WithStack(db.Model(&model.TaskItem{}).Where("key = ?", t.Key).Update("persist_data", t.PersistData).Error) 18 | } 19 | 20 | func CreateTaskData(t *model.TaskItem) error { 21 | return errors.WithStack(db.Create(t).Error) 22 | } 23 | 24 | func GetTaskDataFunc(type_s string, enabled bool) func() ([]byte, error) { 25 | if !enabled { 26 | return nil 27 | } 28 | task, err := GetTaskDataByType(type_s) 29 | if err != nil { 30 | return nil 31 | } 32 | return func() ([]byte, error) { 33 | return []byte(task.PersistData), nil 34 | } 35 | } 36 | 37 | func UpdateTaskDataFunc(type_s string, enabled bool) func([]byte) error { 38 | if !enabled { 39 | return nil 40 | } 41 | return func(data []byte) error { 42 | s := string(data) 43 | if s == "null" || s == "" { 44 | s = "[]" 45 | } 46 | return UpdateTaskData(&model.TaskItem{Key: type_s, PersistData: s}) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /internal/db/util.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/alist-org/alist/v3/internal/conf" 7 | ) 8 | 9 | func columnName(name string) string { 10 | if conf.Conf.Database.Type == "postgres" { 11 | return fmt.Sprintf(`"%s"`, name) 12 | } 13 | return fmt.Sprintf("`%s`", name) 14 | } 15 | -------------------------------------------------------------------------------- /internal/driver/config.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | type Config struct { 4 | Name string `json:"name"` 5 | LocalSort bool `json:"local_sort"` 6 | OnlyLocal bool `json:"only_local"` 7 | OnlyProxy bool `json:"only_proxy"` 8 | NoCache bool `json:"no_cache"` 9 | NoUpload bool `json:"no_upload"` 10 | NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code 11 | DefaultRoot string `json:"default_root"` 12 | CheckStatus bool `json:"-"` 13 | Alert string `json:"alert"` //info,success,warning,danger 14 | NoOverwriteUpload bool `json:"-"` // whether to support overwrite upload 15 | ProxyRangeOption bool `json:"-"` 16 | DeafultProxy bool `json:"default_proxy"` 17 | DeafultWebDavPolicy string `json:"default_webdav_policy"` 18 | } 19 | 20 | func (c Config) MustProxy() bool { 21 | return c.OnlyProxy || c.OnlyLocal 22 | } 23 | -------------------------------------------------------------------------------- /internal/driver/item.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | type Additional interface{} 4 | 5 | type Select string 6 | 7 | type Item struct { 8 | Name string `json:"name"` 9 | Type string `json:"type"` 10 | Default string `json:"default"` 11 | Options string `json:"options"` 12 | Required bool `json:"required"` 13 | Help string `json:"help"` 14 | } 15 | 16 | type Info struct { 17 | Common []Item `json:"common"` 18 | Additional []Item `json:"additional"` 19 | Config Config `json:"config"` 20 | } 21 | 22 | type IRootPath interface { 23 | GetRootPath() string 24 | } 25 | 26 | type IRootId interface { 27 | GetRootId() string 28 | } 29 | 30 | type RootPath struct { 31 | RootFolderPath string `json:"root_folder_path"` 32 | } 33 | 34 | type RootID struct { 35 | RootFolderID string `json:"root_folder_id"` 36 | } 37 | 38 | func (r RootPath) GetRootPath() string { 39 | return r.RootFolderPath 40 | } 41 | 42 | func (r *RootPath) SetRootPath(path string) { 43 | r.RootFolderPath = path 44 | } 45 | 46 | func (r RootID) GetRootId() string { 47 | return r.RootFolderID 48 | } 49 | -------------------------------------------------------------------------------- /internal/errs/driver.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import "errors" 4 | 5 | var ( 6 | EmptyToken = errors.New("empty token") 7 | ) 8 | -------------------------------------------------------------------------------- /internal/errs/errors.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | pkgerr "github.com/pkg/errors" 8 | ) 9 | 10 | var ( 11 | NotImplement = errors.New("not implement") 12 | NotSupport = errors.New("not support") 13 | RelativePath = errors.New("access using relative path is not allowed") 14 | 15 | MoveBetweenTwoStorages = errors.New("can't move files between two storages, try to copy") 16 | UploadNotSupported = errors.New("upload not supported") 17 | 18 | MetaNotFound = errors.New("meta not found") 19 | StorageNotFound = errors.New("storage not found") 20 | StreamIncomplete = errors.New("upload/download stream incomplete, possible network issue") 21 | StreamPeekFail = errors.New("StreamPeekFail") 22 | ) 23 | 24 | // NewErr wrap constant error with an extra message 25 | // use errors.Is(err1, StorageNotFound) to check if err belongs to any internal error 26 | func NewErr(err error, format string, a ...any) error { 27 | return fmt.Errorf("%w; %s", err, fmt.Sprintf(format, a...)) 28 | } 29 | 30 | func IsNotFoundError(err error) bool { 31 | return errors.Is(pkgerr.Cause(err), ObjectNotFound) || errors.Is(pkgerr.Cause(err), StorageNotFound) 32 | } 33 | 34 | func IsNotSupportError(err error) bool { 35 | return errors.Is(pkgerr.Cause(err), NotSupport) 36 | } 37 | 38 | func IsNotImplement(err error) bool { 39 | return errors.Is(pkgerr.Cause(err), NotImplement) 40 | } 41 | -------------------------------------------------------------------------------- /internal/errs/errors_test.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "errors" 5 | pkgerr "github.com/pkg/errors" 6 | "testing" 7 | ) 8 | 9 | func TestErrs(t *testing.T) { 10 | 11 | err1 := NewErr(StorageNotFound, "please add a storage first") 12 | t.Logf("err1: %s", err1) 13 | if !errors.Is(err1, StorageNotFound) { 14 | t.Errorf("failed, expect %s is %s", err1, StorageNotFound) 15 | } 16 | if !errors.Is(pkgerr.Cause(err1), StorageNotFound) { 17 | t.Errorf("failed, expect %s is %s", err1, StorageNotFound) 18 | } 19 | err2 := pkgerr.WithMessage(err1, "failed get storage") 20 | t.Logf("err2: %s", err2) 21 | if !errors.Is(err2, StorageNotFound) { 22 | t.Errorf("failed, expect %s is %s", err2, StorageNotFound) 23 | } 24 | if !errors.Is(pkgerr.Cause(err2), StorageNotFound) { 25 | t.Errorf("failed, expect %s is %s", err2, StorageNotFound) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /internal/errs/object.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import ( 4 | "errors" 5 | 6 | pkgerr "github.com/pkg/errors" 7 | ) 8 | 9 | var ( 10 | ObjectNotFound = errors.New("object not found") 11 | NotFolder = errors.New("not a folder") 12 | NotFile = errors.New("not a file") 13 | ) 14 | 15 | func IsObjectNotFound(err error) bool { 16 | return errors.Is(pkgerr.Cause(err), ObjectNotFound) 17 | } 18 | -------------------------------------------------------------------------------- /internal/errs/operate.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import "errors" 4 | 5 | var ( 6 | PermissionDenied = errors.New("permission denied") 7 | ) 8 | -------------------------------------------------------------------------------- /internal/errs/search.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import "fmt" 4 | 5 | var ( 6 | SearchNotAvailable = fmt.Errorf("search not available") 7 | ) 8 | -------------------------------------------------------------------------------- /internal/errs/user.go: -------------------------------------------------------------------------------- 1 | package errs 2 | 3 | import "errors" 4 | 5 | var ( 6 | EmptyUsername = errors.New("username is empty") 7 | EmptyPassword = errors.New("password is empty") 8 | WrongPassword = errors.New("password is incorrect") 9 | DeleteAdminOrGuest = errors.New("cannot delete admin or guest") 10 | ) 11 | -------------------------------------------------------------------------------- /internal/fs/get.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "context" 5 | stdpath "path" 6 | "time" 7 | 8 | "github.com/alist-org/alist/v3/internal/model" 9 | "github.com/alist-org/alist/v3/internal/op" 10 | "github.com/alist-org/alist/v3/pkg/utils" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | func get(ctx context.Context, path string) (model.Obj, error) { 15 | path = utils.FixAndCleanPath(path) 16 | // maybe a virtual file 17 | if path != "/" { 18 | virtualFiles := op.GetStorageVirtualFilesByPath(stdpath.Dir(path)) 19 | for _, f := range virtualFiles { 20 | if f.GetName() == stdpath.Base(path) { 21 | return f, nil 22 | } 23 | } 24 | } 25 | storage, actualPath, err := op.GetStorageAndActualPath(path) 26 | if err != nil { 27 | // if there are no storage prefix with path, maybe root folder 28 | if path == "/" { 29 | return &model.Object{ 30 | Name: "root", 31 | Size: 0, 32 | Modified: time.Time{}, 33 | IsFolder: true, 34 | }, nil 35 | } 36 | return nil, errors.WithMessage(err, "failed get storage") 37 | } 38 | return op.Get(ctx, storage, actualPath) 39 | } 40 | -------------------------------------------------------------------------------- /internal/fs/link.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "context" 5 | "strings" 6 | 7 | "github.com/alist-org/alist/v3/internal/model" 8 | "github.com/alist-org/alist/v3/internal/op" 9 | "github.com/alist-org/alist/v3/server/common" 10 | "github.com/gin-gonic/gin" 11 | "github.com/pkg/errors" 12 | ) 13 | 14 | func link(ctx context.Context, path string, args model.LinkArgs) (*model.Link, model.Obj, error) { 15 | storage, actualPath, err := op.GetStorageAndActualPath(path) 16 | if err != nil { 17 | return nil, nil, errors.WithMessage(err, "failed get storage") 18 | } 19 | l, obj, err := op.Link(ctx, storage, actualPath, args) 20 | if err != nil { 21 | return nil, nil, errors.WithMessage(err, "failed link") 22 | } 23 | if l.URL != "" && !strings.HasPrefix(l.URL, "http://") && !strings.HasPrefix(l.URL, "https://") { 24 | if c, ok := ctx.(*gin.Context); ok { 25 | l.URL = common.GetApiUrl(c.Request) + l.URL 26 | } 27 | } 28 | return l, obj, nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/fs/walk.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "context" 5 | "path" 6 | "path/filepath" 7 | 8 | "github.com/alist-org/alist/v3/internal/model" 9 | "github.com/alist-org/alist/v3/internal/op" 10 | ) 11 | 12 | // WalkFS traverses filesystem fs starting at name up to depth levels. 13 | // 14 | // WalkFS will stop when current depth > `depth`. For each visited node, 15 | // WalkFS calls walkFn. If a visited file system node is a directory and 16 | // walkFn returns path.SkipDir, walkFS will skip traversal of this node. 17 | func WalkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn func(reqPath string, info model.Obj) error) error { 18 | // This implementation is based on Walk's code in the standard path/path package. 19 | walkFnErr := walkFn(name, info) 20 | if walkFnErr != nil { 21 | if info.IsDir() && walkFnErr == filepath.SkipDir { 22 | return nil 23 | } 24 | return walkFnErr 25 | } 26 | if !info.IsDir() || depth == 0 { 27 | return nil 28 | } 29 | meta, _ := op.GetNearestMeta(name) 30 | // Read directory names. 31 | objs, err := List(context.WithValue(ctx, "meta", meta), name, &ListArgs{}) 32 | if err != nil { 33 | return walkFnErr 34 | } 35 | for _, fileInfo := range objs { 36 | filename := path.Join(name, fileInfo.GetName()) 37 | if err := WalkFS(ctx, depth-1, filename, fileInfo, walkFn); err != nil { 38 | if err == filepath.SkipDir { 39 | break 40 | } 41 | return err 42 | } 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /internal/fuse/mount.go: -------------------------------------------------------------------------------- 1 | package fuse 2 | 3 | import "github.com/winfsp/cgofuse/fuse" 4 | 5 | func Mount(mountSrc, mountDst string, opts []string) { 6 | fs := &Fs{RootFolder: mountSrc} 7 | host := fuse.NewFileSystemHost(fs) 8 | go host.Mount(mountDst, opts) 9 | } 10 | -------------------------------------------------------------------------------- /internal/message/message.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | type Message struct { 4 | Type string `json:"type"` 5 | Content interface{} `json:"content"` 6 | } 7 | 8 | type Messenger interface { 9 | Send(Message) error 10 | Receive() (string, error) 11 | WaitSend(Message, int) error 12 | WaitReceive(int) (string, error) 13 | } 14 | 15 | func GetMessenger() Messenger { 16 | return HttpInstance 17 | } 18 | -------------------------------------------------------------------------------- /internal/message/ws.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | // TODO websocket implementation 4 | -------------------------------------------------------------------------------- /internal/model/file.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "io" 4 | 5 | // File is basic file level accessing interface 6 | type File interface { 7 | io.Reader 8 | io.ReaderAt 9 | io.Seeker 10 | io.Closer 11 | } 12 | 13 | type NopMFileIF interface { 14 | io.Reader 15 | io.ReaderAt 16 | io.Seeker 17 | } 18 | type NopMFile struct { 19 | NopMFileIF 20 | } 21 | 22 | func (NopMFile) Close() error { return nil } 23 | func NewNopMFile(r NopMFileIF) File { 24 | return NopMFile{r} 25 | } 26 | -------------------------------------------------------------------------------- /internal/model/meta.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Meta struct { 4 | ID uint `json:"id" gorm:"primaryKey"` 5 | Path string `json:"path" gorm:"unique" binding:"required"` 6 | Password string `json:"password"` 7 | PSub bool `json:"p_sub"` 8 | Write bool `json:"write"` 9 | WSub bool `json:"w_sub"` 10 | Hide string `json:"hide"` 11 | HSub bool `json:"h_sub"` 12 | Readme string `json:"readme"` 13 | RSub bool `json:"r_sub"` 14 | Header string `json:"header"` 15 | HeaderSub bool `json:"header_sub"` 16 | } 17 | -------------------------------------------------------------------------------- /internal/model/notify.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Bark struct { 4 | BarkPush string `json:"barkPush"` 5 | BarkIcon string `json:"barkIcon,omitempty"` // 可选字段 6 | BarkSound string `json:"barkSound,omitempty"` // 可选字段 7 | BarkGroup string `json:"barkGroup,omitempty"` // 可选字段 8 | BarkLevel string `json:"barkLevel,omitempty"` // 可选字段 9 | BarkUrl string `json:"barkUrl,omitempty"` // 可选字段 10 | } 11 | 12 | type Webhook struct { 13 | WebhookUrl string `json:"webhookUrl"` 14 | WebhookBody string `json:"webhookBody,omitempty"` // 可选字段 15 | WebhookHeaders string `json:"webhookHeaders,omitempty"` // 可选字段 16 | WebhookMethod string `json:"webhookMethod"` // 可选字段 17 | WebhookContentType string `json:"webhookContentType"` // 可选字段 18 | } 19 | -------------------------------------------------------------------------------- /internal/model/req.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type PageReq struct { 4 | Page int `json:"page" form:"page"` 5 | PerPage int `json:"per_page" form:"per_page"` 6 | } 7 | 8 | const MaxUint = ^uint(0) 9 | const MinUint = 0 10 | const MaxInt = int(MaxUint >> 1) 11 | const MinInt = -MaxInt - 1 12 | 13 | func (p *PageReq) Validate() { 14 | if p.Page < 1 { 15 | p.Page = 1 16 | } 17 | if p.PerPage < 1 { 18 | p.PerPage = MaxInt 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /internal/model/search.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type IndexProgress struct { 9 | ObjCount uint64 `json:"obj_count"` 10 | IsDone bool `json:"is_done"` 11 | LastDoneTime *time.Time `json:"last_done_time"` 12 | Error string `json:"error"` 13 | } 14 | 15 | type SearchReq struct { 16 | Parent string `json:"parent"` 17 | Keywords string `json:"keywords"` 18 | // 0 for all, 1 for dir, 2 for file 19 | Scope int `json:"scope"` 20 | PageReq 21 | } 22 | 23 | type SearchNode struct { 24 | Parent string `json:"parent" gorm:"index"` 25 | Name string `json:"name"` 26 | IsDir bool `json:"is_dir"` 27 | Size int64 `json:"size"` 28 | } 29 | 30 | func (p *SearchReq) Validate() error { 31 | if p.Page < 1 { 32 | return fmt.Errorf("page can't < 1") 33 | } 34 | if p.PerPage < 1 { 35 | return fmt.Errorf("per_page can't < 1") 36 | } 37 | return nil 38 | } 39 | 40 | func (s *SearchNode) Type() string { 41 | return "SearchNode" 42 | } 43 | -------------------------------------------------------------------------------- /internal/model/setting.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | const ( 4 | SINGLE = iota 5 | SITE 6 | STYLE 7 | PREVIEW 8 | GLOBAL 9 | OFFLINE_DOWNLOAD 10 | INDEX 11 | SSO 12 | LDAP 13 | S3 14 | NOTIFICATION 15 | ) 16 | 17 | const ( 18 | PUBLIC = iota 19 | PRIVATE 20 | READONLY 21 | DEPRECATED 22 | ) 23 | 24 | type SettingItem struct { 25 | Key string `json:"key" gorm:"primaryKey" binding:"required"` // unique key 26 | Value string `json:"value"` // value 27 | PreDefault string `json:"-" gorm:"-:all"` // deprecated value 28 | Help string `json:"help"` // help message 29 | Type string `json:"type"` // string, number, bool, select 30 | Options string `json:"options"` // values for select 31 | Group int `json:"group"` // use to group setting in frontend 32 | Flag int `json:"flag"` // 0 = public, 1 = private, 2 = readonly, 3 = deprecated, etc. 33 | } 34 | 35 | func (s SettingItem) IsDeprecated() bool { 36 | return s.Flag == DEPRECATED 37 | } 38 | -------------------------------------------------------------------------------- /internal/model/task.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type TaskItem struct { 4 | Key string `json:"key"` 5 | PersistData string `gorm:"type:text" json:"persist_data"` 6 | } 7 | -------------------------------------------------------------------------------- /internal/offline_download/all.go: -------------------------------------------------------------------------------- 1 | package offline_download 2 | 3 | import ( 4 | _ "github.com/alist-org/alist/v3/internal/offline_download/aria2" 5 | _ "github.com/alist-org/alist/v3/internal/offline_download/http" 6 | _ "github.com/alist-org/alist/v3/internal/offline_download/qbit" 7 | _ "github.com/alist-org/alist/v3/internal/offline_download/storage" 8 | ) 9 | -------------------------------------------------------------------------------- /internal/offline_download/http/util.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | "mime" 6 | ) 7 | 8 | func parseFilenameFromContentDisposition(contentDisposition string) (string, error) { 9 | if contentDisposition == "" { 10 | return "", fmt.Errorf("Content-Disposition is empty") 11 | } 12 | _, params, err := mime.ParseMediaType(contentDisposition) 13 | if err != nil { 14 | return "", err 15 | } 16 | filename := params["filename"] 17 | if filename == "" { 18 | return "", fmt.Errorf("filename not found in Content-Disposition: [%s]", contentDisposition) 19 | } 20 | return filename, nil 21 | } 22 | -------------------------------------------------------------------------------- /internal/offline_download/storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/errs" 5 | "github.com/alist-org/alist/v3/internal/model" 6 | "github.com/alist-org/alist/v3/internal/offline_download/tool" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | type Storage struct { 11 | } 12 | 13 | func (a *Storage) Run(task *tool.DownloadTask) error { 14 | return errs.NotSupport 15 | } 16 | 17 | func (a *Storage) Name() string { 18 | return "storage" 19 | } 20 | 21 | func (a *Storage) Items() []model.SettingItem { 22 | // qBittorrent settings 23 | return []model.SettingItem{} 24 | } 25 | 26 | func (a *Storage) Init() (string, error) { 27 | return "ok", nil 28 | } 29 | 30 | func (a *Storage) IsReady() bool { 31 | return true 32 | } 33 | 34 | func (a *Storage) AddURL(args *tool.AddUrlArgs) (string, error) { 35 | return "ok", nil 36 | } 37 | 38 | func (a *Storage) Remove(task *tool.DownloadTask) error { 39 | return errors.Errorf("Failed to Remove") 40 | } 41 | 42 | func (a *Storage) Status(task *tool.DownloadTask) (*tool.Status, error) { 43 | s := &tool.Status{} 44 | return s, nil 45 | } 46 | 47 | var _ tool.Tool = (*Storage)(nil) 48 | 49 | func init() { 50 | tool.Tools.Add(&Storage{}) 51 | } 52 | -------------------------------------------------------------------------------- /internal/offline_download/tool/all_test.go: -------------------------------------------------------------------------------- 1 | package tool_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/alist-org/alist/v3/internal/offline_download/tool" 7 | ) 8 | 9 | func TestGetFiles(t *testing.T) { 10 | files, err := tool.GetFiles("..") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | for _, file := range files { 15 | t.Log(file.Name, file.Size, file.Path, file.Modified) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/offline_download/tool/tools.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "fmt" 5 | "github.com/alist-org/alist/v3/internal/model" 6 | ) 7 | 8 | var ( 9 | Tools = make(ToolsManager) 10 | ) 11 | 12 | type ToolsManager map[string]Tool 13 | 14 | func (t ToolsManager) Get(name string) (Tool, error) { 15 | if tool, ok := t[name]; ok { 16 | return tool, nil 17 | } 18 | return nil, fmt.Errorf("tool %s not found", name) 19 | } 20 | 21 | func (t ToolsManager) Add(tool Tool) { 22 | t[tool.Name()] = tool 23 | } 24 | 25 | func (t ToolsManager) Names() []string { 26 | names := make([]string, 0, len(t)) 27 | for name := range t { 28 | names = append(names, name) 29 | } 30 | return names 31 | } 32 | 33 | func (t ToolsManager) Items() []model.SettingItem { 34 | var items []model.SettingItem 35 | for _, tool := range t { 36 | items = append(items, tool.Items()...) 37 | } 38 | return items 39 | } 40 | -------------------------------------------------------------------------------- /internal/offline_download/tool/util.go: -------------------------------------------------------------------------------- 1 | package tool 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | func GetFiles(dir string) ([]File, error) { 9 | var files []File 10 | err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 11 | if err != nil { 12 | return err 13 | } 14 | if !info.IsDir() { 15 | files = append(files, File{ 16 | Name: info.Name(), 17 | Size: info.Size(), 18 | Path: path, 19 | Modified: info.ModTime(), 20 | }) 21 | } 22 | return nil 23 | }) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return files, nil 28 | } 29 | 30 | func GetFile(path string) (File, error) { 31 | info, err := os.Stat(path) 32 | if err != nil { 33 | return File{}, err 34 | } 35 | return File{ 36 | Name: info.Name(), 37 | Size: info.Size(), 38 | Path: path, 39 | Modified: info.ModTime(), 40 | }, nil 41 | } 42 | -------------------------------------------------------------------------------- /internal/op/const.go: -------------------------------------------------------------------------------- 1 | package op 2 | 3 | const ( 4 | WORK = "work" 5 | DISABLED = "disabled" 6 | RootName = "root" 7 | ) 8 | -------------------------------------------------------------------------------- /internal/op/driver_test.go: -------------------------------------------------------------------------------- 1 | package op_test 2 | 3 | import ( 4 | "testing" 5 | 6 | _ "github.com/alist-org/alist/v3/drivers" 7 | "github.com/alist-org/alist/v3/internal/op" 8 | ) 9 | 10 | func TestDriverItemsMap(t *testing.T) { 11 | itemsMap := op.GetDriverInfoMap() 12 | if len(itemsMap) != 0 { 13 | t.Logf("driverInfoMap: %v", itemsMap) 14 | } else { 15 | t.Errorf("expected driverInfoMap not empty, but got empty") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/op/path.go: -------------------------------------------------------------------------------- 1 | package op 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/errs" 5 | "strings" 6 | 7 | "github.com/alist-org/alist/v3/internal/driver" 8 | "github.com/alist-org/alist/v3/pkg/utils" 9 | log "github.com/sirupsen/logrus" 10 | ) 11 | 12 | // GetStorageAndActualPath Get the corresponding storage and actual path 13 | // for path: remove the mount path prefix and join the actual root folder if exists 14 | func GetStorageAndActualPath(rawPath string) (storage driver.Driver, actualPath string, err error) { 15 | rawPath = utils.FixAndCleanPath(rawPath) 16 | storage = GetBalancedStorage(rawPath) 17 | if storage == nil { 18 | if rawPath == "/" { 19 | err = errs.NewErr(errs.StorageNotFound, "please add a storage first") 20 | return 21 | } 22 | err = errs.NewErr(errs.StorageNotFound, "rawPath: %s", rawPath) 23 | return 24 | } 25 | log.Debugln("use storage: ", storage.GetStorage().MountPath) 26 | mountPath := utils.GetActualMountPath(storage.GetStorage().MountPath) 27 | actualPath = utils.FixAndCleanPath(strings.TrimPrefix(rawPath, mountPath)) 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /internal/search/db/init.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | log "github.com/sirupsen/logrus" 8 | 9 | "github.com/alist-org/alist/v3/internal/conf" 10 | "github.com/alist-org/alist/v3/internal/db" 11 | "github.com/alist-org/alist/v3/internal/search/searcher" 12 | ) 13 | 14 | var config = searcher.Config{ 15 | Name: "database", 16 | AutoUpdate: true, 17 | } 18 | 19 | func init() { 20 | searcher.RegisterSearcher(config, func() (searcher.Searcher, error) { 21 | db := db.GetDb() 22 | switch conf.Conf.Database.Type { 23 | case "mysql": 24 | tableName := fmt.Sprintf("%ssearch_nodes", conf.Conf.Database.TablePrefix) 25 | tx := db.Exec(fmt.Sprintf("CREATE FULLTEXT INDEX idx_%s_name_fulltext ON %s(name);", tableName, tableName)) 26 | if err := tx.Error; err != nil && !strings.Contains(err.Error(), "Error 1061 (42000)") { // duplicate error 27 | log.Errorf("failed to create full text index: %v", err) 28 | return nil, err 29 | } 30 | case "postgres": 31 | db.Exec("CREATE EXTENSION pg_trgm;") 32 | db.Exec("CREATE EXTENSION btree_gin;") 33 | tableName := fmt.Sprintf("%ssearch_nodes", conf.Conf.Database.TablePrefix) 34 | tx := db.Exec(fmt.Sprintf("CREATE INDEX idx_%s_name ON %s USING GIN (name);", tableName, tableName)) 35 | if err := tx.Error; err != nil && !strings.Contains(err.Error(), "SQLSTATE 42P07") { 36 | log.Errorf("failed to create index using GIN: %v", err) 37 | return nil, err 38 | } 39 | } 40 | return &DB{}, nil 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /internal/search/db/search.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/alist-org/alist/v3/internal/db" 7 | "github.com/alist-org/alist/v3/internal/model" 8 | "github.com/alist-org/alist/v3/internal/search/searcher" 9 | ) 10 | 11 | type DB struct{} 12 | 13 | func (D DB) Config() searcher.Config { 14 | return config 15 | } 16 | 17 | func (D DB) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) { 18 | return db.SearchNode(req, true) 19 | } 20 | 21 | func (D DB) Index(ctx context.Context, node model.SearchNode) error { 22 | return db.CreateSearchNode(&node) 23 | } 24 | 25 | func (D DB) BatchIndex(ctx context.Context, nodes []model.SearchNode) error { 26 | return db.BatchCreateSearchNodes(&nodes) 27 | } 28 | 29 | func (D DB) Get(ctx context.Context, parent string) ([]model.SearchNode, error) { 30 | return db.GetSearchNodesByParent(parent) 31 | } 32 | 33 | func (D DB) Del(ctx context.Context, path string) error { 34 | return db.DeleteSearchNodesByParent(path) 35 | } 36 | 37 | func (D DB) Release(ctx context.Context) error { 38 | return nil 39 | } 40 | 41 | func (D DB) Clear(ctx context.Context) error { 42 | return db.ClearSearchNodes() 43 | } 44 | 45 | var _ searcher.Searcher = (*DB)(nil) 46 | -------------------------------------------------------------------------------- /internal/search/db_non_full_text/init.go: -------------------------------------------------------------------------------- 1 | package db_non_full_text 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/search/searcher" 5 | ) 6 | 7 | var config = searcher.Config{ 8 | Name: "database_non_full_text", 9 | AutoUpdate: true, 10 | } 11 | 12 | func init() { 13 | searcher.RegisterSearcher(config, func() (searcher.Searcher, error) { 14 | return &DB{}, nil 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /internal/search/db_non_full_text/search.go: -------------------------------------------------------------------------------- 1 | package db_non_full_text 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/alist-org/alist/v3/internal/db" 7 | "github.com/alist-org/alist/v3/internal/model" 8 | "github.com/alist-org/alist/v3/internal/search/searcher" 9 | ) 10 | 11 | type DB struct{} 12 | 13 | func (D DB) Config() searcher.Config { 14 | return config 15 | } 16 | 17 | func (D DB) Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) { 18 | return db.SearchNode(req, false) 19 | } 20 | 21 | func (D DB) Index(ctx context.Context, node model.SearchNode) error { 22 | return db.CreateSearchNode(&node) 23 | } 24 | 25 | func (D DB) BatchIndex(ctx context.Context, nodes []model.SearchNode) error { 26 | return db.BatchCreateSearchNodes(&nodes) 27 | } 28 | 29 | func (D DB) Get(ctx context.Context, parent string) ([]model.SearchNode, error) { 30 | return db.GetSearchNodesByParent(parent) 31 | } 32 | 33 | func (D DB) Del(ctx context.Context, path string) error { 34 | return db.DeleteSearchNodesByParent(path) 35 | } 36 | 37 | func (D DB) Release(ctx context.Context) error { 38 | return nil 39 | } 40 | 41 | func (D DB) Clear(ctx context.Context) error { 42 | return db.ClearSearchNodes() 43 | } 44 | 45 | var _ searcher.Searcher = (*DB)(nil) 46 | -------------------------------------------------------------------------------- /internal/search/import.go: -------------------------------------------------------------------------------- 1 | package search 2 | 3 | import ( 4 | _ "github.com/alist-org/alist/v3/internal/search/bleve" 5 | _ "github.com/alist-org/alist/v3/internal/search/db" 6 | _ "github.com/alist-org/alist/v3/internal/search/db_non_full_text" 7 | _ "github.com/alist-org/alist/v3/internal/search/meilisearch" 8 | ) 9 | -------------------------------------------------------------------------------- /internal/search/searcher/manage.go: -------------------------------------------------------------------------------- 1 | package searcher 2 | 3 | type New func() (Searcher, error) 4 | 5 | var NewMap = map[string]New{} 6 | 7 | func RegisterSearcher(config Config, searcher New) { 8 | NewMap[config.Name] = searcher 9 | } 10 | -------------------------------------------------------------------------------- /internal/search/searcher/searcher.go: -------------------------------------------------------------------------------- 1 | package searcher 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/alist-org/alist/v3/internal/model" 7 | ) 8 | 9 | type Config struct { 10 | Name string 11 | AutoUpdate bool 12 | } 13 | 14 | type Searcher interface { 15 | // Config of the searcher 16 | Config() Config 17 | // Search specific keywords in specific path 18 | Search(ctx context.Context, req model.SearchReq) ([]model.SearchNode, int64, error) 19 | // Index obj with parent 20 | Index(ctx context.Context, node model.SearchNode) error 21 | // BatchIndex obj with parent 22 | BatchIndex(ctx context.Context, nodes []model.SearchNode) error 23 | // Get by parent 24 | Get(ctx context.Context, parent string) ([]model.SearchNode, error) 25 | // Del with prefix 26 | Del(ctx context.Context, prefix string) error 27 | // Release resource 28 | Release(ctx context.Context) error 29 | // Clear all index 30 | Clear(ctx context.Context) error 31 | } 32 | -------------------------------------------------------------------------------- /internal/setting/setting.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/alist-org/alist/v3/internal/op" 7 | ) 8 | 9 | func GetStr(key string, defaultValue ...string) string { 10 | val, _ := op.GetSettingItemByKey(key) 11 | if val == nil { 12 | if len(defaultValue) > 0 { 13 | return defaultValue[0] 14 | } 15 | return "" 16 | } 17 | return val.Value 18 | } 19 | 20 | func GetInt(key string, defaultVal int) int { 21 | i, err := strconv.Atoi(GetStr(key)) 22 | if err != nil { 23 | return defaultVal 24 | } 25 | return i 26 | } 27 | 28 | func GetBool(key string) bool { 29 | return GetStr(key) == "true" || GetStr(key) == "1" 30 | } 31 | -------------------------------------------------------------------------------- /internal/sign/sign.go: -------------------------------------------------------------------------------- 1 | package sign 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/alist-org/alist/v3/internal/conf" 8 | "github.com/alist-org/alist/v3/internal/setting" 9 | "github.com/alist-org/alist/v3/pkg/sign" 10 | ) 11 | 12 | var once sync.Once 13 | var instance sign.Sign 14 | 15 | func Sign(data string) string { 16 | expire := setting.GetInt(conf.LinkExpiration, 0) 17 | if expire == 0 { 18 | return NotExpired(data) 19 | } else { 20 | return WithDuration(data, time.Duration(expire)*time.Hour) 21 | } 22 | } 23 | 24 | func WithDuration(data string, d time.Duration) string { 25 | once.Do(Instance) 26 | return instance.Sign(data, time.Now().Add(d).Unix()) 27 | } 28 | 29 | func NotExpired(data string) string { 30 | once.Do(Instance) 31 | return instance.Sign(data, 0) 32 | } 33 | 34 | func Verify(data string, sign string) error { 35 | once.Do(Instance) 36 | return instance.Verify(data, sign) 37 | } 38 | 39 | func Instance() { 40 | instance = sign.NewHMACSign([]byte(setting.GetStr(conf.Token))) 41 | } 42 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/alist-org/alist/v3/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/aria2/rpc/call_test.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestWebsocketCaller(t *testing.T) { 10 | time.Sleep(time.Second) 11 | c, err := newWebsocketCaller(context.Background(), "ws://localhost:6800/jsonrpc", time.Second, &DummyNotifier{}) 12 | if err != nil { 13 | t.Fatal(err.Error()) 14 | } 15 | defer c.Close() 16 | 17 | var info VersionInfo 18 | if err := c.Call(aria2GetVersion, []interface{}{}, &info); err != nil { 19 | t.Error(err.Error()) 20 | } else { 21 | println(info.Version) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/aria2/rpc/proc.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import "sync" 4 | 5 | type ResponseProcFn func(resp clientResponse) error 6 | 7 | type ResponseProcessor struct { 8 | cbs map[uint64]ResponseProcFn 9 | mu *sync.RWMutex 10 | } 11 | 12 | func NewResponseProcessor() *ResponseProcessor { 13 | return &ResponseProcessor{ 14 | make(map[uint64]ResponseProcFn), 15 | &sync.RWMutex{}, 16 | } 17 | } 18 | 19 | func (r *ResponseProcessor) Add(id uint64, fn ResponseProcFn) { 20 | r.mu.Lock() 21 | r.cbs[id] = fn 22 | r.mu.Unlock() 23 | } 24 | 25 | func (r *ResponseProcessor) remove(id uint64) { 26 | r.mu.Lock() 27 | delete(r.cbs, id) 28 | r.mu.Unlock() 29 | } 30 | 31 | // Process called by recv routine 32 | func (r *ResponseProcessor) Process(resp clientResponse) error { 33 | id := *resp.Id 34 | r.mu.RLock() 35 | fn, ok := r.cbs[id] 36 | r.mu.RUnlock() 37 | if ok && fn != nil { 38 | defer r.remove(id) 39 | return fn(resp) 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /pkg/chanio/chanio.go: -------------------------------------------------------------------------------- 1 | package chanio 2 | 3 | import ( 4 | "io" 5 | "sync/atomic" 6 | ) 7 | 8 | type ChanIO struct { 9 | cl atomic.Bool 10 | c chan []byte 11 | buf []byte 12 | } 13 | 14 | func New() *ChanIO { 15 | return &ChanIO{ 16 | cl: atomic.Bool{}, 17 | c: make(chan []byte), 18 | buf: make([]byte, 0), 19 | } 20 | } 21 | 22 | func (c *ChanIO) Read(p []byte) (int, error) { 23 | if c.cl.Load() { 24 | if len(c.buf) == 0 { 25 | return 0, io.EOF 26 | } 27 | n := copy(p, c.buf) 28 | if len(c.buf) > n { 29 | c.buf = c.buf[n:] 30 | } else { 31 | c.buf = make([]byte, 0) 32 | } 33 | return n, nil 34 | } 35 | for len(c.buf) < len(p) && !c.cl.Load() { 36 | c.buf = append(c.buf, <-c.c...) 37 | } 38 | n := copy(p, c.buf) 39 | if len(c.buf) > n { 40 | c.buf = c.buf[n:] 41 | } else { 42 | c.buf = make([]byte, 0) 43 | } 44 | return n, nil 45 | } 46 | 47 | func (c *ChanIO) Write(p []byte) (int, error) { 48 | if c.cl.Load() { 49 | return 0, io.ErrClosedPipe 50 | } 51 | c.c <- p 52 | return len(p), nil 53 | } 54 | 55 | func (c *ChanIO) Close() error { 56 | if c.cl.Load() { 57 | return io.ErrClosedPipe 58 | } 59 | c.cl.Store(true) 60 | close(c.c) 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/cookie/cookie.go: -------------------------------------------------------------------------------- 1 | package cookie 2 | 3 | import ( 4 | "net/http" 5 | "strings" 6 | ) 7 | 8 | func Parse(str string) []*http.Cookie { 9 | header := http.Header{} 10 | header.Add("Cookie", str) 11 | request := http.Request{Header: header} 12 | return request.Cookies() 13 | } 14 | 15 | func ToString(cookies []*http.Cookie) string { 16 | if cookies == nil { 17 | return "" 18 | } 19 | cookieStrings := make([]string, len(cookies)) 20 | for i, cookie := range cookies { 21 | cookieStrings[i] = cookie.String() 22 | } 23 | return strings.Join(cookieStrings, ";") 24 | } 25 | 26 | func SetCookie(cookies []*http.Cookie, name, value string) []*http.Cookie { 27 | for i, cookie := range cookies { 28 | if cookie.Name == name { 29 | cookies[i].Value = value 30 | return cookies 31 | } 32 | } 33 | cookies = append(cookies, &http.Cookie{Name: name, Value: value}) 34 | return cookies 35 | } 36 | 37 | func GetCookie(cookies []*http.Cookie, name string) *http.Cookie { 38 | for _, cookie := range cookies { 39 | if cookie.Name == name { 40 | return cookie 41 | } 42 | } 43 | return nil 44 | } 45 | 46 | func SetStr(cookiesStr, name, value string) string { 47 | cookies := Parse(cookiesStr) 48 | cookies = SetCookie(cookies, name, value) 49 | return ToString(cookies) 50 | } 51 | 52 | func GetStr(cookiesStr, name string) string { 53 | cookies := Parse(cookiesStr) 54 | cookie := GetCookie(cookies, name) 55 | if cookie == nil { 56 | return "" 57 | } 58 | return cookie.Value 59 | } 60 | -------------------------------------------------------------------------------- /pkg/cron/cron.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import "time" 4 | 5 | type Cron struct { 6 | d time.Duration 7 | ch chan struct{} 8 | } 9 | 10 | func NewCron(d time.Duration) *Cron { 11 | return &Cron{ 12 | d: d, 13 | ch: make(chan struct{}), 14 | } 15 | } 16 | 17 | func (c *Cron) Do(f func()) { 18 | go func() { 19 | ticker := time.NewTicker(c.d) 20 | defer ticker.Stop() 21 | for { 22 | select { 23 | case <-ticker.C: 24 | f() 25 | case <-c.ch: 26 | return 27 | } 28 | } 29 | }() 30 | } 31 | 32 | func (c *Cron) Stop() { 33 | select { 34 | case _, _ = <-c.ch: 35 | default: 36 | c.ch <- struct{}{} 37 | close(c.ch) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /pkg/cron/cron_test.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestCron(t *testing.T) { 9 | c := NewCron(time.Second) 10 | c.Do(func() { 11 | t.Logf("cron log") 12 | }) 13 | time.Sleep(time.Second * 3) 14 | c.Stop() 15 | c.Stop() 16 | } 17 | -------------------------------------------------------------------------------- /pkg/generic/queue.go: -------------------------------------------------------------------------------- 1 | package generic 2 | 3 | type Queue[T any] struct { 4 | queue []T 5 | } 6 | 7 | func NewQueue[T any]() *Queue[T] { 8 | return &Queue[T]{queue: make([]T, 0)} 9 | } 10 | 11 | func (q *Queue[T]) Push(v T) { 12 | q.queue = append(q.queue, v) 13 | } 14 | 15 | func (q *Queue[T]) Pop() T { 16 | v := q.queue[0] 17 | q.queue = q.queue[1:] 18 | return v 19 | } 20 | 21 | func (q *Queue[T]) Len() int { 22 | return len(q.queue) 23 | } 24 | 25 | func (q *Queue[T]) IsEmpty() bool { 26 | return len(q.queue) == 0 27 | } 28 | 29 | func (q *Queue[T]) Clear() { 30 | q.queue = nil 31 | } 32 | 33 | func (q *Queue[T]) Peek() T { 34 | return q.queue[0] 35 | } 36 | 37 | func (q *Queue[T]) PeekN(n int) []T { 38 | return q.queue[:n] 39 | } 40 | 41 | func (q *Queue[T]) PopN(n int) []T { 42 | v := q.queue[:n] 43 | q.queue = q.queue[n:] 44 | return v 45 | } 46 | 47 | func (q *Queue[T]) PopAll() []T { 48 | v := q.queue 49 | q.queue = nil 50 | return v 51 | } 52 | 53 | func (q *Queue[T]) PopWhile(f func(T) bool) []T { 54 | var i int 55 | for i = 0; i < len(q.queue); i++ { 56 | if !f(q.queue[i]) { 57 | break 58 | } 59 | } 60 | v := q.queue[:i] 61 | q.queue = q.queue[i:] 62 | return v 63 | } 64 | 65 | func (q *Queue[T]) PopUntil(f func(T) bool) []T { 66 | var i int 67 | for i = 0; i < len(q.queue); i++ { 68 | if f(q.queue[i]) { 69 | break 70 | } 71 | } 72 | v := q.queue[:i] 73 | q.queue = q.queue[i:] 74 | return v 75 | } 76 | -------------------------------------------------------------------------------- /pkg/gowebdav/.gitignore: -------------------------------------------------------------------------------- 1 | # Folders to ignore 2 | /src 3 | /bin 4 | /pkg 5 | /gowebdav 6 | /.idea 7 | 8 | # Binaries for programs and plugins 9 | *.exe 10 | *.exe~ 11 | *.dll 12 | *.so 13 | *.dylib 14 | 15 | # Test binary, build with `go test -c` 16 | *.test 17 | 18 | # Output of the go coverage tool, specifically when used with LiteIDE 19 | *.out 20 | 21 | .vscode/ -------------------------------------------------------------------------------- /pkg/gowebdav/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - "1.x" 5 | 6 | install: 7 | - go get ./... 8 | 9 | script: 10 | - go test -v --short ./... -------------------------------------------------------------------------------- /pkg/gowebdav/Makefile: -------------------------------------------------------------------------------- 1 | BIN := gowebdav 2 | SRC := $(wildcard *.go) cmd/gowebdav/main.go 3 | 4 | all: test cmd 5 | 6 | cmd: ${BIN} 7 | 8 | ${BIN}: ${SRC} 9 | go build -o $@ ./cmd/gowebdav 10 | 11 | test: 12 | go test -v --short ./... 13 | 14 | api: 15 | @sed '/^## API$$/,$$d' -i README.md 16 | @echo '## API' >> README.md 17 | @godoc2md github.com/studio-b12/gowebdav | sed '/^$$/N;/^\n$$/D' |\ 18 | sed '2d' |\ 19 | sed 's/\/src\/github.com\/studio-b12\/gowebdav\//https:\/\/github.com\/studio-b12\/gowebdav\/blob\/master\//g' |\ 20 | sed 's/\/src\/target\//https:\/\/github.com\/studio-b12\/gowebdav\/blob\/master\//g' |\ 21 | sed 's/^#/##/g' >> README.md 22 | 23 | check: 24 | gofmt -w -s $(SRC) 25 | @echo 26 | gocyclo -over 15 . 27 | @echo 28 | golint ./... 29 | 30 | clean: 31 | @rm -f ${BIN} 32 | 33 | .PHONY: all cmd clean test api check 34 | -------------------------------------------------------------------------------- /pkg/gowebdav/basicAuth.go: -------------------------------------------------------------------------------- 1 | package gowebdav 2 | 3 | import ( 4 | "encoding/base64" 5 | "net/http" 6 | ) 7 | 8 | // BasicAuth structure holds our credentials 9 | type BasicAuth struct { 10 | user string 11 | pw string 12 | } 13 | 14 | // Type identifies the BasicAuthenticator 15 | func (b *BasicAuth) Type() string { 16 | return "BasicAuth" 17 | } 18 | 19 | // User holds the BasicAuth username 20 | func (b *BasicAuth) User() string { 21 | return b.user 22 | } 23 | 24 | // Pass holds the BasicAuth password 25 | func (b *BasicAuth) Pass() string { 26 | return b.pw 27 | } 28 | 29 | // Authorize the current request 30 | func (b *BasicAuth) Authorize(req *http.Request, method string, path string) { 31 | a := b.user + ":" + b.pw 32 | auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(a)) 33 | req.Header.Set("Authorization", auth) 34 | } 35 | -------------------------------------------------------------------------------- /pkg/gowebdav/doc.go: -------------------------------------------------------------------------------- 1 | // Package gowebdav is a WebDAV client library with a command line tool 2 | // included. 3 | package gowebdav 4 | -------------------------------------------------------------------------------- /pkg/gowebdav/errors.go: -------------------------------------------------------------------------------- 1 | package gowebdav 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | // StatusError implements error and wraps 9 | // an erroneous status code. 10 | type StatusError struct { 11 | Status int 12 | } 13 | 14 | func (se StatusError) Error() string { 15 | return fmt.Sprintf("%d", se.Status) 16 | } 17 | 18 | // IsErrCode returns true if the given error 19 | // is an os.PathError wrapping a StatusError 20 | // with the given status code. 21 | func IsErrCode(err error, code int) bool { 22 | if pe, ok := err.(*os.PathError); ok { 23 | se, ok := pe.Err.(StatusError) 24 | return ok && se.Status == code 25 | } 26 | return false 27 | } 28 | 29 | // IsErrNotFound is shorthand for IsErrCode 30 | // for status 404. 31 | func IsErrNotFound(err error) bool { 32 | return IsErrCode(err, 404) 33 | } 34 | 35 | func newPathError(op string, path string, statusCode int) error { 36 | return &os.PathError{ 37 | Op: op, 38 | Path: path, 39 | Err: StatusError{statusCode}, 40 | } 41 | } 42 | 43 | func newPathErrorErr(op string, path string, err error) error { 44 | return &os.PathError{ 45 | Op: op, 46 | Path: path, 47 | Err: err, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pkg/gowebdav/netrc.go: -------------------------------------------------------------------------------- 1 | package gowebdav 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "net/url" 7 | "os" 8 | "regexp" 9 | "strings" 10 | ) 11 | 12 | func parseLine(s string) (login, pass string) { 13 | fields := strings.Fields(s) 14 | for i, f := range fields { 15 | if f == "login" { 16 | login = fields[i+1] 17 | } 18 | if f == "password" { 19 | pass = fields[i+1] 20 | } 21 | } 22 | return login, pass 23 | } 24 | 25 | // ReadConfig reads login and password configuration from ~/.netrc 26 | // machine foo.com login username password 123456 27 | func ReadConfig(uri, netrc string) (string, string) { 28 | u, err := url.Parse(uri) 29 | if err != nil { 30 | return "", "" 31 | } 32 | 33 | file, err := os.Open(netrc) 34 | if err != nil { 35 | return "", "" 36 | } 37 | defer file.Close() 38 | 39 | re := fmt.Sprintf(`^.*machine %s.*$`, u.Host) 40 | scanner := bufio.NewScanner(file) 41 | for scanner.Scan() { 42 | s := scanner.Text() 43 | 44 | matched, err := regexp.MatchString(re, s) 45 | if err != nil { 46 | return "", "" 47 | } 48 | if matched { 49 | return parseLine(s) 50 | } 51 | } 52 | 53 | return "", "" 54 | } 55 | -------------------------------------------------------------------------------- /pkg/mq/mq.go: -------------------------------------------------------------------------------- 1 | package mq 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/alist-org/alist/v3/pkg/generic" 7 | ) 8 | 9 | type Message[T any] struct { 10 | Content T 11 | } 12 | 13 | type BasicConsumer[T any] func(Message[T]) 14 | type AllConsumer[T any] func([]Message[T]) 15 | 16 | type MQ[T any] interface { 17 | Publish(Message[T]) 18 | Consume(BasicConsumer[T]) 19 | ConsumeAll(AllConsumer[T]) 20 | Clear() 21 | Len() int 22 | } 23 | 24 | type inMemoryMQ[T any] struct { 25 | queue generic.Queue[Message[T]] 26 | sync.Mutex 27 | } 28 | 29 | func NewInMemoryMQ[T any]() MQ[T] { 30 | return &inMemoryMQ[T]{queue: *generic.NewQueue[Message[T]]()} 31 | } 32 | 33 | func (mq *inMemoryMQ[T]) Publish(msg Message[T]) { 34 | mq.Lock() 35 | defer mq.Unlock() 36 | mq.queue.Push(msg) 37 | } 38 | 39 | func (mq *inMemoryMQ[T]) Consume(consumer BasicConsumer[T]) { 40 | mq.Lock() 41 | defer mq.Unlock() 42 | for !mq.queue.IsEmpty() { 43 | consumer(mq.queue.Pop()) 44 | } 45 | } 46 | 47 | func (mq *inMemoryMQ[T]) ConsumeAll(consumer AllConsumer[T]) { 48 | mq.Lock() 49 | defer mq.Unlock() 50 | consumer(mq.queue.PopAll()) 51 | } 52 | 53 | func (mq *inMemoryMQ[T]) Clear() { 54 | mq.Lock() 55 | defer mq.Unlock() 56 | mq.queue.Clear() 57 | } 58 | 59 | func (mq *inMemoryMQ[T]) Len() int { 60 | return mq.queue.Len() 61 | } 62 | -------------------------------------------------------------------------------- /pkg/sign/hmac.go: -------------------------------------------------------------------------------- 1 | package sign 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha256" 6 | "encoding/base64" 7 | "io" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | type HMACSign struct { 14 | SecretKey []byte 15 | } 16 | 17 | func (s HMACSign) Sign(data string, expire int64) string { 18 | h := hmac.New(sha256.New, s.SecretKey) 19 | expireTimeStamp := strconv.FormatInt(expire, 10) 20 | _, err := io.WriteString(h, data+":"+expireTimeStamp) 21 | if err != nil { 22 | return "" 23 | } 24 | 25 | return base64.URLEncoding.EncodeToString(h.Sum(nil)) + ":" + expireTimeStamp 26 | } 27 | 28 | func (s HMACSign) Verify(data, sign string) error { 29 | signSlice := strings.Split(sign, ":") 30 | // check whether contains expire time 31 | if signSlice[len(signSlice)-1] == "" { 32 | return ErrExpireMissing 33 | } 34 | // check whether expire time is expired 35 | expires, err := strconv.ParseInt(signSlice[len(signSlice)-1], 10, 64) 36 | if err != nil { 37 | return ErrExpireInvalid 38 | } 39 | // if expire time is expired, return error 40 | if expires < time.Now().Unix() && expires != 0 { 41 | return ErrSignExpired 42 | } 43 | // verify sign 44 | if s.Sign(data, expires) != sign { 45 | return ErrSignInvalid 46 | } 47 | return nil 48 | } 49 | 50 | func NewHMACSign(secret []byte) Sign { 51 | return HMACSign{SecretKey: secret} 52 | } 53 | -------------------------------------------------------------------------------- /pkg/sign/sign.go: -------------------------------------------------------------------------------- 1 | package sign 2 | 3 | import "errors" 4 | 5 | type Sign interface { 6 | Sign(data string, expire int64) string 7 | Verify(data, sign string) error 8 | } 9 | 10 | var ( 11 | ErrSignExpired = errors.New("sign expired") 12 | ErrSignInvalid = errors.New("sign invalid") 13 | ErrExpireInvalid = errors.New("expire invalid") 14 | ErrExpireMissing = errors.New("expire missing") 15 | ) 16 | -------------------------------------------------------------------------------- /pkg/tache/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /pkg/tache/.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | .idea -------------------------------------------------------------------------------- /pkg/tache/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Andy Hsu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pkg/tache/README.md: -------------------------------------------------------------------------------- 1 | # tache 2 | A task manager for golang 3 | -------------------------------------------------------------------------------- /pkg/tache/able.go: -------------------------------------------------------------------------------- 1 | package tache 2 | 3 | // Persistable judge whether the task is persistable 4 | type Persistable interface { 5 | Persistable() bool 6 | } 7 | 8 | // Recoverable judge whether the task is recoverable 9 | type Recoverable interface { 10 | Recoverable() bool 11 | } 12 | 13 | // Retryable judge whether the task is retryable 14 | type Retryable interface { 15 | Retryable() bool 16 | } 17 | -------------------------------------------------------------------------------- /pkg/tache/errors.go: -------------------------------------------------------------------------------- 1 | package tache 2 | 3 | import "errors" 4 | 5 | // TacheError is a custom error type 6 | type TacheError struct { 7 | Msg string 8 | } 9 | 10 | func (e *TacheError) Error() string { 11 | return e.Msg 12 | } 13 | 14 | // NewErr creates a new TacheError 15 | func NewErr(msg string) error { 16 | return &TacheError{Msg: msg} 17 | } 18 | 19 | //var ( 20 | // ErrTaskNotFound = NewErr("task not found") 21 | // ErrTaskRunning = NewErr("task is running") 22 | //) 23 | 24 | type unrecoverableError struct { 25 | error 26 | } 27 | 28 | func (e unrecoverableError) Unwrap() error { 29 | return e.error 30 | } 31 | 32 | // Unrecoverable wraps an error in `unrecoverableError` struct 33 | func Unrecoverable(err error) error { 34 | return unrecoverableError{err} 35 | } 36 | 37 | // IsRecoverable checks if error is an instance of `unrecoverableError` 38 | func IsRecoverable(err error) bool { 39 | return !errors.Is(err, unrecoverableError{}) 40 | } 41 | -------------------------------------------------------------------------------- /pkg/tache/examples/base/main.go: -------------------------------------------------------------------------------- 1 | package base 2 | -------------------------------------------------------------------------------- /pkg/tache/hook.go: -------------------------------------------------------------------------------- 1 | package tache 2 | 3 | // OnBeforeRetry is the interface for tasks that need to be executed before retrying 4 | type OnBeforeRetry interface { 5 | OnBeforeRetry() 6 | } 7 | 8 | // OnSucceeded is the interface for tasks that need to be executed when they succeed 9 | type OnSucceeded interface { 10 | OnSucceeded() 11 | } 12 | 13 | // OnFailed is the interface for tasks that need to be executed when they fail 14 | type OnFailed interface { 15 | OnFailed() 16 | } 17 | -------------------------------------------------------------------------------- /pkg/tache/state.go: -------------------------------------------------------------------------------- 1 | package tache 2 | 3 | // State is the state of a task 4 | type State int 5 | 6 | const ( 7 | // StatePending is the state of a task when it is pending 8 | StatePending = iota 9 | // StateRunning is the state of a task when it is running 10 | StateRunning 11 | // StateSucceeded is the state of a task when it succeeded 12 | StateSucceeded 13 | // StateCanceling is the state of a task when it is canceling 14 | StateCanceling 15 | // StateCanceled is the state of a task when it is canceled 16 | StateCanceled 17 | // StateErrored is the state of a task when it is errored (it will be retried) 18 | StateErrored 19 | // StateFailing is the state of a task when it is failing (executed OnFailed hook) 20 | StateFailing 21 | // StateFailed is the state of a task when it failed (no retry times left) 22 | StateFailed 23 | // StateWaitingRetry is the state of a task when it is waiting for retry 24 | StateWaitingRetry 25 | // StateBeforeRetry is the state of a task when it is executing OnBeforeRetry hook 26 | StateBeforeRetry 27 | ) 28 | -------------------------------------------------------------------------------- /pkg/task/errors.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrTaskNotFound = errors.New("task not found") 7 | ErrTaskRunning = errors.New("task is running") 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/utils/balance.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "strings" 4 | 5 | var balance = ".balance" 6 | 7 | func IsBalance(str string) bool { 8 | return strings.Contains(str, balance) 9 | } 10 | 11 | // GetActualMountPath remove balance suffix 12 | func GetActualMountPath(mountPath string) string { 13 | bIndex := strings.LastIndex(mountPath, ".balance") 14 | if bIndex != -1 { 15 | mountPath = mountPath[:bIndex] 16 | } 17 | return mountPath 18 | } 19 | -------------------------------------------------------------------------------- /pkg/utils/bool.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func IsBool(bs ...bool) bool { 4 | return len(bs) > 0 && bs[0] 5 | } 6 | -------------------------------------------------------------------------------- /pkg/utils/ctx.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | func IsCanceled(ctx context.Context) bool { 8 | select { 9 | case <-ctx.Done(): 10 | return true 11 | default: 12 | return false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/utils/email.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "regexp" 4 | 5 | func IsEmailFormat(email string) bool { 6 | pattern := `^[0-9a-z][_.0-9a-z-]{0,31}@([0-9a-z][0-9a-z-]{0,30}[0-9a-z]\.){1,4}[a-z]{2,4}$` 7 | reg := regexp.MustCompile(pattern) 8 | return reg.MatchString(email) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/utils/ip.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "strings" 7 | ) 8 | 9 | func ClientIP(r *http.Request) string { 10 | xForwardedFor := r.Header.Get("X-Forwarded-For") 11 | ip := strings.TrimSpace(strings.Split(xForwardedFor, ",")[0]) 12 | if ip != "" { 13 | return ip 14 | } 15 | 16 | ip = strings.TrimSpace(r.Header.Get("X-Real-Ip")) 17 | if ip != "" { 18 | return ip 19 | } 20 | 21 | if ip, _, err := net.SplitHostPort(strings.TrimSpace(r.RemoteAddr)); err == nil { 22 | return ip 23 | } 24 | 25 | return "" 26 | } 27 | 28 | func IsLocalIPAddr(ip string) bool { 29 | return IsLocalIP(net.ParseIP(ip)) 30 | } 31 | 32 | func IsLocalIP(ip net.IP) bool { 33 | if ip == nil { 34 | return false 35 | } 36 | if ip.IsLoopback() { 37 | return true 38 | } 39 | 40 | ip4 := ip.To4() 41 | if ip4 == nil { 42 | return false 43 | } 44 | 45 | return ip4[0] == 10 || // 10.0.0.0/8 46 | (ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31) || // 172.16.0.0/12 47 | (ip4[0] == 169 && ip4[1] == 254) || // 169.254.0.0/16 48 | (ip4[0] == 192 && ip4[1] == 168) // 192.168.0.0/16 49 | } 50 | -------------------------------------------------------------------------------- /pkg/utils/json.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | stdjson "encoding/json" 5 | "os" 6 | 7 | json "github.com/json-iterator/go" 8 | log "github.com/sirupsen/logrus" 9 | ) 10 | 11 | var Json = json.ConfigCompatibleWithStandardLibrary 12 | 13 | // WriteJsonToFile write struct to json file 14 | func WriteJsonToFile(dst string, data interface{}, std ...bool) bool { 15 | str, err := json.MarshalIndent(data, "", " ") 16 | if len(std) > 0 && std[0] { 17 | str, err = stdjson.MarshalIndent(data, "", " ") 18 | } 19 | if err != nil { 20 | log.Errorf("failed convert Conf to []byte:%s", err.Error()) 21 | return false 22 | } 23 | err = os.WriteFile(dst, str, 0777) 24 | if err != nil { 25 | log.Errorf("failed to write json file:%s", err.Error()) 26 | return false 27 | } 28 | return true 29 | } 30 | -------------------------------------------------------------------------------- /pkg/utils/log.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | ) 6 | 7 | var Log = log.New() 8 | -------------------------------------------------------------------------------- /pkg/utils/map.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func MergeMap(mObj ...map[string]interface{}) map[string]interface{} { 4 | newObj := map[string]interface{}{} 5 | for _, m := range mObj { 6 | for k, v := range m { 7 | newObj[k] = v 8 | } 9 | } 10 | return newObj 11 | } 12 | -------------------------------------------------------------------------------- /pkg/utils/oauth2.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "golang.org/x/oauth2" 4 | 5 | type tokenSource struct { 6 | fn func() (*oauth2.Token, error) 7 | } 8 | 9 | func (t *tokenSource) Token() (*oauth2.Token, error) { 10 | return t.fn() 11 | } 12 | 13 | func TokenSource(fn func() (*oauth2.Token, error)) oauth2.TokenSource { 14 | return &tokenSource{fn} 15 | } 16 | -------------------------------------------------------------------------------- /pkg/utils/path_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "testing" 4 | 5 | func TestEncodePath(t *testing.T) { 6 | t.Log(EncodePath("http://localhost:5244/d/123#.png")) 7 | } 8 | 9 | func TestFixAndCleanPath(t *testing.T) { 10 | datas := map[string]string{ 11 | "": "/", 12 | ".././": "/", 13 | "../../.../": "/...", 14 | "x//\\y/": "/x/y", 15 | ".././.x/.y/.//..x../..y..": "/.x/.y/..x../..y..", 16 | } 17 | for key, value := range datas { 18 | if FixAndCleanPath(key) != value { 19 | t.Logf("raw %s fix fail", key) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pkg/utils/random/random.go: -------------------------------------------------------------------------------- 1 | package random 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | ) 9 | 10 | var Rand *rand.Rand 11 | 12 | const letterBytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 13 | 14 | func String(n int) string { 15 | b := make([]byte, n) 16 | for i := range b { 17 | b[i] = letterBytes[Rand.Intn(len(letterBytes))] 18 | } 19 | return string(b) 20 | } 21 | 22 | func Token() string { 23 | return "alist-" + uuid.NewString() + String(64) 24 | } 25 | 26 | func RangeInt64(left, right int64) int64 { 27 | return rand.Int63n(left+right) - left 28 | } 29 | 30 | func init() { 31 | s := rand.NewSource(time.Now().UnixNano()) 32 | Rand = rand.New(s) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/utils/str.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/base64" 5 | "strings" 6 | 7 | "github.com/alist-org/alist/v3/internal/conf" 8 | ) 9 | 10 | func MappingName(name string) string { 11 | for k, v := range conf.FilenameCharMap { 12 | name = strings.ReplaceAll(name, k, v) 13 | } 14 | return name 15 | } 16 | 17 | var DEC = map[string]string{ 18 | "-": "+", 19 | "_": "/", 20 | ".": "=", 21 | } 22 | 23 | func SafeAtob(data string) (string, error) { 24 | for k, v := range DEC { 25 | data = strings.ReplaceAll(data, k, v) 26 | } 27 | bytes, err := base64.StdEncoding.DecodeString(data) 28 | if err != nil { 29 | return "", err 30 | } 31 | return string(bytes), err 32 | } 33 | 34 | // GetNoneEmpty returns the first non-empty string, return empty if all empty 35 | func GetNoneEmpty(strArr ...string) string { 36 | for _, s := range strArr { 37 | if len(s) > 0 { 38 | return s 39 | } 40 | } 41 | return "" 42 | } 43 | -------------------------------------------------------------------------------- /pkg/utils/time.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | var CNLoc = time.FixedZone("UTC", 8*60*60) 9 | 10 | func MustParseCNTime(str string) time.Time { 11 | lastOpTime, _ := time.ParseInLocation("2006-01-02 15:04:05 -07", str+" +08", CNLoc) 12 | return lastOpTime 13 | } 14 | 15 | func NewDebounce(interval time.Duration) func(f func()) { 16 | var timer *time.Timer 17 | var lock sync.Mutex 18 | return func(f func()) { 19 | lock.Lock() 20 | defer lock.Unlock() 21 | if timer != nil { 22 | timer.Stop() 23 | } 24 | timer = time.AfterFunc(interval, f) 25 | } 26 | } 27 | 28 | func NewDebounce2(interval time.Duration, f func()) func() { 29 | var timer *time.Timer 30 | var lock sync.Mutex 31 | return func() { 32 | lock.Lock() 33 | defer lock.Unlock() 34 | if timer == nil { 35 | timer = time.AfterFunc(interval, f) 36 | } 37 | (*time.Timer)(timer).Reset(interval) 38 | } 39 | } 40 | 41 | func NewThrottle(interval time.Duration) func(func()) { 42 | var lastCall time.Time 43 | 44 | return func(fn func()) { 45 | now := time.Now() 46 | if now.Sub(lastCall) < interval { 47 | return 48 | } 49 | time.AfterFunc(interval, fn) 50 | lastCall = now 51 | } 52 | } 53 | 54 | func NewThrottle2(interval time.Duration, fn func()) func() { 55 | var lastCall time.Time 56 | return func() { 57 | now := time.Now() 58 | if now.Sub(lastCall) < interval { 59 | return 60 | } 61 | time.AfterFunc(interval, fn) 62 | lastCall = now 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /pkg/utils/url.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/url" 5 | ) 6 | 7 | func InjectQuery(raw string, query url.Values) (string, error) { 8 | param := query.Encode() 9 | if param == "" { 10 | return raw, nil 11 | } 12 | u, err := url.Parse(raw) 13 | if err != nil { 14 | return "", err 15 | } 16 | joiner := "?" 17 | if u.RawQuery != "" { 18 | joiner = "&" 19 | } 20 | return raw + joiner + param, nil 21 | } 22 | -------------------------------------------------------------------------------- /public/dist/README.md: -------------------------------------------------------------------------------- 1 | ## Put dist of frontend here. -------------------------------------------------------------------------------- /public/public.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import "embed" 4 | 5 | //go:embed all:dist 6 | var Public embed.FS 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /serv00.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | API_URL="https://api.github.com/repos/ykxVK8yL5L/alist/releases/latest" 4 | 5 | #DOWNLOAD_URL=$(curl -s $API_URL | jq -r ".assets[] | select(.name | contains(\"alist-freebsd\")) | .browser_download_url") 6 | DOWNLOAD_URL=https://github.com/ykxVK8yL5L/alist/releases/download/latest/alist-freebsd-amd64.tar.gz 7 | curl -L $DOWNLOAD_URL -o alist.tar.gz 8 | tar -xvf alist.tar.gz 9 | 10 | chmod +x alist 11 | 12 | if [ -f "./data/config.json" ]; then 13 | echo "Alist-FreeBSD最新版本已经下载覆盖完成!" 14 | else 15 | nohup ./alist server > /dev/null 2>&1 & 16 | clear 17 | echo "已生成配置文件,请修改端口!" 18 | echo 19 | echo "使用命令 cd data 进入data路径下" 20 | echo 21 | echo "再使用 vim config.json 命令,对配置文件进行编辑,按 i 进入插入模式" 22 | echo 23 | echo "将 port: 5244中的 5244 修改成你放行的端口即可" 24 | echo 25 | echo "接着按 Esc 键退出插入模式,再按 : 键进入命令模式,并输入 wq 再回车,保存并退出" 26 | echo 27 | echo "再使用命令 cd .. 回到上级目录" 28 | echo 29 | fi 30 | -------------------------------------------------------------------------------- /server/common/base.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | stdpath "path" 7 | "strings" 8 | 9 | "github.com/alist-org/alist/v3/internal/conf" 10 | ) 11 | 12 | func GetApiUrl(r *http.Request) string { 13 | api := conf.Conf.SiteURL 14 | if strings.HasPrefix(api, "http") { 15 | return api 16 | } 17 | if r != nil { 18 | protocol := "http" 19 | if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" { 20 | protocol = "https" 21 | } 22 | host := r.Host 23 | if r.Header.Get("X-Forwarded-Host") != "" { 24 | host = r.Header.Get("X-Forwarded-Host") 25 | } 26 | api = fmt.Sprintf("%s://%s", protocol, stdpath.Join(host, api)) 27 | } 28 | api = strings.TrimSuffix(api, "/") 29 | return api 30 | } 31 | -------------------------------------------------------------------------------- /server/common/check_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "testing" 4 | 5 | func TestIsApply(t *testing.T) { 6 | datas := []struct { 7 | metaPath string 8 | reqPath string 9 | applySub bool 10 | result bool 11 | }{ 12 | { 13 | metaPath: "/", 14 | reqPath: "/test", 15 | applySub: true, 16 | result: true, 17 | }, 18 | } 19 | for i, data := range datas { 20 | if IsApply(data.metaPath, data.reqPath, data.applySub) != data.result { 21 | t.Errorf("TestIsApply %d failed", i) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /server/common/hide_privacy_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/alist-org/alist/v3/internal/conf" 8 | ) 9 | 10 | func TestHidePrivacy(t *testing.T) { 11 | reg, err := regexp.Compile("(?U)access_token=(.*)&") 12 | if err != nil { 13 | t.Fatal(err) 14 | } 15 | conf.PrivacyReg = []*regexp.Regexp{reg} 16 | res := hidePrivacy(`Get "https://pan.baidu.com/rest/2.0/xpan/file?access_token=121.d1f66e95acfa40274920079396a51c48.Y2aP2vQDq90hLBE3PAbVije59uTcn7GiWUfw8LCM_olw&dir=%2F&limit=200&method=list&order=name&start=0&web=web " : net/http: TLS handshake timeout`) 17 | t.Log(res) 18 | } 19 | -------------------------------------------------------------------------------- /server/common/resp.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | type Resp[T any] struct { 4 | Code int `json:"code"` 5 | Message string `json:"message"` 6 | Data T `json:"data"` 7 | } 8 | 9 | type PageResp struct { 10 | Content interface{} `json:"content"` 11 | Total int64 `json:"total"` 12 | } 13 | -------------------------------------------------------------------------------- /server/common/sign.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | stdpath "path" 5 | 6 | "github.com/alist-org/alist/v3/internal/conf" 7 | "github.com/alist-org/alist/v3/internal/model" 8 | "github.com/alist-org/alist/v3/internal/setting" 9 | "github.com/alist-org/alist/v3/internal/sign" 10 | ) 11 | 12 | func Sign(obj model.Obj, parent string, encrypt bool) string { 13 | if obj.IsDir() || (!encrypt && !setting.GetBool(conf.SignAll)) { 14 | return "" 15 | } 16 | return sign.Sign(stdpath.Join(parent, obj.GetName())) 17 | } 18 | -------------------------------------------------------------------------------- /server/debug.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net/http" 5 | _ "net/http/pprof" 6 | "runtime" 7 | 8 | "github.com/alist-org/alist/v3/server/common" 9 | "github.com/alist-org/alist/v3/server/middlewares" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func _pprof(g *gin.RouterGroup) { 14 | g.Any("/*name", gin.WrapH(http.DefaultServeMux)) 15 | } 16 | 17 | func debug(g *gin.RouterGroup) { 18 | g.GET("/path/*path", middlewares.Down, func(ctx *gin.Context) { 19 | rawPath := ctx.MustGet("path").(string) 20 | ctx.JSON(200, gin.H{ 21 | "path": rawPath, 22 | }) 23 | }) 24 | g.GET("/hide_privacy", func(ctx *gin.Context) { 25 | common.ErrorStrResp(ctx, "This is ip: 1.1.1.1", 400) 26 | }) 27 | g.GET("/gc", func(c *gin.Context) { 28 | runtime.GC() 29 | c.String(http.StatusOK, "ok") 30 | }) 31 | _pprof(g.Group("/pprof")) 32 | } 33 | -------------------------------------------------------------------------------- /server/handles/driver.go: -------------------------------------------------------------------------------- 1 | package handles 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/alist-org/alist/v3/internal/op" 7 | "github.com/alist-org/alist/v3/server/common" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func ListDriverInfo(c *gin.Context) { 12 | common.SuccessResp(c, op.GetDriverInfoMap()) 13 | } 14 | 15 | func ListDriverNames(c *gin.Context) { 16 | common.SuccessResp(c, op.GetDriverNames()) 17 | } 18 | 19 | func GetDriverInfo(c *gin.Context) { 20 | driverName := c.Query("driver") 21 | infoMap := op.GetDriverInfoMap() 22 | items, ok := infoMap[driverName] 23 | if !ok { 24 | common.ErrorStrResp(c, fmt.Sprintf("driver [%s] not found", driverName), 404) 25 | return 26 | } 27 | common.SuccessResp(c, items) 28 | } 29 | -------------------------------------------------------------------------------- /server/middlewares/check.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/alist-org/alist/v3/internal/conf" 7 | "github.com/alist-org/alist/v3/pkg/utils" 8 | "github.com/alist-org/alist/v3/server/common" 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func StoragesLoaded(c *gin.Context) { 13 | if conf.StoragesLoaded { 14 | c.Next() 15 | } else { 16 | if utils.SliceContains([]string{"", "/", "/favicon.ico"}, c.Request.URL.Path) { 17 | c.Next() 18 | return 19 | } 20 | paths := []string{"/assets", "/images", "/streamer", "/static"} 21 | for _, path := range paths { 22 | if strings.HasPrefix(c.Request.URL.Path, path) { 23 | c.Next() 24 | return 25 | } 26 | } 27 | common.ErrorStrResp(c, "Loading storage, please wait", 500) 28 | c.Abort() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/middlewares/fsup.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/url" 5 | stdpath "path" 6 | 7 | "github.com/alist-org/alist/v3/internal/errs" 8 | "github.com/alist-org/alist/v3/internal/model" 9 | "github.com/alist-org/alist/v3/internal/op" 10 | "github.com/alist-org/alist/v3/server/common" 11 | "github.com/gin-gonic/gin" 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | func FsUp(c *gin.Context) { 16 | path := c.GetHeader("File-Path") 17 | password := c.GetHeader("Password") 18 | path, err := url.PathUnescape(path) 19 | if err != nil { 20 | common.ErrorResp(c, err, 400) 21 | c.Abort() 22 | return 23 | } 24 | user := c.MustGet("user").(*model.User) 25 | path, err = user.JoinPath(path) 26 | if err != nil { 27 | common.ErrorResp(c, err, 403) 28 | return 29 | } 30 | meta, err := op.GetNearestMeta(stdpath.Dir(path)) 31 | if err != nil { 32 | if !errors.Is(errors.Cause(err), errs.MetaNotFound) { 33 | common.ErrorResp(c, err, 500, true) 34 | c.Abort() 35 | return 36 | } 37 | } 38 | if !(common.CanAccess(user, meta, path, password) && (user.CanWrite() || common.CanWrite(meta, stdpath.Dir(path)))) { 39 | common.ErrorResp(c, errs.PermissionDenied, 403) 40 | c.Abort() 41 | return 42 | } 43 | c.Next() 44 | } 45 | -------------------------------------------------------------------------------- /server/middlewares/https.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/alist-org/alist/v3/internal/conf" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func ForceHttps(c *gin.Context) { 12 | if c.Request.TLS == nil { 13 | host := c.Request.Host 14 | // change port to https port 15 | host = strings.Replace(host, fmt.Sprintf(":%d", conf.Conf.Scheme.HttpPort), fmt.Sprintf(":%d", conf.Conf.Scheme.HttpsPort), 1) 16 | c.Redirect(302, "https://"+host+c.Request.RequestURI) 17 | c.Abort() 18 | return 19 | } 20 | c.Next() 21 | } 22 | -------------------------------------------------------------------------------- /server/middlewares/limit.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func MaxAllowed(n int) gin.HandlerFunc { 8 | sem := make(chan struct{}, n) 9 | acquire := func() { sem <- struct{}{} } 10 | release := func() { <-sem } 11 | return func(c *gin.Context) { 12 | acquire() 13 | defer release() 14 | c.Next() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/middlewares/search.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/alist-org/alist/v3/internal/conf" 5 | "github.com/alist-org/alist/v3/internal/errs" 6 | "github.com/alist-org/alist/v3/internal/setting" 7 | "github.com/alist-org/alist/v3/server/common" 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | func SearchIndex(c *gin.Context) { 12 | mode := setting.GetStr(conf.SearchIndex) 13 | if mode == "none" { 14 | common.ErrorResp(c, errs.SearchNotAvailable, 500) 15 | c.Abort() 16 | } else { 17 | c.Next() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /server/s3.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "context" 5 | "path" 6 | "strings" 7 | 8 | "github.com/alist-org/alist/v3/internal/conf" 9 | "github.com/alist-org/alist/v3/server/common" 10 | "github.com/alist-org/alist/v3/server/s3" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func S3(g *gin.RouterGroup) { 15 | if !conf.Conf.S3.Enable { 16 | g.Any("/*path", func(c *gin.Context) { 17 | common.ErrorStrResp(c, "S3 server is not enabled", 403) 18 | }) 19 | return 20 | } 21 | if conf.Conf.S3.Port != -1 { 22 | g.Any("/*path", func(c *gin.Context) { 23 | common.ErrorStrResp(c, "S3 server bound to single port", 403) 24 | }) 25 | return 26 | } 27 | h, _ := s3.NewServer(context.Background()) 28 | 29 | g.Any("/*path", func(c *gin.Context) { 30 | adjustedPath := strings.TrimPrefix(c.Request.URL.Path, path.Join(conf.URL.Path, "/s3")) 31 | c.Request.URL.Path = adjustedPath 32 | gin.WrapH(h)(c) 33 | }) 34 | } 35 | 36 | func S3Server(g *gin.RouterGroup) { 37 | h, _ := s3.NewServer(context.Background()) 38 | g.Any("/*path", gin.WrapH(h)) 39 | } 40 | -------------------------------------------------------------------------------- /server/s3/ioutils.go: -------------------------------------------------------------------------------- 1 | // Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3 2 | // Package s3 implements a fake s3 server for alist 3 | package s3 4 | 5 | import "io" 6 | 7 | type noOpReadCloser struct{} 8 | 9 | type readerWithCloser struct { 10 | io.Reader 11 | closer func() error 12 | } 13 | 14 | var _ io.ReadCloser = &readerWithCloser{} 15 | 16 | func (d noOpReadCloser) Read(b []byte) (n int, err error) { 17 | return 0, io.EOF 18 | } 19 | 20 | func (d noOpReadCloser) Close() error { 21 | return nil 22 | } 23 | 24 | func limitReadCloser(rdr io.Reader, closer func() error, sz int64) io.ReadCloser { 25 | return &readerWithCloser{ 26 | Reader: io.LimitReader(rdr, sz), 27 | closer: closer, 28 | } 29 | } 30 | 31 | func (rwc *readerWithCloser) Close() error { 32 | if rwc.closer != nil { 33 | return rwc.closer() 34 | } 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /server/s3/list.go: -------------------------------------------------------------------------------- 1 | // Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3 2 | // Package s3 implements a fake s3 server for alist 3 | package s3 4 | 5 | import ( 6 | "path" 7 | "strings" 8 | 9 | "github.com/Mikubill/gofakes3" 10 | ) 11 | 12 | func (b *s3Backend) entryListR(bucket, fdPath, name string, addPrefix bool, response *gofakes3.ObjectList) error { 13 | fp := path.Join(bucket, fdPath) 14 | 15 | dirEntries, err := getDirEntries(fp) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | for _, entry := range dirEntries { 21 | object := entry.GetName() 22 | 23 | // workround for control-chars detect 24 | objectPath := path.Join(fdPath, object) 25 | 26 | if !strings.HasPrefix(object, name) { 27 | continue 28 | } 29 | 30 | if entry.IsDir() { 31 | if addPrefix { 32 | // response.AddPrefix(gofakes3.URLEncode(objectPath)) 33 | response.AddPrefix(objectPath) 34 | continue 35 | } 36 | err := b.entryListR(bucket, path.Join(fdPath, object), "", false, response) 37 | if err != nil { 38 | return err 39 | } 40 | } else { 41 | item := &gofakes3.Content{ 42 | // Key: gofakes3.URLEncode(objectPath), 43 | Key: objectPath, 44 | LastModified: gofakes3.NewContentTime(entry.ModTime()), 45 | ETag: getFileHash(entry), 46 | Size: entry.GetSize(), 47 | StorageClass: gofakes3.StorageStandard, 48 | } 49 | response.Add(item) 50 | } 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /server/s3/logger.go: -------------------------------------------------------------------------------- 1 | // Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3 2 | // Package s3 implements a fake s3 server for alist 3 | package s3 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/Mikubill/gofakes3" 9 | "github.com/alist-org/alist/v3/pkg/utils" 10 | ) 11 | 12 | // logger output formatted message 13 | type logger struct{} 14 | 15 | // print log message 16 | func (l logger) Print(level gofakes3.LogLevel, v ...interface{}) { 17 | switch level { 18 | default: 19 | fallthrough 20 | case gofakes3.LogErr: 21 | utils.Log.Errorf("serve s3: %s", fmt.Sprintln(v...)) 22 | case gofakes3.LogWarn: 23 | utils.Log.Infof("serve s3: %s", fmt.Sprintln(v...)) 24 | case gofakes3.LogInfo: 25 | utils.Log.Debugf("serve s3: %s", fmt.Sprintln(v...)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/s3/server.go: -------------------------------------------------------------------------------- 1 | // Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3 2 | // Package s3 implements a fake s3 server for alist 3 | package s3 4 | 5 | import ( 6 | "context" 7 | "math/rand" 8 | "net/http" 9 | 10 | "github.com/Mikubill/gofakes3" 11 | ) 12 | 13 | // Make a new S3 Server to serve the remote 14 | func NewServer(ctx context.Context) (h http.Handler, err error) { 15 | var newLogger logger 16 | faker := gofakes3.New( 17 | newBackend(), 18 | // gofakes3.WithHostBucket(!opt.pathBucketMode), 19 | gofakes3.WithLogger(newLogger), 20 | gofakes3.WithRequestID(rand.Uint64()), 21 | gofakes3.WithoutVersioning(), 22 | gofakes3.WithV4Auth(authlistResolver()), 23 | gofakes3.WithIntegrityCheck(true), // Check Content-MD5 if supplied 24 | ) 25 | 26 | return faker.Server(), nil 27 | } 28 | -------------------------------------------------------------------------------- /server/static/config.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/alist-org/alist/v3/internal/conf" 7 | "github.com/alist-org/alist/v3/pkg/utils" 8 | ) 9 | 10 | type SiteConfig struct { 11 | BasePath string 12 | Cdn string 13 | } 14 | 15 | func getSiteConfig() SiteConfig { 16 | siteConfig := SiteConfig{ 17 | BasePath: conf.URL.Path, 18 | Cdn: strings.ReplaceAll(strings.TrimSuffix(conf.Conf.Cdn, "/"), "$version", conf.WebVersion), 19 | } 20 | if siteConfig.BasePath != "" { 21 | siteConfig.BasePath = utils.FixAndCleanPath(siteConfig.BasePath) 22 | } 23 | if siteConfig.Cdn == "" { 24 | siteConfig.Cdn = strings.TrimSuffix(siteConfig.BasePath, "/") 25 | } 26 | return siteConfig 27 | } 28 | -------------------------------------------------------------------------------- /server/webdav/buffered_response_writer.go: -------------------------------------------------------------------------------- 1 | package webdav 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type bufferedResponseWriter struct { 8 | statusCode int 9 | data []byte 10 | header http.Header 11 | } 12 | 13 | func (w *bufferedResponseWriter) Header() http.Header { 14 | if w.header == nil { 15 | w.header = make(http.Header) 16 | } 17 | return w.header 18 | } 19 | 20 | func (w *bufferedResponseWriter) Write(bytes []byte) (int, error) { 21 | w.data = append(w.data, bytes...) 22 | return len(bytes), nil 23 | } 24 | 25 | func (w *bufferedResponseWriter) WriteHeader(statusCode int) { 26 | if w.statusCode == 0 { 27 | w.statusCode = statusCode 28 | } 29 | } 30 | 31 | func (w *bufferedResponseWriter) WriteToResponse(rw http.ResponseWriter) (int, error) { 32 | h := rw.Header() 33 | for k, vs := range w.header { 34 | for _, v := range vs { 35 | h.Add(k, v) 36 | } 37 | } 38 | rw.WriteHeader(w.statusCode) 39 | return rw.Write(w.data) 40 | } 41 | 42 | func newBufferedResponseWriter() *bufferedResponseWriter { 43 | return &bufferedResponseWriter{ 44 | statusCode: 0, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /server/webdav/internal/xml/README: -------------------------------------------------------------------------------- 1 | This is a fork of the encoding/xml package at ca1d6c4, the last commit before 2 | https://go.googlesource.com/go/+/c0d6d33 "encoding/xml: restore Go 1.4 name 3 | space behavior" made late in the lead-up to the Go 1.5 release. 4 | 5 | The list of encoding/xml changes is at 6 | https://go.googlesource.com/go/+log/master/src/encoding/xml 7 | 8 | This fork is temporary, and I (nigeltao) expect to revert it after Go 1.6 is 9 | released. 10 | 11 | See http://golang.org/issue/11841 12 | -------------------------------------------------------------------------------- /server/webdav/util.go: -------------------------------------------------------------------------------- 1 | package webdav 2 | 3 | import ( 4 | log "github.com/sirupsen/logrus" 5 | "net/http" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | func (h *Handler) getModTime(r *http.Request) time.Time { 11 | return h.getHeaderTime(r, "X-OC-Mtime") 12 | } 13 | 14 | // owncloud/ nextcloud haven't impl this, but we can add the support since rclone may support this soon 15 | func (h *Handler) getCreateTime(r *http.Request) time.Time { 16 | return h.getHeaderTime(r, "X-OC-Ctime") 17 | } 18 | 19 | func (h *Handler) getHeaderTime(r *http.Request, header string) time.Time { 20 | hVal := r.Header.Get(header) 21 | if hVal != "" { 22 | modTimeUnix, err := strconv.ParseInt(hVal, 10, 64) 23 | if err == nil { 24 | return time.Unix(modTimeUnix, 0) 25 | } 26 | log.Warnf("getModTime in Webdav, failed to parse %s, %s", header, err) 27 | } 28 | return time.Now() 29 | } 30 | -------------------------------------------------------------------------------- /termux.sh: -------------------------------------------------------------------------------- 1 | #!/data/data/com.termux/files/usr/bin/sh 2 | termux-change-repo 3 | pkg update 4 | echo '安装依赖' 5 | pkg instal termux-services tsu vim -y 6 | apt install -y wget dpkg 7 | echo '安装Alist' 8 | wget https://ghfast.top/https://github.com/ykxVK8yL5L/termux-packages/releases/latest/download/alist_1_aarch64.deb -O alist.deb 9 | dpkg -i alist.deb 10 | alist admin set admin 11 | echo '创建开机启动服务' 12 | mkdir -p $PREFIX/var/service/alist/log 13 | ln -sf $PREFIX/share/termux-services/svlogger $PREFIX/var/service/alist/log/run 14 | echo '#!/data/data/com.termux/files/usr/bin/sh' > $PREFIX/var/service/alist/run 15 | echo 'exec 2>&1' >> $PREFIX/var/service/alist/run 16 | echo 'cd ~ && alist server' >> $PREFIX/var/service/alist/run 17 | chmod a+x $PREFIX/var/service/alist/run 18 | sv-enable alist 19 | sv up alist 20 | -------------------------------------------------------------------------------- /wrapper/zcc-arm64: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | zig cc -target aarch64-windows-gnu $@ 3 | -------------------------------------------------------------------------------- /wrapper/zcxx-arm64: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | zig c++ -target aarch64-windows-gnu $@ 3 | --------------------------------------------------------------------------------