Repository: guiyumin/vget Branch: main Commit: 4cb325d9aaaa Files: 304 Total size: 1.8 MB Directory structure: gitextract_53wrt5os/ ├── .dockerignore ├── .github/ │ └── workflows/ │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yaml ├── .vscode/ │ └── settings.json ├── CLAUDE.md ├── LICENSE ├── Makefile ├── README.md ├── README_de.md ├── README_es.md ├── README_fr.md ├── README_jp.md ├── README_kr.md ├── README_zh.md ├── TODO.md ├── cmd/ │ ├── vget/ │ │ └── main.go │ └── vget-server/ │ └── main.go ├── compose.yml ├── docker/ │ └── vget/ │ ├── Dockerfile │ ├── Dockerfile.arm64 │ ├── entrypoint-arm64.sh │ └── entrypoint.sh ├── docs/ │ ├── FAQs.md │ ├── PRD.md │ ├── YOUTUBE_NOTES.md │ ├── bilibili-port-plan.md │ ├── bugfix/ │ │ └── docker-browser-launch.md │ ├── homebrew-distribution.md │ ├── http-server-mode.md │ ├── multi-binary-architecture.md │ ├── seedbox.md │ ├── tauri.md │ ├── telegram.md │ ├── torrent-dispatch.md │ ├── tui-file-browser.md │ ├── webdav-browsing.md │ ├── webdav.md │ ├── xhs-mcp-analysis.md │ └── zsh-completion-limit.md ├── go.mod ├── go.sum ├── internal/ │ ├── cli/ │ │ ├── batch.go │ │ ├── browse.go │ │ ├── completion.go │ │ ├── config.go │ │ ├── extract.go │ │ ├── init.go │ │ ├── kuaidi100.go │ │ ├── login/ │ │ │ ├── bilibili.go │ │ │ └── qrwriter.go │ │ ├── login.go │ │ ├── ls.go │ │ ├── root.go │ │ ├── search.go │ │ ├── search_tui.go │ │ ├── telegram.go │ │ ├── update.go │ │ └── version.go │ ├── core/ │ │ ├── config/ │ │ │ ├── config.go │ │ │ ├── config_test.go │ │ │ ├── sites.go │ │ │ └── wizard.go │ │ ├── downloader/ │ │ │ ├── downloader.go │ │ │ ├── ffmpeg.go │ │ │ ├── hls.go │ │ │ ├── hls_parser.go │ │ │ ├── magic.go │ │ │ ├── multistream.go │ │ │ └── progress.go │ │ ├── extractor/ │ │ │ ├── bilibili.go │ │ │ ├── browser.go │ │ │ ├── direct.go │ │ │ ├── instagram.go │ │ │ ├── itunes.go │ │ │ ├── m3u8.go │ │ │ ├── registry.go │ │ │ ├── telegram/ │ │ │ │ ├── constants.go │ │ │ │ ├── download.go │ │ │ │ ├── extractor.go │ │ │ │ ├── media.go │ │ │ │ ├── parser.go │ │ │ │ ├── session.go │ │ │ │ └── takeout.go │ │ │ ├── telegram.go │ │ │ ├── tiktok.go │ │ │ ├── twitter.go │ │ │ ├── types.go │ │ │ ├── types_test.go │ │ │ ├── xiaohongshu.go │ │ │ ├── xiaoyuzhou.go │ │ │ └── youtube.go │ │ ├── i18n/ │ │ │ ├── i18n.go │ │ │ └── locales/ │ │ │ ├── de.yml │ │ │ ├── en.yml │ │ │ ├── es.yml │ │ │ ├── fr.yml │ │ │ ├── jp.yml │ │ │ ├── kr.yml │ │ │ └── zh.yml │ │ ├── site/ │ │ │ └── bilibili/ │ │ │ └── auth.go │ │ ├── tracker/ │ │ │ └── kuaidi100.go │ │ ├── version/ │ │ │ └── version.go │ │ └── webdav/ │ │ └── client.go │ ├── server/ │ │ ├── auth.go │ │ ├── bilibili.go │ │ ├── embed.go │ │ ├── history.go │ │ ├── job.go │ │ ├── podcast.go │ │ ├── server.go │ │ └── webdav_browse.go │ ├── torrent/ │ │ ├── client.go │ │ ├── qbittorrent.go │ │ ├── synology.go │ │ └── transmission.go │ └── updater/ │ └── updater.go ├── sites.md ├── tauri/ │ ├── .gitignore │ ├── Makefile │ ├── components.json │ ├── index.html │ ├── package.json │ ├── src/ │ │ ├── components/ │ │ │ ├── AppSidebar.tsx │ │ │ ├── home/ │ │ │ │ ├── DownloadItem.tsx │ │ │ │ ├── HomePage.tsx │ │ │ │ └── types.ts │ │ │ ├── icons/ │ │ │ │ └── PdfIcon.tsx │ │ │ ├── media-tools/ │ │ │ │ ├── MediaToolsPage.tsx │ │ │ │ ├── panels/ │ │ │ │ │ ├── AudioConvertPanel.tsx │ │ │ │ │ ├── CompressPanel.tsx │ │ │ │ │ ├── ConvertPanel.tsx │ │ │ │ │ ├── ExtractAudioPanel.tsx │ │ │ │ │ ├── ExtractFramesPanel.tsx │ │ │ │ │ ├── TrimPanel.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── types.ts │ │ │ ├── pdf-tools/ │ │ │ │ ├── PDFToolsPage.tsx │ │ │ │ ├── panels/ │ │ │ │ │ ├── DeletePagesPanel.tsx │ │ │ │ │ ├── ImagesToPdfPanel.tsx │ │ │ │ │ ├── Md2PdfPanel.tsx │ │ │ │ │ ├── MergePdfPanel.tsx │ │ │ │ │ ├── RemoveWatermarkPanel.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── types.ts │ │ │ ├── settings/ │ │ │ │ ├── AboutSettings.tsx │ │ │ │ ├── GeneralSettings.tsx │ │ │ │ ├── SettingsPage.tsx │ │ │ │ ├── SiteSettings.tsx │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ └── ui/ │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── alert.tsx │ │ │ ├── aspect-ratio.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── button-group.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── card.tsx │ │ │ ├── carousel.tsx │ │ │ ├── chart.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── context-menu.tsx │ │ │ ├── dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── empty.tsx │ │ │ ├── field.tsx │ │ │ ├── file-drop-input.tsx │ │ │ ├── form.tsx │ │ │ ├── hover-card.tsx │ │ │ ├── input-group.tsx │ │ │ ├── input-otp.tsx │ │ │ ├── input.tsx │ │ │ ├── item.tsx │ │ │ ├── kbd.tsx │ │ │ ├── label.tsx │ │ │ ├── menubar.tsx │ │ │ ├── navigation-menu.tsx │ │ │ ├── pagination.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── resizable.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── slider.tsx │ │ │ ├── sonner.tsx │ │ │ ├── spinner.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ └── tooltip.tsx │ │ ├── hooks/ │ │ │ ├── use-mobile.ts │ │ │ └── useDropZone.ts │ │ ├── i18n/ │ │ │ ├── index.ts │ │ │ └── locales/ │ │ │ ├── de.yml │ │ │ ├── en.yml │ │ │ ├── es.yml │ │ │ ├── fr.yml │ │ │ ├── jp.yml │ │ │ ├── kr.yml │ │ │ └── zh.yml │ │ ├── index.css │ │ ├── lib/ │ │ │ └── utils.ts │ │ ├── main.tsx │ │ ├── routeTree.gen.ts │ │ ├── routes/ │ │ │ ├── __root.tsx │ │ │ ├── index.tsx │ │ │ ├── media-tools.tsx │ │ │ ├── pdf-tools.tsx │ │ │ └── settings.tsx │ │ ├── services/ │ │ │ └── dockerApi.ts │ │ ├── stores/ │ │ │ ├── auth.ts │ │ │ └── downloads.ts │ │ └── vite-env.d.ts │ ├── src-tauri/ │ │ ├── Cargo.toml │ │ ├── binaries/ │ │ │ └── .gitkeep │ │ ├── build.rs │ │ ├── capabilities/ │ │ │ └── default.json │ │ ├── gen/ │ │ │ └── schemas/ │ │ │ ├── acl-manifests.json │ │ │ ├── capabilities.json │ │ │ ├── desktop-schema.json │ │ │ └── macOS-schema.json │ │ ├── icons/ │ │ │ ├── android/ │ │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ │ └── ic_launcher.xml │ │ │ │ └── values/ │ │ │ │ └── ic_launcher_background.xml │ │ │ └── icon.icns │ │ ├── rust-toolchain.toml │ │ ├── src/ │ │ │ ├── auth.rs │ │ │ ├── config.rs │ │ │ ├── downloader/ │ │ │ │ ├── mod.rs │ │ │ │ └── simple.rs │ │ │ ├── extractor/ │ │ │ │ ├── bilibili.rs │ │ │ │ ├── direct.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── twitter.rs │ │ │ │ └── types.rs │ │ │ ├── ffmpeg.rs │ │ │ ├── lib.rs │ │ │ ├── main.rs │ │ │ ├── md2pdf.rs │ │ │ └── pdf.rs │ │ └── tauri.conf.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts └── ui/ ├── .gitignore ├── README.md ├── eslint.config.js ├── index.html ├── package.json ├── src/ │ ├── components/ │ │ ├── ConfigEditor.tsx │ │ ├── ConfigRow.tsx │ │ ├── DownloadJobCard.tsx │ │ ├── Kuaidi100.tsx │ │ ├── Layout.tsx │ │ ├── Sidebar.tsx │ │ ├── Toast.tsx │ │ ├── Torrent.tsx │ │ └── TorrentSettings.tsx │ ├── context/ │ │ └── AppContext.tsx │ ├── index.css │ ├── main.tsx │ ├── pages/ │ │ ├── BilibiliPage.tsx │ │ ├── BulkDownloadPage.tsx │ │ ├── ConfigPage.tsx │ │ ├── DownloadPage.tsx │ │ ├── HistoryPage.tsx │ │ ├── Kuaidi100Page.tsx │ │ ├── PodcastPage.tsx │ │ ├── TokenPage.tsx │ │ ├── TorrentPage.tsx │ │ └── WebDAVPage.tsx │ ├── routeTree.gen.ts │ ├── routes/ │ │ ├── __root.tsx │ │ ├── bilibili.tsx │ │ ├── bulk.tsx │ │ ├── config.tsx │ │ ├── history.tsx │ │ ├── index.tsx │ │ ├── kuaidi100.tsx │ │ ├── podcast.tsx │ │ ├── token.tsx │ │ ├── torrent.tsx │ │ └── webdav.tsx │ └── utils/ │ ├── apis.ts │ └── translations.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ # Build artifacts build/ tmp/ *.exe *.dll *.so *.dylib # UI build artifacts (will be built in container) ui/node_modules/ ui/dist/ # Embedded UI dist (will be copied from ui-builder stage) internal/server/dist/ # Development files .git/ .gitignore .air.toml .vscode/ .idea/ *.md !README.md # Test files *_test.go **/*_test.go # Config and secrets *.env *.local config.yml config.yaml # OS files .DS_Store Thumbs.db # Docker files (not needed inside container) Dockerfile docker-compose*.yml .dockerignore ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: push: branches: [main] pull_request: branches: [main] jobs: test: name: Test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Create placeholders for embed run: | mkdir -p internal/server/dist && touch internal/server/dist/.gitkeep - name: Run tests run: CGO_ENABLED=0 go test -v ./... lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod - name: Create placeholders for embed run: | mkdir -p internal/server/dist && touch internal/server/dist/.gitkeep - name: Run staticcheck uses: dominikh/staticcheck-action@v1 with: version: "latest" env: CGO_ENABLED: "0" ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: tags: - "v*.*.*" permissions: contents: write packages: write jobs: release: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v5 with: go-version: "1.25.4" - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "22" - name: Build UI run: | cd ui && npm install && npm run build cd .. mkdir -p internal/server/dist rm -rf internal/server/dist/* cp -r ui/dist/* internal/server/dist/ - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser version: "~> v2" args: release --clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} - name: Extract version id: version run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT - name: Prepare latest zips run: | mkdir -p latest cp dist/vget_${{ steps.version.outputs.VERSION }}_darwin_amd64.zip latest/vget-darwin-amd64.zip cp dist/vget_${{ steps.version.outputs.VERSION }}_darwin_arm64.zip latest/vget-darwin-arm64.zip cp dist/vget_${{ steps.version.outputs.VERSION }}_linux_amd64.zip latest/vget-linux-amd64.zip cp dist/vget_${{ steps.version.outputs.VERSION }}_linux_arm64.zip latest/vget-linux-arm64.zip cp dist/vget_${{ steps.version.outputs.VERSION }}_windows_amd64.zip latest/vget-windows-amd64.zip - name: Upload latest zips uses: softprops/action-gh-release@v2 with: files: | latest/vget-darwin-amd64.zip latest/vget-darwin-arm64.zip latest/vget-linux-amd64.zip latest/vget-linux-arm64.zip latest/vget-windows-amd64.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} docker-amd64: if: ${{ !contains(github.ref_name, '-') }} runs-on: ubuntu-latest outputs: digest: ${{ steps.build.outputs.digest }} steps: - name: Free disk space run: | sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL sudo docker image prune --all --force - name: Checkout uses: actions/checkout@v5 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push by digest id: build uses: docker/build-push-action@v6 with: context: . file: ./docker/vget/Dockerfile platforms: linux/amd64 outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true docker-arm64: if: ${{ !contains(github.ref_name, '-') }} runs-on: ubuntu-24.04-arm outputs: digest: ${{ steps.build.outputs.digest }} steps: - name: Checkout uses: actions/checkout@v5 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push by digest id: build uses: docker/build-push-action@v6 with: context: . file: ./docker/vget/Dockerfile.arm64 platforms: linux/arm64 outputs: type=image,name=ghcr.io/${{ github.repository }},push-by-digest=true,name-canonical=true,push=true docker-manifest: if: ${{ !contains(github.ref_name, '-') }} runs-on: ubuntu-latest needs: [docker-amd64, docker-arm64] steps: - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} tags: | type=semver,pattern={{version}} type=semver,pattern={{major}}.{{minor}} type=raw,value=latest - name: Create and push manifest run: | for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n'); do docker buildx imagetools create -t "$tag" \ "ghcr.io/${{ github.repository }}@${{ needs.docker-amd64.outputs.digest }}" \ "ghcr.io/${{ github.repository }}@${{ needs.docker-arm64.outputs.digest }}" done ================================================ FILE: .gitignore ================================================ # Build output /build/ /dist/ /internal/server/dist/ # Binary /vget # IDE .idea/ *.swp *.swo # OS .DS_Store Thumbs.db # Test output coverage.out *.test # Temporary files *.tmp .vget-meta.json /tmp/ /downloads/ ================================================ FILE: .goreleaser.yaml ================================================ version: 2 project_name: vget before: hooks: - go mod tidy builds: - main: ./cmd/vget binary: vget env: - CGO_ENABLED=0 ldflags: - -s -w - -X github.com/guiyumin/vget/internal/core/version.Version={{.Version}} - -X github.com/guiyumin/vget/internal/core/version.Commit={{.Commit}} - -X github.com/guiyumin/vget/internal/core/version.Date={{.Date}} goos: - darwin - linux - windows goarch: - amd64 - arm64 ignore: - goos: windows goarch: arm64 archives: - formats: - zip name_template: >- {{ .ProjectName }}_ {{- .Version }}_ {{- .Os }}_ {{- .Arch }} {{- if .Arm }}v{{ .Arm }}{{ end }} files: - README.md - LICENSE* checksum: name_template: "checksums.txt" algorithm: sha256 changelog: sort: asc filters: exclude: - "^docs:" - "^test:" - "^chore:" - "^ci:" - Merge pull request - Merge branch release: github: owner: guiyumin name: vget draft: false prerelease: auto name_template: "v{{.Version}}" brews: - repository: owner: guiyumin name: homebrew-tap token: "{{ .Env.TAP_GITHUB_TOKEN }}" homepage: "https://github.com/guiyumin/vget" description: "Media downloader CLI for various platforms" license: "Apache-2.0" directory: Formula commit_author: name: goreleaserbot email: bot@goreleaser.com commit_msg_template: "{{ .ProjectName }}: update to {{ .Tag }}" ================================================ FILE: .vscode/settings.json ================================================ { "makefile.configureOnOpen": false, "diffEditor.renderSideBySide": false, "diffEditor.hideUnchangedRegions.enabled": false, "gopls": { "build.env": { "CGO_ENABLED": "0" } } } ================================================ FILE: CLAUDE.md ================================================ # CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Build Commands ```bash # Build go build ./cmd/vget # Build to specific directory go build -o build/vget ./cmd/vget # Run directly go run ./cmd/vget # Build with version info (for releases) go build -ldflags "-X github.com/guiyumin/vget/internal/version.Version=1.0.0" ./cmd/vget ``` ## Architecture vget is a media downloader CLI built with Go. It uses Cobra for command parsing and Bubbletea for interactive TUI elements (spinners, progress bars). ### Core Flow 1. **CLI Layer** (`internal/cli/`) - Cobra commands parse flags and dispatch to handlers 2. **Extractor Layer** (`internal/extractor/`) - URL matching and media metadata extraction 3. **Downloader Layer** (`internal/downloader/`) - HTTP download with Bubbletea progress TUI ### Media Types The `MediaType` enum in `internal/extractor/extractor.go` defines supported media types: - `MediaTypeVideo` - Video files (Twitter, YouTube, etc.) - `MediaTypeAudio` - Audio files (podcasts) - `MediaTypePDF` - PDF documents - `MediaTypeEPUB` - EPUB ebooks - `MediaTypeMOBI` - MOBI ebooks - `MediaTypeAZW` - AZW ebooks - `MediaTypeUnknown` - Fallback (treated as video) Each type has specific terminal output formatting in `internal/cli/extract.go`. ### Extractor Pattern To add support for a new site, implement the `Extractor` interface in `internal/extractor/`: ```go type Extractor interface { Name() string Match(url string) bool Extract(url string) (*VideoInfo, error) } ``` Set the appropriate `MediaType` in the returned `VideoInfo`: ```go return &VideoInfo{ ID: "...", Title: "...", MediaType: MediaTypeAudio, // or MediaTypeVideo, etc. Formats: []Format{...}, }, nil ``` Extractors are auto-registered via `init()` functions. See `xiaoyuzhou.go` or `twitter.go` for examples. ### Commands - `vget ` - Download media from URL - `vget init` - Interactive config wizard (TUI) - `vget update` - Self-update to latest version - `vget search --podcast ` - Search Xiaoyuzhou podcasts - `vget ls :` - List WebDAV remote directory - `vget config show` - Show current configuration - `vget config set ` - Set config value (non-interactive) - `vget config get ` - Get config value - `vget config webdav ...` - Manage WebDAV servers ### i18n Translations are embedded YAML files in `internal/i18n/locales/`. Supported: en, zh, jp, kr, es, fr, de. Access translations via `i18n.T(langCode)` which returns a `*Translations` struct with typed fields. ### Config User config lives in `~/.config/vget/config.yml`. Two ways to configure: 1. **Interactive (TUI):** `vget init` - Bubbletea wizard for first-time setup 2. **Non-interactive:** `vget config set ` - For scripting/Docker Supported keys for `vget config set`: - `language` - Language code (en, zh, jp, kr, es, fr, de) - `output_dir` - Default download directory - `format` - Preferred format (mp4, webm, best) - `quality` - Default quality (1080p, 720p, best) - `twitter.auth_token` - Twitter auth for NSFW content **IMPORTANT:** Config is read fresh on every command execution (not cached at startup). This is intentional and MUST be preserved: - Enables config changes without restart - Critical for Docker UX (no container restart needed) - Never change this behavior ### Xiaohongshu (XHS) Extractor The XHS extractor (`internal/extractor/xiaohongshu.go`) uses browser automation: - **Browser**: Rod's auto-downloaded Chromium (NOT system Chrome) - **Binary location**: `~/.cache/rod/browser/` - **User data**: `~/.config/vget/browser/` (persistent, shared by all extractors) - **Stealth**: Uses `go-rod/stealth` for anti-bot detection **Important**: Never use system Chrome profiles with browser automation - it can corrupt session data. ### Self-Update `internal/updater/` uses go-selfupdate to fetch releases from GitHub (`guiyumin/vget`). Version is set in `internal/version/version.go`. # My Rules - **MUST** USE ./build AS THE BUILD OUTPUT DIRECTORY - **MUST** USE CGO_ENABLED=0 when building vget cli locally - **MUST NOT** RUN npm run dev in @ui - **MUST NOT** VERIFY or TEST your work, since I will test and verify it ================================================ FILE: LICENSE ================================================ Copyright 2025 Yumin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ .PHONY: build build-ui build-metal build-cuda build-nocgo build-whisper push version patch minor major BUILD_DIR := ./build VERSION_FILE := internal/core/version/version.go UI_DIR := ./ui SERVER_DIST := ./internal/server/dist # Get current version from latest git tag (strips 'v' prefix) CURRENT_VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null | sed 's/^v//' || echo "0.0.0") # Get whisper.cpp module path WHISPER_PATH := $(shell go list -m -f '{{.Dir}}' github.com/ggerganov/whisper.cpp/bindings/go 2>/dev/null) build-ui: cd $(UI_DIR) && npm install && npm run build rm -rf $(SERVER_DIST)/* cp -r $(UI_DIR)/dist/* $(SERVER_DIST)/ # Build whisper.cpp static library build-whisper: @if [ -z "$(WHISPER_PATH)" ]; then \ echo "Error: whisper.cpp module not found. Run 'go mod download' first."; \ exit 1; \ fi cd "$(WHISPER_PATH)" && make whisper @echo "whisper.cpp library built at $(WHISPER_PATH)/libwhisper.a" # Standard build with CGO for whisper.cpp (CPU only) # Requires: make build-whisper (run once) build: build-ui CGO_ENABLED=1 \ C_INCLUDE_PATH="$(WHISPER_PATH)" \ LIBRARY_PATH="$(WHISPER_PATH)" \ go build -o $(BUILD_DIR)/vget ./cmd/vget CGO_ENABLED=1 \ C_INCLUDE_PATH="$(WHISPER_PATH)" \ LIBRARY_PATH="$(WHISPER_PATH)" \ go build -o $(BUILD_DIR)/vget-server ./cmd/vget-server # macOS with Metal acceleration (Apple Silicon) # Requires: WHISPER_METAL=1 make build-whisper (run once) build-metal: build-ui CGO_ENABLED=1 \ C_INCLUDE_PATH="$(WHISPER_PATH)" \ LIBRARY_PATH="$(WHISPER_PATH)" \ go build -tags metal -o $(BUILD_DIR)/vget ./cmd/vget CGO_ENABLED=1 \ C_INCLUDE_PATH="$(WHISPER_PATH)" \ LIBRARY_PATH="$(WHISPER_PATH)" \ go build -tags metal -o $(BUILD_DIR)/vget-server ./cmd/vget-server # Linux with CUDA acceleration (NVIDIA GPU) # Requires: GGML_CUDA=1 make build-whisper (run once) build-cuda: build-ui CGO_ENABLED=1 \ C_INCLUDE_PATH="$(WHISPER_PATH)" \ LIBRARY_PATH="$(WHISPER_PATH)" \ CGO_CFLAGS="-I/usr/local/cuda/include" \ CGO_LDFLAGS="-L/usr/local/cuda/lib64" \ go build -tags cuda -o $(BUILD_DIR)/vget ./cmd/vget CGO_ENABLED=1 \ C_INCLUDE_PATH="$(WHISPER_PATH)" \ LIBRARY_PATH="$(WHISPER_PATH)" \ CGO_CFLAGS="-I/usr/local/cuda/include" \ CGO_LDFLAGS="-L/usr/local/cuda/lib64" \ go build -tags cuda -o $(BUILD_DIR)/vget-server ./cmd/vget-server # Build without CGO (uses embedded whisper.cpp binary) build-nocgo: build-ui CGO_ENABLED=0 go build -o $(BUILD_DIR)/vget ./cmd/vget CGO_ENABLED=0 go build -o $(BUILD_DIR)/vget-server ./cmd/vget-server push: git push origin main --tags # Version bump: make version version: @if [ -z "$(filter patch minor major,$(MAKECMDGOALS))" ]; then \ echo "Usage: make version "; \ echo "Current version: $(CURRENT_VERSION)"; \ exit 1; \ fi patch minor major: version @TYPE=$@ && \ echo "Current version: $(CURRENT_VERSION)" && \ NEW_VERSION=$$(echo "$(CURRENT_VERSION)" | awk -F. -v type="$$TYPE" '{ \ split($$3, parts, "-"); \ patch = parts[1]; \ if (index($$3, "-") > 0) { print $$1"."$$2"."patch } \ else if (type == "major") { print $$1+1".0.0" } \ else if (type == "minor") { print $$1"."$$2+1".0" } \ else { print $$1"."$$2"."$$3+1 } \ }') && \ BUILD_DATE=$$(date -u +"%Y-%m-%d") && \ echo "New version: $$NEW_VERSION" && \ echo "Build date: $$BUILD_DATE" && \ sed -i '' 's/Version = ".*"/Version = "'$$NEW_VERSION'"/' $(VERSION_FILE) && \ sed -i '' 's/Date = ".*"/Date = "'$$BUILD_DATE'"/' $(VERSION_FILE) && \ git add $(VERSION_FILE) && \ git commit -m "chore: bump version to v$$NEW_VERSION" && \ git tag "v$$NEW_VERSION" && \ echo "Created tag v$$NEW_VERSION" && \ echo "Run 'make push' to push changes and trigger release" ================================================ FILE: README.md ================================================ # vget Versatile downloader for audio, video, podcasts, PDFs and more. Available as CLI and Docker [简体中文](README_zh.md) | [日本語](README_jp.md) | [한국어](README_kr.md) | [Español](README_es.md) | [Français](README_fr.md) | [Deutsch](README_de.md) ## Installation ### macOS ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-darwin-arm64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Linux / WSL ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-linux-amd64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Windows Download `vget-windows-amd64.zip` from [Releases](https://github.com/guiyumin/vget/releases/latest), extract it, and add to your PATH. ## Screenshots ### Download Progress ![Download Progress](screenshots/pikpak_download.png) ### Docker Server UI ![](screenshots/vget_server_ui.png) ## Docker ```bash docker run -d -p 8080:8080 -v ~/downloads:/home/vget/downloads ghcr.io/guiyumin/vget:latest ``` ## Supported Sources See [sites.md](sites.md) for the full list of supported sites. ## Commands | Command | Description | | -------------------------------------- | ---------------------------------------- | | `vget [url]` | Download media (`-o`, `-q`, `--info`) | | `vget ls :` | List remote directory (`--json`) | | `vget init` | Interactive config wizard | | `vget update` | Self-update (use `sudo` on Mac/Linux) | | `vget search --podcast ` | Search podcasts | | `vget completion [shell]` | Generate shell completion script | | `vget config show` | Show config | | `vget config set ` | Set config value (non-interactive) | | `vget config get ` | Get config value | | `vget config path` | Show config file path | | `vget config webdav list` | List configured WebDAV servers | | `vget config webdav add ` | Add a WebDAV server | | `vget config webdav show ` | Show server details | | `vget config webdav delete ` | Delete a server | | `vget telegram login --import-desktop` | Import Telegram session from desktop app | ### Examples ```bash vget https://twitter.com/user/status/123456789 vget https://www.xiaoyuzhoufm.com/episode/abc123 vget https://www.xiaohongshu.com/explore/abc123 # XHS video/image vget https://example.com/video -o my_video.mp4 vget --info https://example.com/video vget search --podcast "tech news" vget pikpak:/path/to/file.mp4 # WebDAV download vget ls pikpak:/Movies # List remote directory ``` ## Configuration Config file location: | OS | Path | | ----------- | --------------------------- | | macOS/Linux | `~/.config/vget/config.yml` | | Windows | `%APPDATA%\vget\config.yml` | Run `vget init` to create the config file interactively, or create it manually: ```yaml language: en # en, zh, jp, kr, es, fr, de ``` **Note:** Config is read fresh on every command. No restart required after changes (useful for Docker). ## Updating To update vget to the latest version: **macOS / Linux:** ```bash sudo vget update ``` **Windows (run PowerShell as Administrator):** ```powershell vget update ``` ## Languages vget supports multiple languages: - English (en) - 中文 (zh) - 日本語 (jp) - 한국어 (kr) - Español (es) - Français (fr) - Deutsch (de) ## License Apache License 2.0 ================================================ FILE: README_de.md ================================================ # vget Vielseitiger Downloader für Audio, Video, Podcasts, PDFs und mehr. Verfügbar als CLI und Docker. [English](README.md) | [简体中文](README_zh.md) | [日本語](README_jp.md) | [한국어](README_kr.md) | [Español](README_es.md) | [Français](README_fr.md) ## Installation ### macOS ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-darwin-arm64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Linux / WSL ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-linux-amd64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Windows Laden Sie `vget-windows-amd64.zip` von [Releases](https://github.com/guiyumin/vget/releases/latest) herunter, entpacken Sie es und fügen Sie es zum PATH hinzu. ## Screenshots ### Download-Fortschritt ![Download-Fortschritt](screenshots/pikpak_download.png) ### Docker Server-Benutzeroberfläche ![](screenshots/vget_server_ui.png) ## Docker ```bash docker run -d -p 8080:8080 -v ~/downloads:/home/vget/downloads ghcr.io/guiyumin/vget:latest ``` ## Unterstützte Quellen Siehe [sites.md](sites.md) für die vollständige Liste der unterstützten Seiten. ## Befehle | Befehl | Beschreibung | |------------------------------------|---------------------------------------| | `vget [url]` | Medien herunterladen (`-o`, `-q`, `--info`) | | `vget ls :` | Remote-Verzeichnis auflisten (`--json`) | | `vget init` | Interaktiver Konfigurationsassistent | | `vget update` | Aktualisieren (`sudo` auf Mac/Linux) | | `vget search --podcast ` | Podcasts suchen | | `vget completion [shell]` | Shell-Vervollständigung generieren | | `vget config show` | Konfiguration anzeigen | | `vget config set ` | Konfigurationswert setzen (nicht interaktiv) | | `vget config get ` | Konfigurationswert abrufen | | `vget config path` | Konfigurationsdateipfad anzeigen | | `vget config webdav list` | Konfigurierte WebDAV-Server auflisten | | `vget config webdav add ` | WebDAV-Server hinzufügen | | `vget config webdav show ` | Serverdetails anzeigen | | `vget config webdav delete ` | Server löschen | | `vget telegram login --import-desktop` | Telegram-Sitzung von Desktop-App importieren | ### Beispiele ```bash vget https://twitter.com/user/status/123456789 vget https://www.xiaoyuzhoufm.com/episode/abc123 vget https://example.com/video -o mein_video.mp4 vget --info https://example.com/video vget search --podcast "tech news" vget pikpak:/path/to/file.mp4 # WebDAV-Download vget ls pikpak:/Movies # Remote-Verzeichnis auflisten ``` ## Konfiguration Speicherort der Konfigurationsdatei: | OS | Pfad | | ----------- | --------------------------- | | macOS/Linux | `~/.config/vget/config.yml` | | Windows | `%APPDATA%\vget\config.yml` | Führen Sie `vget init` aus, um die Konfigurationsdatei interaktiv zu erstellen, oder erstellen Sie sie manuell: ```yaml language: de # en, zh, jp, kr, es, fr, de ``` **Hinweis:** Die Konfiguration wird bei jedem Befehl neu gelesen. Kein Neustart nach Änderungen erforderlich (nützlich für Docker). ## Aktualisierung Um vget auf die neueste Version zu aktualisieren: **macOS / Linux:** ```bash sudo vget update ``` **Windows (PowerShell als Administrator ausführen):** ```powershell vget update ``` ## Sprachen vget unterstützt mehrere Sprachen: - English (en) - 中文 (zh) - 日本語 (jp) - 한국어 (kr) - Español (es) - Français (fr) - Deutsch (de) ## Lizenz Apache License 2.0 ================================================ FILE: README_es.md ================================================ # vget Descargador versátil para audio, video, podcasts, PDFs y más. Disponible como CLI y Docker. [English](README.md) | [简体中文](README_zh.md) | [日本語](README_jp.md) | [한국어](README_kr.md) | [Français](README_fr.md) | [Deutsch](README_de.md) ## Instalación ### macOS ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-darwin-arm64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Linux / WSL ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-linux-amd64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Windows Descarga `vget-windows-amd64.zip` desde [Releases](https://github.com/guiyumin/vget/releases/latest), extráelo y agrégalo al PATH. ## Capturas de pantalla ### Progreso de descarga ![Progreso de descarga](screenshots/pikpak_download.png) ### Interfaz del servidor Docker ![](screenshots/vget_server_ui.png) ## Docker ```bash docker run -d -p 8080:8080 -v ~/downloads:/home/vget/downloads ghcr.io/guiyumin/vget:latest ``` ## Fuentes compatibles Consulta [sites.md](sites.md) para la lista completa de sitios compatibles. ## Comandos | Comando | Descripción | |------------------------------------|---------------------------------------| | `vget [url]` | Descargar medios (`-o`, `-q`, `--info`) | | `vget ls :` | Listar directorio remoto (`--json`) | | `vget init` | Asistente de configuración interactivo | | `vget update` | Actualizar (usar `sudo` en Mac/Linux) | | `vget search --podcast ` | Buscar podcasts | | `vget completion [shell]` | Generar script de autocompletado | | `vget config show` | Mostrar configuración | | `vget config set ` | Establecer valor de config (no interactivo) | | `vget config get ` | Obtener valor de configuración | | `vget config path` | Mostrar ruta del archivo de config | | `vget config webdav list` | Listar servidores WebDAV configurados | | `vget config webdav add ` | Agregar servidor WebDAV | | `vget config webdav show ` | Mostrar detalles del servidor | | `vget config webdav delete ` | Eliminar servidor | | `vget telegram login --import-desktop` | Importar sesión de Telegram desde la app de escritorio | ### Ejemplos ```bash vget https://twitter.com/user/status/123456789 vget https://www.xiaoyuzhoufm.com/episode/abc123 vget https://example.com/video -o mi_video.mp4 vget --info https://example.com/video vget search --podcast "tech news" vget pikpak:/path/to/file.mp4 # Descarga WebDAV vget ls pikpak:/Movies # Listar directorio remoto ``` ## Configuración Ubicación del archivo de configuración: | SO | Ruta | | ----------- | --------------------------- | | macOS/Linux | `~/.config/vget/config.yml` | | Windows | `%APPDATA%\vget\config.yml` | Ejecuta `vget init` para crear el archivo de configuración interactivamente, o créalo manualmente: ```yaml language: es # en, zh, jp, kr, es, fr, de ``` **Nota:** La configuración se lee en cada comando. No se requiere reinicio después de cambios (útil para Docker). ## Actualización Para actualizar vget a la última versión: **macOS / Linux:** ```bash sudo vget update ``` **Windows (ejecutar PowerShell como Administrador):** ```powershell vget update ``` ## Idiomas vget soporta múltiples idiomas: - English (en) - 中文 (zh) - 日本語 (jp) - 한국어 (kr) - Español (es) - Français (fr) - Deutsch (de) ## Licencia Apache License 2.0 ================================================ FILE: README_fr.md ================================================ # vget Téléchargeur polyvalent pour audio, vidéo, podcasts, PDFs et plus. Disponible en CLI et Docker. [English](README.md) | [简体中文](README_zh.md) | [日本語](README_jp.md) | [한국어](README_kr.md) | [Español](README_es.md) | [Deutsch](README_de.md) ## Installation ### macOS ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-darwin-arm64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Linux / WSL ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-linux-amd64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Windows Téléchargez `vget-windows-amd64.zip` depuis [Releases](https://github.com/guiyumin/vget/releases/latest), extrayez-le et ajoutez-le au PATH. ## Captures d'écran ### Progression du téléchargement ![Progression du téléchargement](screenshots/pikpak_download.png) ### Interface serveur Docker ![](screenshots/vget_server_ui.png) ## Docker ```bash docker run -d -p 8080:8080 -v ~/downloads:/home/vget/downloads ghcr.io/guiyumin/vget:latest ``` ## Sources prises en charge Consultez [sites.md](sites.md) pour la liste complète des sites pris en charge. ## Commandes | Commande | Description | |------------------------------------|---------------------------------------| | `vget [url]` | Télécharger des médias (`-o`, `-q`, `--info`) | | `vget ls :` | Lister un répertoire distant (`--json`) | | `vget init` | Assistant de configuration interactif | | `vget update` | Mise à jour (`sudo` sur Mac/Linux) | | `vget search --podcast ` | Rechercher des podcasts | | `vget completion [shell]` | Générer un script d'autocomplétion | | `vget config show` | Afficher la configuration | | `vget config set ` | Définir une valeur de config (non interactif) | | `vget config get ` | Obtenir une valeur de configuration | | `vget config path` | Afficher le chemin du fichier config | | `vget config webdav list` | Lister les serveurs WebDAV configurés | | `vget config webdav add ` | Ajouter un serveur WebDAV | | `vget config webdav show ` | Afficher les détails du serveur | | `vget config webdav delete ` | Supprimer un serveur | | `vget telegram login --import-desktop` | Importer la session Telegram depuis l'app de bureau | ### Exemples ```bash vget https://twitter.com/user/status/123456789 vget https://www.xiaoyuzhoufm.com/episode/abc123 vget https://example.com/video -o ma_video.mp4 vget --info https://example.com/video vget search --podcast "tech news" vget pikpak:/path/to/file.mp4 # Téléchargement WebDAV vget ls pikpak:/Movies # Lister un répertoire distant ``` ## Configuration Emplacement du fichier de configuration : | OS | Chemin | | ----------- | --------------------------- | | macOS/Linux | `~/.config/vget/config.yml` | | Windows | `%APPDATA%\vget\config.yml` | Exécutez `vget init` pour créer le fichier de configuration de manière interactive, ou créez-le manuellement : ```yaml language: fr # en, zh, jp, kr, es, fr, de ``` **Note :** La configuration est lue à chaque commande. Pas de redémarrage nécessaire après modification (utile pour Docker). ## Mise à jour Pour mettre à jour vget vers la dernière version : **macOS / Linux :** ```bash sudo vget update ``` **Windows (exécuter PowerShell en tant qu'Administrateur) :** ```powershell vget update ``` ## Langues vget prend en charge plusieurs langues : - English (en) - 中文 (zh) - 日本語 (jp) - 한국어 (kr) - Español (es) - Français (fr) - Deutsch (de) ## Licence Apache License 2.0 ================================================ FILE: README_jp.md ================================================ # vget オーディオ、ビデオ、ポッドキャスト、PDFなどをダウンロードする多機能ツール。CLI と Docker で利用可能。 [English](README.md) | [简体中文](README_zh.md) | [한국어](README_kr.md) | [Español](README_es.md) | [Français](README_fr.md) | [Deutsch](README_de.md) ## インストール ### macOS ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-darwin-arm64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Linux / WSL ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-linux-amd64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Windows [Releases](https://github.com/guiyumin/vget/releases/latest) から `vget-windows-amd64.zip` をダウンロードし、解凍して PATH に追加してください。 ## スクリーンショット ### ダウンロード進捗 ![ダウンロード進捗](screenshots/pikpak_download.png) ### Docker サーバー UI ![](screenshots/vget_server_ui.png) ## Docker ```bash docker run -d -p 8080:8080 -v ~/downloads:/home/vget/downloads ghcr.io/guiyumin/vget:latest ``` ## 対応ソース 対応サイトの一覧は [sites.md](sites.md) をご覧ください。 ## コマンド | コマンド | 説明 | |------------------------------------|---------------------------------------| | `vget [url]` | メディアをダウンロード (`-o`, `-q`, `--info`) | | `vget ls :` | リモートディレクトリを一覧表示 (`--json`) | | `vget init` | 対話式設定ウィザード | | `vget update` | 自動更新(Mac/Linux は `sudo` が必要)| | `vget search --podcast ` | ポッドキャスト検索 | | `vget completion [shell]` | シェル補完スクリプトを生成 | | `vget config show` | 設定を表示 | | `vget config set ` | 設定値を設定(非対話式) | | `vget config get ` | 設定値を取得 | | `vget config path` | 設定ファイルのパスを表示 | | `vget config webdav list` | 設定済み WebDAV サーバー一覧 | | `vget config webdav add ` | WebDAV サーバーを追加 | | `vget config webdav show ` | サーバー詳細を表示 | | `vget config webdav delete ` | サーバーを削除 | | `vget telegram login --import-desktop` | デスクトップアプリから Telegram セッションをインポート | ### 例 ```bash vget https://twitter.com/user/status/123456789 vget https://www.xiaoyuzhoufm.com/episode/abc123 vget https://example.com/video -o my_video.mp4 vget --info https://example.com/video vget search --podcast "tech news" vget pikpak:/path/to/file.mp4 # WebDAV ダウンロード vget ls pikpak:/Movies # リモートディレクトリを一覧表示 ``` ## 設定 設定ファイルの場所: | OS | パス | | ----------- | --------------------------- | | macOS/Linux | `~/.config/vget/config.yml` | | Windows | `%APPDATA%\vget\config.yml` | `vget init` で対話的に設定ファイルを作成するか、手動で作成してください: ```yaml language: jp # en, zh, jp, kr, es, fr, de ``` **注意:** 設定はコマンド実行ごとに読み込まれます。変更後の再起動は不要です(Docker に便利)。 ## 更新 vget を最新バージョンに更新: **macOS / Linux:** ```bash sudo vget update ``` **Windows(管理者として PowerShell を実行):** ```powershell vget update ``` ## 言語 vget は複数の言語をサポートしています: - English (en) - 中文 (zh) - 日本語 (jp) - 한국어 (kr) - Español (es) - Français (fr) - Deutsch (de) ## ライセンス Apache License 2.0 ================================================ FILE: README_kr.md ================================================ # vget 오디오, 비디오, 팟캐스트, PDF 등을 다운로드하는 다목적 도구. CLI 및 Docker로 사용 가능. [English](README.md) | [简体中文](README_zh.md) | [日本語](README_jp.md) | [Español](README_es.md) | [Français](README_fr.md) | [Deutsch](README_de.md) ## 설치 ### macOS ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-darwin-arm64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Linux / WSL ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-linux-amd64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Windows [Releases](https://github.com/guiyumin/vget/releases/latest)에서 `vget-windows-amd64.zip`을 다운로드하고 압축을 푼 후 PATH에 추가하세요. ## 스크린샷 ### 다운로드 진행률 ![다운로드 진행률](screenshots/pikpak_download.png) ### Docker 서버 UI ![](screenshots/vget_server_ui.png) ## Docker ```bash docker run -d -p 8080:8080 -v ~/downloads:/home/vget/downloads ghcr.io/guiyumin/vget:latest ``` ## 지원 소스 지원 사이트 전체 목록은 [sites.md](sites.md)를 참조하세요. ## 명령어 | 명령어 | 설명 | |------------------------------------|---------------------------------------| | `vget [url]` | 미디어 다운로드 (`-o`, `-q`, `--info`) | | `vget ls :` | 원격 디렉토리 목록 (`--json`) | | `vget init` | 대화형 설정 마법사 | | `vget update` | 자동 업데이트 (Mac/Linux는 `sudo` 필요) | | `vget search --podcast ` | 팟캐스트 검색 | | `vget completion [shell]` | 쉘 자동완성 스크립트 생성 | | `vget config show` | 설정 표시 | | `vget config set ` | 설정 값 지정 (비대화형) | | `vget config get ` | 설정 값 가져오기 | | `vget config path` | 설정 파일 경로 표시 | | `vget config webdav list` | 설정된 WebDAV 서버 목록 | | `vget config webdav add ` | WebDAV 서버 추가 | | `vget config webdav show ` | 서버 상세 정보 표시 | | `vget config webdav delete ` | 서버 삭제 | | `vget telegram login --import-desktop` | 데스크톱 앱에서 Telegram 세션 가져오기 | ### 예시 ```bash vget https://twitter.com/user/status/123456789 vget https://www.xiaoyuzhoufm.com/episode/abc123 vget https://example.com/video -o my_video.mp4 vget --info https://example.com/video vget search --podcast "tech news" vget pikpak:/path/to/file.mp4 # WebDAV 다운로드 vget ls pikpak:/Movies # 원격 디렉토리 목록 ``` ## 설정 설정 파일 위치: | OS | 경로 | | ----------- | --------------------------- | | macOS/Linux | `~/.config/vget/config.yml` | | Windows | `%APPDATA%\vget\config.yml` | `vget init`으로 대화형으로 설정 파일을 생성하거나 수동으로 생성하세요: ```yaml language: kr # en, zh, jp, kr, es, fr, de ``` **참고:** 설정은 명령 실행 시마다 새로 읽습니다. 변경 후 재시작이 필요 없습니다 (Docker에 유용). ## 업데이트 vget을 최신 버전으로 업데이트: **macOS / Linux:** ```bash sudo vget update ``` **Windows (관리자 권한으로 PowerShell 실행):** ```powershell vget update ``` ## 언어 vget은 여러 언어를 지원합니다: - English (en) - 中文 (zh) - 日本語 (jp) - 한국어 (kr) - Español (es) - Français (fr) - Deutsch (de) ## 라이선스 Apache License 2.0 ================================================ FILE: README_zh.md ================================================ # vget 多功能下载工具,支持音频、视频、播客、PDF等。提供 CLI 和 Docker 两种方式。 [English](README.md) | [日本語](README_jp.md) | [한국어](README_kr.md) | [Español](README_es.md) | [Français](README_fr.md) | [Deutsch](README_de.md) ## 安装 ### macOS ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-darwin-arm64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Linux / WSL ```bash curl -fsSL https://github.com/guiyumin/vget/releases/latest/download/vget-linux-amd64.zip -o vget.zip unzip vget.zip sudo mv vget /usr/local/bin/ rm vget.zip ``` ### Windows 从 [Releases](https://github.com/guiyumin/vget/releases/latest) 下载 `vget-windows-amd64.zip`,解压后添加到系统 PATH。 ## 截图 ### 下载进度 ![下载进度](screenshots/pikpak_download.png) ### Docker 服务器界面 ![](screenshots/vget_server_ui.png) ## Docker ```bash docker run -d -p 8080:8080 -v ~/downloads:/home/vget/downloads ghcr.io/guiyumin/vget:latest ``` ## 支持的来源 查看 [sites.md](sites.md) 获取完整的支持网站列表。 ## 命令 | 命令 | 描述 | |------------------------------------|---------------------------------------| | `vget [url]` | 下载媒体 (`-o`, `-q`, `--info`) | | `vget ls :` | 列出远程目录 (`--json`) | | `vget init` | 交互式配置向导 | | `vget update` | 自动更新(Mac/Linux 需使用 `sudo`) | | `vget search --podcast ` | 搜索播客 | | `vget completion [shell]` | 生成 shell 补全脚本 | | `vget config show` | 显示配置 | | `vget config set ` | 设置配置值(非交互式) | | `vget config get ` | 获取配置值 | | `vget config path` | 显示配置文件路径 | | `vget config webdav list` | 列出已配置的 WebDAV 服务器 | | `vget config webdav add ` | 添加 WebDAV 服务器 | | `vget config webdav show ` | 显示服务器详情 | | `vget config webdav delete ` | 删除服务器 | | `vget telegram login --import-desktop` | 从桌面应用导入 Telegram 会话 | | `vget kuaidi100 <单号>` | 查询快递物流信息(需配置快递100 API) | ### 示例 ```bash vget https://twitter.com/user/status/123456789 vget https://www.xiaoyuzhoufm.com/episode/abc123 vget https://www.xiaohongshu.com/explore/abc123 # 小红书视频/图片 vget https://example.com/video -o my_video.mp4 vget --info https://example.com/video vget search --podcast "科技" vget pikpak:/path/to/file.mp4 # WebDAV 下载 vget ls pikpak:/Movies # 列出远程目录 ``` ## 配置 配置文件位置: | 操作系统 | 路径 | | ----------- | --------------------------- | | macOS/Linux | `~/.config/vget/config.yml` | | Windows | `%APPDATA%\vget\config.yml` | 运行 `vget init` 交互式创建配置文件,或手动创建: ```yaml language: zh # en, zh, jp, kr, es, fr, de ``` **注意:** 配置文件在每次命令执行时重新读取,修改后无需重启(适用于 Docker)。 ## 更新 将 vget 更新到最新版本: **macOS / Linux:** ```bash sudo vget update ``` **Windows(以管理员身份运行 PowerShell):** ```powershell vget update ``` ## 语言 vget 支持多种语言: - English (en) - 中文 (zh) - 日本語 (jp) - 한국어 (kr) - Español (es) - Français (fr) - Deutsch (de) ## 代理 / 翻墙 如果你需要翻墙(绕过 GFW),推荐使用 Clash。 **Clash 有两种模式:** 1. **系统代理模式** - 设置系统级 HTTP/HTTPS 代理。支持系统代理的应用会自动使用。 2. **TUN 模式** - 创建虚拟网卡,在网络层捕获所有流量。 **推荐使用 TUN 模式**:开启后,所有应用的流量都会自动经过 Clash,无需任何配置。vget 会自动走代理,无需额外设置。 **如果使用系统代理模式**:Clash 会设置 `HTTP_PROXY` / `HTTPS_PROXY` 环境变量,vget 会自动读取并使用这些代理设置。 简而言之:**只要 Clash 正常运行,vget 就能正常工作**,无需在 vget 中配置代理。 ## 许可证 Apache License 2.0 ================================================ FILE: TODO.md ================================================ # TODO ## Tomorrow's Tasks 3. [x] kuaidi100 - Bring Your Own Key (API is expensive) ## Features - [x] `vget init` command - Language preference - Default output directory - Default format/quality - [x] Self update - [x] m3u8 streaming support - [x] Bulk download from txt file - Read URLs from txt file - Sequential or parallel processing - [x] Format/quality selection (`-q` flag) - [x] Audio extraction (podcasts) - [ ] Resume interrupted downloads - [ ] Retry on failure - [x] Progress bar with speed/ETA - [ ] Quiet/verbose modes - [ ] Dry run mode - [ ] More extractors (YouTube, TikTok, etc.) - [ ] Playlist support - [x] Concurrent downloads - [ ] Rate limiting - [x] Cookie/auth support - [ ] Metadata embedding - Audio (MP3/M4A): ID3 tags - title, artist, album, cover art - Video (MP4): title, description, thumbnail - Auto-fill from source (podcast name, episode title, artwork) - Media players (Apple Music, VLC, etc.) would then show this info instead of just the filename. - [x] `vget server` - HTTP server mode - REST API for remote downloads - Run as background daemon (`vget server start -d`) - Web UI for submitting URLs - systemd service installation (`vget server install`) - [x] WebDAV client integration - Connect to PikPak, other WebDAV-compatible cloud storage - Download files from cloud (`vget :`) - Browse and select files with TUI (`vget ls :`) ## Extractors - [x] Twitter/X - [x] Xiaoyuzhou (小宇宙) podcasts - [x] Episode download - [x] Search (`vget search --podcast `) - [x] Podcast listing (all episodes) - [x] YouTube (Docker only, uses yt-dlp/youtube-dl) - [ ] TikTok - [x] Apple Podcasts - [x] Xiaohongshu (小红书/RED) - Requires browser automation (Rod) + cookie auth - Reference: [xpzouying/xiaohongshu-mcp](https://github.com/xpzouying/xiaohongshu-mcp) (7.2k stars, stable 1+ year) - Extraction approach: - Navigate to `https://www.xiaohongshu.com/explore/{feedID}?xsec_token=...` - Extract `window.__INITIAL_STATE__.note.noteDetailMap` via JS - Parse JSON for images (`urlDefault`) and video URLs - Feasibility: Moderate effort, more achievable than Instagram - Note: yt-dlp also has extractor but frequently breaks due to bot detection ## Tracking (Versatile Get) - [ ] FedEx tracking - [ ] Scraping (default, no setup) - [ ] API mode (user provides own keys in config.yml) - [ ] UPS tracking - [ ] Scraping (default, no setup) - [ ] API mode (user provides own keys in config.yml) - [ ] USPS tracking - [ ] Scraping (default, no setup) - [ ] API mode (user provides own keys in config.yml) - [ ] kuaidi100 - Bring Your Own Key (API is expensive) ## DevOps - [x] GoReleaser + GitHub Actions for tagged releases - [x] Dockerfile for NAS deployment - Multi-stage build for minimal image - Support for Synology/QNAP/TrueNAS - compose.yml with NAS path examples ================================================ FILE: cmd/vget/main.go ================================================ package main import ( "os" "github.com/guiyumin/vget/internal/cli" ) func main() { if err := cli.Execute(); err != nil { os.Exit(1) } } ================================================ FILE: cmd/vget-server/main.go ================================================ package main import ( "context" "flag" "fmt" "log" "os" "os/signal" "path/filepath" "syscall" "time" "github.com/guiyumin/vget/internal/core/config" "github.com/guiyumin/vget/internal/core/version" "github.com/guiyumin/vget/internal/server" ) func main() { // Command-line flags port := flag.Int("port", 0, "HTTP listen port (default: 8080)") output := flag.String("output", "", "output directory for downloads") showVersion := flag.Bool("version", false, "show version") flag.Parse() if *showVersion { fmt.Printf("vget-server %s\n", version.Version) return } // Load configuration cfg := config.LoadOrDefault() // Resolve port (flag > config > default) serverPort := *port if serverPort == 0 { if cfg.Server.Port > 0 { serverPort = cfg.Server.Port } else { serverPort = 8080 } } // Resolve output directory (flag > config > default) outputDir := *output if outputDir == "" { if cfg.OutputDir != "" { outputDir = cfg.OutputDir } else { outputDir = config.DefaultDownloadDir() } } // Expand ~ in path if len(outputDir) >= 2 && outputDir[:2] == "~/" { home, _ := os.UserHomeDir() outputDir = filepath.Join(home, outputDir[2:]) } // Resolve max concurrent (config > default) maxConcurrent := cfg.Server.MaxConcurrent if maxConcurrent <= 0 { maxConcurrent = 10 } // Get API key from config apiKey := cfg.Server.APIKey // Create and start server srv := server.NewServer(serverPort, outputDir, apiKey, maxConcurrent) // Handle graceful shutdown sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) go func() { <-sigChan log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Stop(ctx) }() log.Printf("Starting vget server on port %d", serverPort) log.Printf("Output directory: %s", outputDir) if err := srv.Start(); err != nil { log.Fatalf("Server error: %v", err) } } ================================================ FILE: compose.yml ================================================ # vget Docker Compose # # Image variants (choose based on your system): # ghcr.io/guiyumin/vget:latest - No models, downloads on first use (~200MB) # ghcr.io/guiyumin/vget:full-small - Whisper Small bundled (~500MB) # ghcr.io/guiyumin/vget:full-medium - Whisper Medium bundled (~1.5GB) # ghcr.io/guiyumin/vget:full-large - Whisper Large V3 Turbo bundled (~1.8GB) # # Whisper models support 99 languages including Chinese, Japanese, Korean. # # Configure via .env file or environment variables: # VGET_PORT=8080 # Web UI port # VGET_DOWNLOADS=./downloads # Download directory # VGET_CONFIG=./config # Config directory # TZ=Asia/Shanghai # Timezone # # Example paths by NAS: # Synology: /volume1/vget/downloads # QNAP: /share/vget/downloads # Unraid: /mnt/user/vget/downloads # TrueNAS: /mnt/pool/vget/downloads services: vget: # Change to :full-small, :full-medium, or :full-large based on your RAM/GPU image: ghcr.io/guiyumin/vget:latest container_name: vget restart: unless-stopped ports: - "${VGET_PORT:-8080}:8080" volumes: - ${VGET_DOWNLOADS:?Set VGET_DOWNLOADS in .env}:/home/vget/downloads - ${VGET_CONFIG:?Set VGET_CONFIG in .env}:/home/vget/.config/vget environment: - TZ=${TZ:-Asia/Shanghai} # Required for NAS systems (Synology, QNAP, etc.) to allow ffmpeg thread creation security_opt: - seccomp:unconfined ================================================ FILE: docker/vget/Dockerfile ================================================ # vget Docker Image # Build stage for UI FROM node:22-slim AS ui-builder WORKDIR /app/ui COPY ui/package*.json ./ RUN npm ci COPY ui/ ./ RUN npm run build # Go builder stage FROM golang:1.25-bookworm AS go-builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . COPY --from=ui-builder /app/ui/dist ./internal/server/dist RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /vget-server ./cmd/vget-server # Runtime stage FROM debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ chromium \ fonts-noto-cjk \ fonts-noto-color-emoji \ python3 \ python3-pip \ python3-venv \ ffmpeg \ nodejs \ gosu \ curl \ bzip2 \ && rm -rf /var/lib/apt/lists/* RUN pip3 install --no-cache-dir --break-system-packages \ yt-dlp \ youtube-dl RUN (getent group 1000 >/dev/null || groupadd -g 1000 vget) && \ (id -u 1000 >/dev/null 2>&1 || useradd -u 1000 -g 1000 -m -d /home/vget vget) && \ mkdir -p /home/vget/downloads /home/vget/.config/vget && \ chown -R 1000:1000 /home/vget COPY --from=go-builder /vget-server /usr/local/bin/vget-server ENV ROD_BROWSER=/usr/bin/chromium COPY docker/vget/entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/entrypoint.sh WORKDIR /home/vget EXPOSE 8080 VOLUME ["/home/vget/downloads", "/home/vget/.config/vget"] ENTRYPOINT ["entrypoint.sh"] CMD [] ================================================ FILE: docker/vget/Dockerfile.arm64 ================================================ # vget Docker Image for ARM64 (Apple Silicon, Raspberry Pi, ARM servers) # Build stage for UI FROM node:22-slim AS ui-builder WORKDIR /app/ui COPY ui/package*.json ./ RUN npm ci COPY ui/ ./ RUN npm run build # Go builder stage FROM golang:1.25-bookworm AS go-builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . COPY --from=ui-builder /app/ui/dist ./internal/server/dist RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /vget-server ./cmd/vget-server # Runtime stage FROM debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ chromium \ fonts-noto-cjk \ fonts-noto-color-emoji \ python3 \ python3-pip \ python3-venv \ ffmpeg \ nodejs \ gosu \ curl \ bzip2 \ && rm -rf /var/lib/apt/lists/* RUN pip3 install --no-cache-dir --break-system-packages \ yt-dlp \ youtube-dl RUN (getent group 1000 >/dev/null || groupadd -g 1000 vget) && \ (id -u 1000 >/dev/null 2>&1 || useradd -u 1000 -g 1000 -m -d /home/vget vget) && \ mkdir -p /home/vget/downloads /home/vget/.config/vget && \ chown -R 1000:1000 /home/vget COPY --from=go-builder /vget-server /usr/local/bin/vget-server ENV ROD_BROWSER=/usr/bin/chromium COPY docker/vget/entrypoint-arm64.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh WORKDIR /home/vget EXPOSE 8080 VOLUME ["/home/vget/downloads", "/home/vget/.config/vget"] ENTRYPOINT ["entrypoint.sh"] CMD [] ================================================ FILE: docker/vget/entrypoint-arm64.sh ================================================ #!/bin/bash set -e # Fix ownership of mounted volumes if running as root if [ "$(id -u)" = "0" ]; then chown -R 1000:1000 /home/vget/downloads /home/vget/.config/vget exec gosu 1000:1000 vget-server "$@" else exec vget-server "$@" fi ================================================ FILE: docker/vget/entrypoint.sh ================================================ #!/bin/bash set -e # Fix ownership of mounted volumes if running as root if [ "$(id -u)" = "0" ]; then chown -R 1000:1000 /home/vget/downloads /home/vget/.config/vget exec gosu 1000:1000 vget-server "$@" else exec vget-server "$@" fi ================================================ FILE: docs/FAQs.md ================================================ # FAQs & Troubleshooting ## FFmpeg Merge Failed: thread_create failed **Error message:** ``` ffmpeg merge failed: thread_create failed: Operation not permitted. Try to increase 'ulimit -v' or decrease 'ulimit -s'. ``` **Cause:** This is a system resource limitation, not a vget bug. FFmpeg cannot create threads due to OS-level restrictions on your system. **Common scenarios:** - Running in a Docker container with restricted resources - VPS or shared hosting with strict ulimit settings - Systems with low thread/memory limits **Solutions:** ### If running in Docker Add ulimit settings to your container: **compose.yml:** ```yaml services: vget: image: your-vget-image ulimits: nproc: 65535 nofile: soft: 65535 hard: 65535 ``` **docker run:** ```bash docker run --ulimit nproc=65535 --ulimit nofile=65535:65535 your-vget-image ``` ### If running on bare Linux Adjust ulimit settings before running vget: ```bash # Reduce stack size ulimit -s 8192 # Or increase virtual memory limit ulimit -v unlimited ``` To make changes permanent, edit `/etc/security/limits.conf`: ``` * soft nproc 65535 * hard nproc 65535 * soft nofile 65535 * hard nofile 65535 ``` ### Alternative workaround If you cannot change system limits, download video and audio separately without merging (if supported by the source). ================================================ FILE: docs/PRD.md ================================================ # vget – Product Requirement Document (PRD) **Version:** 1.2 **Author:** Yumin **Language:** Golang **UI:** Bubble Tea (TUI) **Purpose:** A modern, multi-source video downloader with elegant CLI & TUI. --- ## 1. Product Vision & Core Positioning ### One-Line Vision **vget:** A modern, minimalist, high-speed video downloader that works like wget, with a beautiful Bubble Tea TUI. Starting with X/Twitter, expanding to more platforms. ### Core Philosophy vget's core value is not "protocol-level innovation", but rather: - **Ultimate user experience** - Simple CLI, beautiful TUI - **Single binary distribution** - No Python/Node dependencies - **Clean architecture** - Extensible extractor system - **Modern developer experience** - Golang + Bubble Tea + Worker Pool ### Why Not Just Use yt-dlp? | Aspect | yt-dlp | vget | | ------------ | ------------ | --------------------- | | Installation | Python + pip | Single binary | | UI | CLI only | CLI + Bubble Tea TUI | | Complexity | 500+ flags | Minimal, opinionated | | Focus | 1000+ sites | Quality over quantity | vget aims to be the "modern wget for videos" - simple, fast, beautiful. --- ## 2. Product Goals ### 2.1 MVP Goals (v0.1 - Twitter Focus) **Target:** Working Twitter/X video downloader - [x] Project structure setup - [x] Twitter/X extractor (native Go, no yt-dlp dependency) ✅ - Bearer token + guest token authentication - Tweet API parsing - Video variant extraction (multiple qualities) - [ ] Direct MP4 downloader with progress bar - [ ] HLS (.m3u8) support (Twitter uses this for some videos) - [x] Simple CLI: `vget ` ✅ - [x] Auto-select best quality ✅ - [ ] Basic retry on failure ### 2.2 v0.2 Goals - [x] Multi-threaded segmented downloads (range requests) ✅ **Implemented** - [x] Output filename customization (`-o`) ✅ ### 2.3 v0.3 Goals - Bubble Tea TUI (`vget --ui`) - More platform extractors (based on demand) - Optional yt-dlp bridge for unsupported sites --- ## 3. User Experience (UX) Goals ### CLI Minimalism ```bash vget https://example.com/video ``` ### TUI Mode (Bubble Tea) ```bash vget --ui URL ``` ### Display Features - Per-thread speed - Total speed - ETA - Progress bar - Task queue - Pause/Resume capability - Download history ### Automatic Content Type Detection ``` URL → Extractor → (MP4 / HLS / DASH / Playlist) ``` **Fully automatic:** Users don't need to think about the underlying protocol. --- ## 4. Feature Specification ### 4.1 Downloader Engine (Core) | Feature | Description | Status | | --------------------- | ----------------------------------------------------- | -------------- | | Multi-Stream Download | HTTP Range requests with parallel streams (default 8) | ✅ Implemented | | Concurrent Download | goroutine + worker pool pattern | ✅ Implemented | | Chunk-based Transfer | 16MB chunks with 128KB buffers per stream | ✅ Implemented | | Progress Display | Real-time speed, ETA, elapsed time, avg speed | ✅ Implemented | | Auto Retry | Exponential backoff retry (5 retries per chunk) | ✅ Implemented | | File Merge | Merge multiple segments into MP4 | Planned | | Verification | Support md5/sha256 (optional) | Planned | | Speed Limit | Throttle mode (optional) | Planned | | Download Queue | Multiple simultaneous tasks | Planned | #### Multi-Stream Download Architecture (Implemented) ``` ┌─────────────────────────────────────────────────────────────┐ │ MultiStreamConfig │ │ Streams: 8 (parallel connections) │ │ ChunkSize: 16MB (per chunk) │ │ BufferSize: 128KB (per stream read buffer) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ HEAD Request (Check Support) │ │ - Get Content-Length │ │ - Check Accept-Ranges: bytes │ └─────────────────────────────────────────────────────────────┘ │ ┌───────────────┴───────────────┐ ▼ ▼ [Range Supported] [Range Not Supported] │ │ ▼ ▼ ┌─────────────────────────┐ ┌─────────────────────────┐ │ Calculate Chunks │ │ Single-Stream Fallback │ │ File ÷ ChunkSize │ │ (128KB buffer) │ └─────────────────────────┘ └─────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Worker Pool (8 workers) │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ W1 │ │ W2 │ │ W3 │ │ W4 │ │ W5 │ │ W6 │ │ W7 │...│ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ │ │ Range: Range: Range: Range: Range: Range: Range: │ │ 0-16M 16M-32M 32M-48M ... │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ file.WriteAt(data, offset) │ │ (Thread-safe positional writes) │ └─────────────────────────────────────────────────────────────┘ ``` **Performance Comparison:** | Metric | Before (Single Stream) | After (Multi-Stream) | | -------------- | ---------------------- | ------------------------ | | Streams | 1 | 8 (configurable) | | Buffer | 32KB | 128KB per stream | | Typical Speed | ~10-20 MB/s | ~50-80 MB/s | | WebDAV Support | Basic | Full with Range requests | ### 4.2 Extractor Layer (URL Parsing) #### Extractor Interface ```go type Extractor interface { // Match returns true if this extractor can handle the URL Match(url string) bool // Extract returns video info (title, formats, etc.) Extract(url string) (*VideoInfo, error) } type VideoInfo struct { ID string Title string Formats []Format // Multiple qualities available Duration int } type Format struct { URL string Quality string // "1080p", "720p", etc. Ext string // "mp4", "m3u8" Width int Height int Bitrate int } ``` #### Supported Extractors | Extractor | Status | Notes | | ------------- | ------ | ------------------------ | | Twitter/X | MVP | Native Go implementation | | Direct MP4 | MVP | Content-Type detection | | HLS | MVP | m3u8 parsing | | DASH | v0.2 | mpd XML parsing | | yt-dlp bridge | v0.3 | Optional fallback | #### Twitter/X Extractor Details ``` URL: https://x.com/user/status/123456789 ↓ Extract tweet ID ↓ Get guest token (POST /1.1/guest/activate.json) ↓ Fetch tweet (GET /1.1/statuses/show/{id}.json) ↓ Parse extended_entities.media[].video_info.variants ↓ Return VideoInfo with all quality options ``` ### 4.3 CLI Specification ```bash # Basic download vget # Specify quality vget -q 1080p # Segment thread count vget -t 32 # Output filename vget -o out.mp4 # Cookie vget --cookies cookies.txt # Custom headers vget -H "Referer: https://xxx" # Parse only, don't download vget --info # Configuration management vget init # Interactive config wizard (TUI) vget config show # Show current config vget config set language en # Set config value (non-interactive) vget config get language # Get config value ``` ### 4.4 TUI (Bubble Tea) Design #### Components - Header (speed, ETA) - Global progress bar - Per-thread speed bars - Error messages - Undo/Pause/Resume controls - Log window - Task queue #### Keyboard Shortcuts | Key | Function | | ------- | ------------ | | `space` | Pause/Resume | | `p` | Pause | | `r` | Retry | | `q` | Quit | | `↑↓` | Switch tasks | #### TUI Aesthetic - lipgloss + Nord theme - Clean and minimalist - Style similar to glow, gh-dash, gum --- ## 5. Architecture Design ``` /cmd/vget main.go # Entry point, CLI parsing /internal /cli root.go # Main command & WebDAV download handler config.go # Config management commands extract.go # Extraction with spinner ls.go # Directory listing command search.go # Search command completion.go # Shell completion /extractor extractor.go # Extractor interface & media types twitter.go # Twitter/X extractor xiaoyuzhou.go # Xiaoyuzhou podcast extractor instagram.go # Instagram extractor tiktok.go # TikTok extractor xiaohongshu.go # Xiaohongshu extractor registry.go # Extractor registration & matching /downloader downloader.go # Download interface progress.go # Progress tracking & Bubble Tea TUI multistream.go # Multi-stream parallel downloader ✅ NEW utils.go # Helper functions /webdav client.go # WebDAV client with Range request support ✅ NEW /config config.go # User configuration & WebDAV servers # IMPORTANT: Config is read fresh per-command (no restart needed) /i18n i18n.go # Internationalization /locales/*.yml # Translation files (en, zh, jp, kr, es, fr, de) /updater updater.go # Self-update functionality /version version.go # Version info ``` --- ## 6. Technical Implementation Details ### 6.1 Extractor Logic **Pseudocode:** ``` if url endsWith .mp4 → MP4Extractor if content-type == application/vnd.apple.mpegurl → HLSExtractor if content-type == application/dash+xml → DASHExtractor if URL contains "playlist" → PlaylistExtractor ``` #### HLS Flow 1. Download m3u8 2. Find master playlist 3. Select highest bitrate 4. Parse TS segments 5. Build task list in order #### DASH Flow 1. Download mpd XML 2. Extract mediaBaseURL + segmentTemplate 3. Select a Representation 4. Generate task list for all segments ### 6.2 Downloader Engine (Implemented) **Multi-Stream Configuration:** ```go type MultiStreamConfig struct { Streams int // Number of parallel streams (default 8) ChunkSize int64 // Size of each chunk (default 16MB) BufferSize int // Buffer size per stream (default 128KB) } ``` **Worker Pool Pattern:** ```go // Create chunk channel and feed all chunks chunkChan := make(chan chunk, len(chunks)) for _, c := range chunks { chunkChan <- c } close(chunkChan) // Start N worker goroutines for i := 0; i < config.Streams; i++ { go func() { for c := range chunkChan { downloadChunk(ctx, client, url, file, c, state) } }() } ``` **Chunk Download with Range Requests:** ```go req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", chunk.start, chunk.end)) // ... file.WriteAt(data, offset) // Thread-safe positional write ``` **Progress Tracking:** ```go type downloadState struct { current int64 // Atomic counter across all streams total int64 speed float64 // Real-time speed startTime time.Time endTime time.Time finalSpeed float64 // Average speed at completion } ``` ### 6.3 WebDAV Support (Implemented) **Features:** - Remote path syntax: `vget pikpak:/path/to/file.mp4` - Full URL syntax: `vget webdav://user:pass@host/path` - Multi-stream parallel downloads with HTTP Range requests - Automatic fallback to single-stream if Range not supported - Directory listing: `vget ls pikpak:/movies` **WebDAV Client Architecture:** ```go type Client struct { client *webdav.Client // go-webdav for PROPFIND/etc baseURL string username string password string } // Methods func (c *Client) Stat(ctx, path) (*FileInfo, error) func (c *Client) List(ctx, path) ([]FileInfo, error) func (c *Client) Open(ctx, path) (io.ReadCloser, int64, error) func (c *Client) GetFileURL(path) string // For Range requests func (c *Client) GetAuthHeader() string // Basic Auth header func (c *Client) SupportsRangeRequests(ctx, path) (bool, error) ``` **Download Flow:** ``` pikpak:/movies/video.mp4 │ ▼ ┌─────────────────────┐ │ Load config.yml │ │ Get server creds │ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ client.Stat() │ │ Get file size │ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ HEAD request │ │ Check Range support│ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ Multi-stream DL │ │ 8 parallel streams │ └─────────────────────┘ ``` ### 6.4 Merge (mp4 / ts / m4s) **HLS:** ```bash cat part*.ts | ffmpeg -i - -c copy out.mp4 ``` **DASH:** - mp4box or pure Go mux (can be supported after v1) --- ## 7. Future Roadmap ### TODO - **Optimize download speed for WebDAV/PikPak** - Current multi-stream implementation is significantly slower than rclone. Target: 30MB/s for PikPak. Investigate: - Connection reuse / keep-alive - Chunk size tuning - Number of parallel streams - Buffer sizes - TCP tuning ### v1 (MVP) - MP4 / HLS / DASH download - CLI - TUI - Multi-threaded segmentation - Resume support - Auto quality detection ### v1.5 - Multi-task queue - History records - Graceful pause/resume ### v2 - Plugin system (extractor plugins) - `.vget/plugins/*.wasm` for custom site loaders ### v3 - Distributed downloading - Integration with S3 / OSS / R2 - Become a true "media download platform" --- ## 8. Success Metrics | Metric | Target | | -------------- | -------------------------------------- | | GitHub Stars | 1,000 (first month) / 5,000 (6 months) | | CLI Installs | 5K+ | | TUI Open Rate | > 40% | | Issue Feedback | > 20 (community engagement) | | Pull Requests | At least 5 external contributors | --- ## 9. Top Selling Points (Highlight in README) - **Modern video downloader** - **Fast, concurrent, resumable** - **HLS & DASH built-in** - **Beautiful Bubble Tea TUI** - **Cross-platform single binary** - **Plugin ecosystem (future)** --- ## 10. README Sample ``` vget ---- A modern, blazing-fast video downloader for the command line. Supports MP4, HLS (m3u8), DASH (mpd), multi-thread downloads, resume, cookies, proxies, and a beautiful Bubble Tea-powered TUI. Usage: vget # auto detect and download vget --ui # open interactive TUI vget -t 32 # 32-thread segmented download vget -q 1080p # choose quality (HLS/DASH) vget --cookies c.txt # cookie support ``` ================================================ FILE: docs/YOUTUBE_NOTES.md ================================================ # YouTube Support Notes ## Status: Delegated to yt-dlp (Docker Only) After extensive research and failed attempts, we've concluded that building a native Go YouTube extractor is not viable. ## What We Tried (2025-12-04) ### The Go Implementation Worked... Briefly - Browser automation (Rod + stealth) captured BotGuard tokens - Innertube API with iOS client returned unencrypted stream URLs (no cipher) - Separate video/audio streams downloaded and merged with ffmpeg ### Then It Broke 1. **BotGuard Detection** - YouTube's anti-bot (Error 153) detected rod/stealth automation 2. **IP Binding** - Stream URLs are bound to the requesting IP; VPNs/IPv6 cause 403s 3. **Rate Limiting** - Heavy testing flagged our IP/session; even new IPs didn't help 4. **Constant Changes** - YouTube updates anti-bot weekly; we can't keep up ## Why We Don't Build Our Own Extractor ### The Problems Are Real 1. **Aggressive Anti-Bot Detection** - PO Tokens (Proof of Origin) require JavaScript execution - N parameter challenge requires solving obfuscated JS functions - SAPISID hash authentication with rotating signatures - Client version checks that change frequently - Rate limiting that bans IPs quickly 2. **Constantly Moving Target** - YouTube updates their anti-bot mechanisms weekly - yt-dlp has 1000+ contributors constantly reverse-engineering changes - A solo developer cannot keep up with Google's anti-bot team 3. **IP Bans Are Inevitable** - Even with all the right tokens and signatures, YouTube rate-limits aggressively - Residential IPs get banned after moderate usage - Datacenter IPs are blocked almost immediately 4. **Resource Requirements** - Requires JavaScript runtime (Node.js/Deno) for challenge solving - Needs rotating residential proxies ($$$) - Cookie/session management is complex ## Our Solution We delegate YouTube extraction to **yt-dlp** and **youtube-dl**, but only in Docker: - **In Docker**: vget shells out to yt-dlp/youtube-dl - **Outside Docker**: vget shows an error suggesting Docker usage ### Why Docker Only? 1. Windows/Mac users won't have Python installed 2. Bundling yt-dlp in the Go binary is impractical 3. Docker image includes all dependencies (Python, ffmpeg, Node.js) 4. NAS users (Synology, QNAP, Unraid) commonly use Docker ## User Responsibilities **IMPORTANT**: Users must provide their own infrastructure: 1. **Residential Proxy / Rotating IPs** - YouTube will ban datacenter IPs and rate-limit residential IPs. Users need to configure their own proxy solution. **This is not optional for sustained usage.** 2. **Cookies (Optional)** - For age-restricted or premium content, users can mount a cookies file. 3. **Rate Limiting** - Users should use `--sleep-interval` with yt-dlp to avoid bans. ## Usage ```bash # Basic usage (user handles proxy externally) docker run -v ~/downloads:/downloads guiyumin/vget "https://youtube.com/watch?v=xxx" # With proxy configured in environment docker run -e HTTP_PROXY=http://proxy:port -v ~/downloads:/downloads guiyumin/vget "https://youtube.com/watch?v=xxx" # With cookies file for premium/age-restricted content docker run -v ~/downloads:/downloads -v ~/cookies.txt:/home/vget/cookies.txt guiyumin/vget "https://youtube.com/watch?v=xxx" ``` ## Alternatives for Users If Docker isn't an option, users should use yt-dlp directly: ```bash # Install yt-dlp pip install yt-dlp # Download video yt-dlp "https://youtube.com/watch?v=xxx" # With proxy yt-dlp --proxy http://proxy:port "https://youtube.com/watch?v=xxx" ``` ## Old Troubleshooting (For Reference) These were issues with our native Go implementation: ### 403 on download 1. Clear browser profile: `rm -rf ~/.config/vget/browser/` 2. Disable IPv6: `sudo networksetup -setv6off Wi-Fi` 3. Try a different network/IP 4. Wait for rate limiting to expire (24-48 hours) ### No POToken captured - YouTube detecting automation (Error 153) - go-rod/stealth needs constant updates ### IP mismatch - VPN must tunnel ALL traffic (not just browser) - Disable IPv6 to force IPv4 - Browser, API call, and download must use same IP ## References - [yt-dlp GitHub](https://github.com/yt-dlp/yt-dlp) - [youtube-dl GitHub](https://github.com/ytdl-org/youtube-dl) - [yt-dlp Wiki: Rate Limiting](https://github.com/yt-dlp/yt-dlp/wiki/Extractors#this-content-isnt-available-try-again-later) ## Lessons Learned 1. Don't fight Google's anti-bot team alone 2. Leverage existing open-source solutions (yt-dlp has 1000+ contributors) 3. Make infrastructure (proxies, IPs) the user's responsibility 4. Docker is the right abstraction for complex dependencies 5. Know when to give up and delegate ================================================ FILE: docs/bilibili-port-plan.md ================================================ # BBDown Go Port Plan This document outlines the plan for porting [BBDown](https://github.com/nilaoda/BBDown) (a C# Bilibili downloader) to Go, integrating it into vget. ## Overview **BBDown** is a comprehensive Bilibili downloader with ~6,500 lines of C# code. Key capabilities: - Download videos, anime (Bangumi), courses (Cheese), playlists - Multiple quality levels (144P to 8K) and codecs (AVC, HEVC, AV1) - Audio formats: AAC, FLAC, Dolby Atmos, E-AC-3 - Authentication via QR code or cookie/token - Subtitles, danmaku (bullet comments), cover images - FFmpeg/MP4Box muxing integration ## Architecture Comparison ### BBDown (C#) ``` BBDown/ # CLI Application ├── Program.cs # Entry point (897 lines) ├── CommandLineInvoker.cs # CLI parsing ├── BBDownUtil.cs # URL parsing utilities ├── BBDownDownloadUtil.cs # HTTP download logic ├── BBDownMuxer.cs # Audio/video muxing ├── BBDownLoginUtil.cs # QR code login └── Model/ # Data models BBDown.Core/ # Core library ├── Parser.cs # API response parsing (467 lines) ├── AppHelper.cs # gRPC/Protobuf for APP API ├── Config.cs # Global configuration ├── FetcherFactory.cs # Content type routing ├── IFetcher.cs # Fetcher interface ├── Entity/ # Data models ├── Fetcher/ # Content type handlers │ ├── NormalInfoFetcher.cs # Regular videos │ ├── BangumiInfoFetcher.cs # Anime │ ├── CheeseInfoFetcher.cs # Courses │ └── ... # Others └── Util/ # HTTP, subtitles, danmaku ``` ### vget Target (Go) ``` internal/extractor/ ├── bilibili.go # Main extractor + interface ├── bilibili_api.go # API client (WEB/TV/APP/INTL) ├── bilibili_parser.go # Stream parsing ├── bilibili_auth.go # Authentication (QR, cookie) ├── bilibili_fetcher.go # Fetcher interface + factory ├── bilibili_fetcher_normal.go # Regular videos ├── bilibili_fetcher_bangumi.go # Anime ├── bilibili_fetcher_cheese.go # Courses ├── bilibili_fetcher_space.go # User uploads ├── bilibili_fetcher_list.go # Playlists/collections ├── bilibili_subtitle.go # Subtitle processing ├── bilibili_danmaku.go # Bullet comments └── bilibili_proto/ # Generated protobuf (for APP API) ``` ## Bilibili API Structure BBDown supports 4 different APIs for accessing content: | API | Endpoint | Auth Method | Use Case | | ---- | -------------------- | ------------------- | --------------------- | | WEB | api.bilibili.com | Cookie (SESSDATA) | Standard access | | TV | api.snm0516.aisee.tv | App key + signature | Unrestricted streams | | APP | grpc.biliapi.net | gRPC + Protobuf | FLAC, Dolby, 8K | | INTL | api.biliintl.com | Similar to WEB | International content | ### WBI Signature (WEB API) Bilibili uses a dynamic signature scheme called WBI: 1. Extract `img_key` and `sub_key` from website HTML 2. Combine and reorder using a fixed mapping table 3. Generate MD5 signature of parameters + key 4. Keys rotate periodically (need to refresh) ### APP API (gRPC) The APP API uses Protocol Buffers with custom headers: - Device info (Dalvik, Android version) - Access key authentication - Protobuf request/response encoding ## Implementation Phases ### Phase 1: Foundation (Priority: High) **Goal**: Basic video download for regular Bilibili videos #### 1.1 Data Models ```go // internal/extractor/bilibili.go type BilibiliVideoInfo struct { AID int64 // av number BVID string // BV number CID int64 // cid (for video stream) Title string Desc string Pic string // cover URL Duration int64 Pages []Page // multi-part videos } type Page struct { CID int64 Page int Title string Duration int64 } type VideoStream struct { Quality int // 127=8K, 120=4K, 116=1080P60, etc. Codec string // avc, hevc, av1 URL string Bandwidth int64 Width int Height int } type AudioStream struct { Quality int // 30280=320kbps, 30232=128kbps, 30216=64kbps Codec string // mp4a, flac, ec-3 URL string Bandwidth int64 } ``` #### 1.2 URL Pattern Matching ```go // Match patterns: // - https://www.bilibili.com/video/BV1xx411c7mD // - https://www.bilibili.com/video/av170001 // - https://b23.tv/BV1xx411c7mD (short URL) // - bilibili://video/170001 func (e *BilibiliExtractor) Match(url string) bool { patterns := []string{ `bilibili\.com/video/(BV[\w]+|av\d+)`, `b23\.tv/(BV[\w]+|av\d+|\w+)`, `bilibili://video/\d+`, } // ... } ``` #### 1.3 BV/AV ID Conversion ```go // AV to BV and vice versa (algorithm from BBDown) // BV is base58-like encoding of AV number const table = "fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF" var s = []int{11, 10, 3, 8, 4, 6} const xor = 177451812 const add = 8728348608 func BV2AV(bv string) int64 { ... } func AV2BV(av int64) string { ... } ``` #### 1.4 WEB API Client ```go // internal/extractor/bilibili_api.go type BilibiliClient struct { httpClient *http.Client cookie string wbi *WBIKeys // signature keys } func (c *BilibiliClient) GetVideoInfo(bvid string) (*BilibiliVideoInfo, error) { // GET https://api.bilibili.com/x/web-interface/view?bvid=xxx } func (c *BilibiliClient) GetPlayURL(bvid string, cid int64, qn int) (*PlayURLResponse, error) { // GET https://api.bilibili.com/x/player/wbi/playurl?bvid=xxx&cid=xxx&qn=xxx // Requires WBI signature } ``` #### 1.5 Stream Extraction ```go // Parse playurl API response to extract video/audio streams func (c *BilibiliClient) ExtractStreams(resp *PlayURLResponse) ([]VideoStream, []AudioStream, error) { // Handle both DASH (video+audio separate) and legacy FLV formats } ``` ### Phase 2: Authentication (Priority: High) **Goal**: Support both QR code login and manual cookie input, in both CLI and UI. #### 2.1 Authentication Architecture ``` internal/ ├── extractor/ │ └── bilibili_auth.go # Core auth logic (API calls, token storage) ├── cli/ │ └── bilibili_login.go # CLI: ASCII QR + cookie prompt └── ui/ # (existing React UI) └── components/ └── BilibiliLogin.tsx # UI: Image QR + cookie input field ``` #### 2.2 Core Auth Module ```go // internal/extractor/bilibili_auth.go type BilibiliAuth struct { configPath string // ~/.config/vget/bilibili.json } type BilibiliCredentials struct { SESSDATA string `json:"sessdata"` BiliJCT string `json:"bili_jct"` DedeUserID string `json:"dede_user_id"` ExpiresAt time.Time `json:"expires_at"` } // QR Code Login Flow type QRLoginSession struct { URL string // QR code content URL QRCodeKey string // Key for polling status } func (a *BilibiliAuth) GenerateQRCode() (*QRLoginSession, error) { // GET https://passport.bilibili.com/x/passport-login/web/qrcode/generate // Returns: { data: { url: "...", qrcode_key: "..." } } } type QRStatus int const ( QRWaiting QRStatus = 86101 // Not scanned yet QRScanned QRStatus = 86090 // Scanned, waiting confirm QRExpired QRStatus = 86038 // QR code expired QRConfirmed QRStatus = 0 // Success ) func (a *BilibiliAuth) PollQRStatus(qrcodeKey string) (QRStatus, *BilibiliCredentials, error) { // GET https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=xxx // Returns status code and credentials on success } // Manual Cookie func (a *BilibiliAuth) SetCookie(cookie string) (*BilibiliCredentials, error) { // Parse cookie string: "SESSDATA=xxx; bili_jct=xxx; DedeUserID=xxx" // Validate by calling user info API // Save to config file } // Token Storage func (a *BilibiliAuth) SaveCredentials(creds *BilibiliCredentials) error { // Save to ~/.config/vget/bilibili.json } func (a *BilibiliAuth) LoadCredentials() (*BilibiliCredentials, error) { // Load from ~/.config/vget/bilibili.json // Return nil if not found or expired } func (a *BilibiliAuth) GetCookieString() string { // Return formatted cookie for HTTP requests // "SESSDATA=xxx; bili_jct=xxx; DedeUserID=xxx" } ``` #### 2.3 CLI Login Interface ```go // internal/cli/bilibili_login.go // Command: vget login bilibili func BilibiliLoginCmd() *cobra.Command { cmd := &cobra.Command{ Use: "bilibili", Short: "Login to Bilibili", } cmd.AddCommand( bilibiliQRLoginCmd(), // vget login bilibili qr bilibiliCookieLoginCmd(), // vget login bilibili cookie ) return cmd } // QR Login in Terminal func bilibiliQRLogin() error { auth := extractor.NewBilibiliAuth() // 1. Generate QR code session, _ := auth.GenerateQRCode() // 2. Display ASCII QR in terminal qr, _ := qrcode.New(session.URL, qrcode.Medium) fmt.Println(qr.ToSmallString(false)) fmt.Println("Scan with Bilibili app, or open:", session.URL) // 3. Poll for confirmation for { status, creds, _ := auth.PollQRStatus(session.QRCodeKey) switch status { case extractor.QRWaiting: // Show spinner case extractor.QRScanned: fmt.Println("Scanned! Please confirm in app...") case extractor.QRExpired: return errors.New("QR code expired") case extractor.QRConfirmed: auth.SaveCredentials(creds) fmt.Println("Login successful!") return nil } time.Sleep(time.Second) } } // Cookie Login in Terminal func bilibiliCookieLogin() error { fmt.Println("Enter your Bilibili cookie (SESSDATA=xxx; bili_jct=xxx):") reader := bufio.NewReader(os.Stdin) cookie, _ := reader.ReadString('\n') auth := extractor.NewBilibiliAuth() creds, err := auth.SetCookie(strings.TrimSpace(cookie)) if err != nil { return fmt.Errorf("invalid cookie: %w", err) } fmt.Printf("Login successful! User ID: %s\n", creds.DedeUserID) return nil } ``` #### 2.4 UI Login Interface ```typescript // ui/components/BilibiliLogin.tsx interface QRLoginState { qrUrl: string; qrCodeKey: string; status: 'waiting' | 'scanned' | 'expired' | 'success'; } export function BilibiliLogin() { const [mode, setMode] = useState<'qr' | 'cookie'>('qr'); const [qrState, setQrState] = useState(null); const [cookie, setCookie] = useState(''); // QR Code Login async function startQRLogin() { const session = await api.bilibili.generateQR(); setQrState({ qrUrl: session.url, qrCodeKey: session.qrcode_key, status: 'waiting' }); pollQRStatus(session.qrcode_key); } async function pollQRStatus(key: string) { const interval = setInterval(async () => { const result = await api.bilibili.pollQR(key); if (result.status === 'confirmed') { clearInterval(interval); setQrState(s => ({ ...s!, status: 'success' })); } else if (result.status === 'expired') { clearInterval(interval); setQrState(s => ({ ...s!, status: 'expired' })); } else if (result.status === 'scanned') { setQrState(s => ({ ...s!, status: 'scanned' })); } }, 1000); } // Cookie Login async function submitCookie() { await api.bilibili.setCookie(cookie); } return (
QR Code Cookie {mode === 'qr' && (
{qrState ? ( <> ) : ( )}
)} {mode === 'cookie' && (

Get cookie from browser DevTools → Application → Cookies