Showing preview only (491K chars total). Download the full file or copy to clipboard to get everything.
Repository: iawia002/lux
Branch: master
Commit: dd00f6d258d8
Files: 186
Total size: 449.2 KB
Directory structure:
gitextract_1wxrt7hr/
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── can-not-download-video.md
│ │ └── support-a-new-website.md
│ └── workflows/
│ ├── builder.yml
│ ├── ci.yml
│ ├── goreleaser.yml
│ ├── stream_acfun.yml
│ ├── stream_bcy.yml
│ ├── stream_bilibili.yml
│ ├── stream_bitchute.yml
│ ├── stream_douyin.yml
│ ├── stream_douyu.yml
│ ├── stream_eporner.yml
│ ├── stream_facebook.yml
│ ├── stream_geekbang.yml
│ ├── stream_haokan.yml
│ ├── stream_hupu.yml
│ ├── stream_huya.yml
│ ├── stream_instagram.yml
│ ├── stream_iqiyi.yml
│ ├── stream_ixigua.yml
│ ├── stream_kuaishou.yml
│ ├── stream_mgtv.yml
│ ├── stream_miaopai.yml
│ ├── stream_netease.yml
│ ├── stream_odysee.yml
│ ├── stream_pinterest.yml
│ ├── stream_pixivision.yml
│ ├── stream_pornhub.yml
│ ├── stream_qq.yml
│ ├── stream_reddit.yml
│ ├── stream_rumble.yml
│ ├── stream_streamtape.yml
│ ├── stream_tangdou.yml
│ ├── stream_threads.yml
│ ├── stream_tiktok.yml
│ ├── stream_tumblr.yml
│ ├── stream_twitter.yml
│ ├── stream_udn.yml
│ ├── stream_vimeo.yml
│ ├── stream_vk.yml
│ ├── stream_weibo.yml
│ ├── stream_xiaohongshu.yml
│ ├── stream_ximalaya.yml
│ ├── stream_xinpianchang.yml
│ ├── stream_xvideos.yml
│ ├── stream_yinyuetai.yml
│ ├── stream_youku.yml
│ ├── stream_youtube.yml
│ ├── stream_zhihu.yml
│ └── stream_zingmp3.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── CONTRIBUTING.md
├── Cask.toml
├── LICENSE
├── README.md
├── app/
│ ├── app.go
│ └── register.go
├── codecov.yml
├── config/
│ └── config.go
├── downloader/
│ ├── downloader.go
│ ├── downloader_test.go
│ ├── types.go
│ └── utils.go
├── extractors/
│ ├── acfun/
│ │ ├── acfun.go
│ │ ├── acfun_test.go
│ │ └── types.go
│ ├── bcy/
│ │ ├── bcy.go
│ │ └── bcy_test.go
│ ├── bilibili/
│ │ ├── bilibili.go
│ │ ├── bilibili_test.go
│ │ └── types.go
│ ├── bitchute/
│ │ ├── bitchute.go
│ │ └── bitchute_test.go
│ ├── douyin/
│ │ ├── douyin.go
│ │ ├── douyin_test.go
│ │ ├── sign.js
│ │ └── types.go
│ ├── douyu/
│ │ ├── douyu.go
│ │ └── douyu_test.go
│ ├── eporner/
│ │ ├── eporner.go
│ │ └── eporner_test.go
│ ├── errors.go
│ ├── extractors.go
│ ├── facebook/
│ │ ├── facebook.go
│ │ └── facebook_test.go
│ ├── geekbang/
│ │ ├── geekbang.go
│ │ └── geekbang_test.go
│ ├── haokan/
│ │ ├── haokan.go
│ │ └── haokan_test.go
│ ├── hupu/
│ │ ├── hupu.go
│ │ └── hupu_test.go
│ ├── huya/
│ │ ├── huya.go
│ │ └── huya_test.go
│ ├── instagram/
│ │ ├── instagram.go
│ │ └── instagram_test.go
│ ├── iqiyi/
│ │ ├── iqiyi.go
│ │ └── iqiyi_test.go
│ ├── ixigua/
│ │ ├── ixigua.go
│ │ ├── ixigua_test.go
│ │ └── types.go
│ ├── kuaishou/
│ │ ├── kuaishou.go
│ │ └── kuaishou_test.go
│ ├── mgtv/
│ │ ├── mgtv.go
│ │ └── mgtv_test.go
│ ├── miaopai/
│ │ ├── miaopai.go
│ │ └── miaopai_test.go
│ ├── netease/
│ │ ├── netease.go
│ │ └── netease_test.go
│ ├── odysee/
│ │ ├── odysee.go
│ │ └── odysee_test.go
│ ├── pinterest/
│ │ ├── pinterest.go
│ │ └── pinterest_test.go
│ ├── pixivision/
│ │ ├── pixivision.go
│ │ └── pixivision_test.go
│ ├── pornhub/
│ │ ├── pornhub.go
│ │ └── pornhub_test.go
│ ├── qq/
│ │ ├── qq.go
│ │ └── qq_test.go
│ ├── reddit/
│ │ ├── reddit.go
│ │ └── reddit_test.go
│ ├── rumble/
│ │ ├── rumble.go
│ │ └── rumble_test.go
│ ├── streamtape/
│ │ ├── streamtape.go
│ │ └── streamtape_test.go
│ ├── tangdou/
│ │ ├── tangdou.go
│ │ └── tangdou_test.go
│ ├── threads/
│ │ ├── threads.go
│ │ └── threads_test.go
│ ├── tiktok/
│ │ ├── tiktok.go
│ │ └── tiktok_test.go
│ ├── tumblr/
│ │ ├── tumblr.go
│ │ └── tumblr_test.go
│ ├── twitter/
│ │ ├── twitter.go
│ │ └── twitter_test.go
│ ├── types.go
│ ├── udn/
│ │ ├── udn.go
│ │ └── udn_test.go
│ ├── universal/
│ │ ├── universal.go
│ │ └── universal_test.go
│ ├── vimeo/
│ │ ├── vimeo.go
│ │ └── vimeo_test.go
│ ├── vk/
│ │ ├── vk.go
│ │ └── vk_test.go
│ ├── weibo/
│ │ ├── weibo.go
│ │ └── weibo_test.go
│ ├── xiaohongshu/
│ │ ├── xiaohongshu.go
│ │ └── xiaohongshu_test.go
│ ├── ximalaya/
│ │ ├── types.go
│ │ ├── ximalaya.go
│ │ └── ximalaya_test.go
│ ├── xinpianchang/
│ │ ├── xinpianchang.go
│ │ └── xinpianchang_test.go
│ ├── xvideos/
│ │ ├── xvideos.go
│ │ └── xvideos_test.go
│ ├── yinyuetai/
│ │ ├── types.go
│ │ ├── yinyuetai.go
│ │ └── yinyuetai_test.go
│ ├── youku/
│ │ ├── youku.go
│ │ └── youku_test.go
│ ├── youtube/
│ │ ├── youtube.go
│ │ └── youtube_test.go
│ ├── zhihu/
│ │ ├── types.go
│ │ ├── zhihu.go
│ │ └── zhihu_test.go
│ └── zingmp3/
│ ├── zingmp3.go
│ └── zingmp3_test.go
├── go.mod
├── go.sum
├── main.go
├── parser/
│ ├── parser.go
│ └── parser_test.go
├── request/
│ ├── request.go
│ └── request_test.go
├── script/
│ ├── generate_github_action_template.js
│ └── github_action_template.yml
├── test/
│ └── utils.go
└── utils/
├── download.go
├── download_test.go
├── ffmpeg.go
├── pool.go
├── pool_test.go
├── utils.go
└── utils_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
*.go text eol=lf
*.md text eol=lf
================================================
FILE: .github/ISSUE_TEMPLATE/can-not-download-video.md
================================================
---
name: Can not download video
about: If you find that a website download is not working
title: "[download fail]: the website name"
labels: bug
assignees: ''
---
<!-- 请输入网站的名称 -->
<!-- enter the website name -->
**Website name**: name
<!-- 你的操作系统 -->
<!-- your operating system -->
**OS:**: Windows/Linux/macOS
<!-- 视频链接地址 -->
<!-- the video url -->
**Video URL:**: url
<!-- 请输入下载时显示的错误信息 -->
<!-- enter the error message when downloading -->
**Stack overflow**
```
error message here
```
<!-- 如果能提供截图,则对于解决你的问题非常有帮助 -->
<!-- If you can provide screenshots, it will be very helpful to solve your problem. -->
**Screenshots**
none
<!-- 其他信息 -->
<!-- add any other context about the problem here -->
**Additional context**
none
================================================
FILE: .github/ISSUE_TEMPLATE/support-a-new-website.md
================================================
---
name: Support a new website
about: If you want to request support for a new website
title: "[new website require]: the website name"
labels: enhancement
assignees: ''
---
<!-- 请输入网站的名称。例如 抖音 -->
<!-- enter the website name. eg. TikTok -->
- **Website name**: name
<!-- 请输入视频/音频资源地址。 例如 https://video.example.com/v/123456 -->
<!-- enter the video/radio url. eg. https://video.example.com/v/123456 -->
- **Stream link**: url
================================================
FILE: .github/workflows/builder.yml
================================================
name: Builder
on:
push:
branches: "*"
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/builder.yml"
pull_request:
branches: "*"
paths:
- "**/*.go"
- "go.mod"
- "go.sum"
- ".github/workflows/builder.yml"
workflow_dispatch:
env:
PRODUCT: lux
CGO_ENABLED: 0
GO111MODULE: on
jobs:
build:
name: Build
strategy:
matrix:
os: [ linux, freebsd, openbsd, dragonfly, windows, darwin ]
arch: [ amd64, 386 ]
include:
- os: linux
arch: arm
arm: 5
- os: linux
arch: arm
arm: 6
- os: linux
arch: arm
arm: 7
- os: linux
arch: arm64
- os: linux
arch: mips
mips: softfloat
- os: linux
arch: mips
mips: hardfloat
- os: linux
arch: mipsle
mipsle: softfloat
- os: linux
arch: mipsle
mipsle: hardfloat
- os: linux
arch: mips64
- os: linux
arch: mips64le
- os: linux
arch: ppc64
- os: linux
arch: ppc64le
- os: linux
arch: s390x
- os: windows
arch: arm
- os: android
arch: arm64
- os: darwin
arch: arm64
- os: freebsd
arch: arm64
exclude:
- os: darwin
arch: 386
- os: dragonfly
arch: 386
fail-fast: false
runs-on: ubuntu-latest
continue-on-error: true
env:
GOOS: ${{ matrix.os }}
GOARCH: ${{ matrix.arch }}
GOARM: ${{ matrix.arm }}
GOMIPS: ${{ matrix.mips }}
GOMIPS64: ${{ matrix.mips64 }}
GOMIPSLE: ${{ matrix.mipsle }}
steps:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24
- name: Check out code base
if: github.event_name == 'push'
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check out code base
if: github.event_name == 'pull_request'
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Cache go module
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: ${{ runner.os }}-go-
- name: Get dependencies
run: |
go get -v -t -d ./...
- name: Build binary
id: builder
run: |
ARGS="${GOOS}-${GOARCH}"
if [[ -n "${GOARM}" ]]; then
ARGS="${ARGS}v${GOARM}"
elif [[ -n "${GOMIPS}" ]]; then
ARGS="${ARGS}-${GOMIPS}"
elif [[ -n "${GOMIPS64}" ]]; then
ARGS="${ARGS}-${GOMIPS64}"
elif [[ -n "${GOMIPSLE}" ]]; then
ARGS="${ARGS}-${GOMIPSLE}"
fi
go build -trimpath --ldflags "-s -w -buildid=" -v -o ./bin/${{ env.PRODUCT }}-${ARGS}
echo "::set-output name=filename::${{ env.PRODUCT }}-${ARGS}"
- name: Upload binary artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ steps.builder.outputs.filename }}
path: ./bin/${{ env.PRODUCT }}*
if-no-files-found: error
================================================
FILE: .github/workflows/ci.yml
================================================
name: ci
on:
push:
pull_request:
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
ci:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest, macOS-latest]
name: Go ${{ matrix.go }} in ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Environment
run: |
go version
go env
- name: Lint
uses: golangci/golangci-lint-action@v5
with:
version: v1.64.7
only-new-issues: true
- name: Test
env:
GOFLAGS: -mod=mod
run: go test -race -coverpkg=./... -coverprofile=coverage.txt ./...
- name: Send coverage
run: bash <(curl -s https://codecov.io/bash)
================================================
FILE: .github/workflows/goreleaser.yml
================================================
name: goreleaser
on:
push:
tags:
- '*'
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GORELEASER_TOKEN }}
================================================
FILE: .github/workflows/stream_acfun.yml
================================================
name: acfun
on:
push:
paths:
- "extractors/acfun/*.go"
- ".github/workflows/stream_acfun.yml"
pull_request:
paths:
- "extractors/acfun/*.go"
- ".github/workflows/stream_acfun.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/acfun
================================================
FILE: .github/workflows/stream_bcy.yml
================================================
name: bcy
on:
push:
paths:
- "extractors/bcy/*.go"
- ".github/workflows/stream_bcy.yml"
pull_request:
paths:
- "extractors/bcy/*.go"
- ".github/workflows/stream_bcy.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/bcy
================================================
FILE: .github/workflows/stream_bilibili.yml
================================================
name: bilibili
on:
push:
paths:
- "extractors/bilibili/*.go"
- ".github/workflows/stream_bilibili.yml"
pull_request:
paths:
- "extractors/bilibili/*.go"
- ".github/workflows/stream_bilibili.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/bilibili
================================================
FILE: .github/workflows/stream_bitchute.yml
================================================
name: bitchute
on:
push:
paths:
- "extractors/bitchute/*.go"
- ".github/workflows/stream_bitchute.yml"
pull_request:
paths:
- "extractors/bitchute/*.go"
- ".github/workflows/stream_bitchute.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/bitchute
================================================
FILE: .github/workflows/stream_douyin.yml
================================================
name: douyin
on:
push:
paths:
- "extractors/douyin/*.go"
- ".github/workflows/stream_douyin.yml"
pull_request:
paths:
- "extractors/douyin/*.go"
- ".github/workflows/stream_douyin.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/douyin
================================================
FILE: .github/workflows/stream_douyu.yml
================================================
name: douyu
on:
push:
paths:
- "extractors/douyu/*.go"
- ".github/workflows/stream_douyu.yml"
pull_request:
paths:
- "extractors/douyu/*.go"
- ".github/workflows/stream_douyu.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/douyu
================================================
FILE: .github/workflows/stream_eporner.yml
================================================
name: eporner
on:
push:
paths:
- "extractors/eporner/*.go"
- ".github/workflows/stream_eporner.yml"
pull_request:
paths:
- "extractors/eporner/*.go"
- ".github/workflows/stream_eporner.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/eporner
================================================
FILE: .github/workflows/stream_facebook.yml
================================================
name: facebook
on:
push:
paths:
- "extractors/facebook/*.go"
- ".github/workflows/stream_facebook.yml"
pull_request:
paths:
- "extractors/facebook/*.go"
- ".github/workflows/stream_facebook.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/facebook
================================================
FILE: .github/workflows/stream_geekbang.yml
================================================
name: geekbang
on:
push:
paths:
- "extractors/geekbang/*.go"
- ".github/workflows/stream_geekbang.yml"
pull_request:
paths:
- "extractors/geekbang/*.go"
- ".github/workflows/stream_geekbang.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/geekbang
================================================
FILE: .github/workflows/stream_haokan.yml
================================================
name: haokan
on:
push:
paths:
- "extractors/haokan/*.go"
- ".github/workflows/stream_haokan.yml"
pull_request:
paths:
- "extractors/haokan/*.go"
- ".github/workflows/stream_haokan.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/haokan
================================================
FILE: .github/workflows/stream_hupu.yml
================================================
name: hupu
on:
push:
paths:
- "extractors/hupu/*.go"
- ".github/workflows/stream_hupu.yml"
pull_request:
paths:
- "extractors/hupu/*.go"
- ".github/workflows/stream_hupu.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/hupu
================================================
FILE: .github/workflows/stream_huya.yml
================================================
name: huya
on:
push:
paths:
- "extractors/huya/*.go"
- ".github/workflows/stream_huya.yml"
pull_request:
paths:
- "extractors/huya/*.go"
- ".github/workflows/stream_huya.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/huya
================================================
FILE: .github/workflows/stream_instagram.yml
================================================
name: instagram
on:
push:
paths:
- "extractors/instagram/*.go"
- ".github/workflows/stream_instagram.yml"
pull_request:
paths:
- "extractors/instagram/*.go"
- ".github/workflows/stream_instagram.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/instagram
================================================
FILE: .github/workflows/stream_iqiyi.yml
================================================
name: iqiyi
on:
push:
paths:
- "extractors/iqiyi/*.go"
- ".github/workflows/stream_iqiyi.yml"
pull_request:
paths:
- "extractors/iqiyi/*.go"
- ".github/workflows/stream_iqiyi.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/iqiyi
================================================
FILE: .github/workflows/stream_ixigua.yml
================================================
name: ixigua
on:
push:
paths:
- "extractors/ixigua/*.go"
- ".github/workflows/stream_ixigua.yml"
pull_request:
paths:
- "extractors/ixigua/*.go"
- ".github/workflows/stream_ixigua.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/ixigua
================================================
FILE: .github/workflows/stream_kuaishou.yml
================================================
name: kuaishou
on:
push:
paths:
- "extractors/kuaishou/*.go"
- ".github/workflows/stream_kuaishou.yml"
pull_request:
paths:
- "extractors/kuaishou/*.go"
- ".github/workflows/stream_kuaishou.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/kuaishou
================================================
FILE: .github/workflows/stream_mgtv.yml
================================================
name: mgtv
on:
push:
paths:
- "extractors/mgtv/*.go"
- ".github/workflows/stream_mgtv.yml"
pull_request:
paths:
- "extractors/mgtv/*.go"
- ".github/workflows/stream_mgtv.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/mgtv
================================================
FILE: .github/workflows/stream_miaopai.yml
================================================
name: miaopai
on:
push:
paths:
- "extractors/miaopai/*.go"
- ".github/workflows/stream_miaopai.yml"
pull_request:
paths:
- "extractors/miaopai/*.go"
- ".github/workflows/stream_miaopai.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/miaopai
================================================
FILE: .github/workflows/stream_netease.yml
================================================
name: netease
on:
push:
paths:
- "extractors/netease/*.go"
- ".github/workflows/stream_netease.yml"
pull_request:
paths:
- "extractors/netease/*.go"
- ".github/workflows/stream_netease.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/netease
================================================
FILE: .github/workflows/stream_odysee.yml
================================================
name: odysee
on:
push:
paths:
- "extractors/odysee/*.go"
- ".github/workflows/stream_odysee.yml"
pull_request:
paths:
- "extractors/odysee/*.go"
- ".github/workflows/stream_odysee.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/odysee
================================================
FILE: .github/workflows/stream_pinterest.yml
================================================
name: pinterest
on:
push:
paths:
- "extractors/pinterest/*.go"
- ".github/workflows/stream_pinterest.yml"
pull_request:
paths:
- "extractors/pinterest/*.go"
- ".github/workflows/stream_pinterest.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/pinterest
================================================
FILE: .github/workflows/stream_pixivision.yml
================================================
name: pixivision
on:
push:
paths:
- "extractors/pixivision/*.go"
- ".github/workflows/stream_pixivision.yml"
pull_request:
paths:
- "extractors/pixivision/*.go"
- ".github/workflows/stream_pixivision.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/pixivision
================================================
FILE: .github/workflows/stream_pornhub.yml
================================================
name: pornhub
on:
push:
paths:
- "extractors/pornhub/*.go"
- ".github/workflows/stream_pornhub.yml"
pull_request:
paths:
- "extractors/pornhub/*.go"
- ".github/workflows/stream_pornhub.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/pornhub
================================================
FILE: .github/workflows/stream_qq.yml
================================================
name: qq
on:
push:
paths:
- "extractors/qq/*.go"
- ".github/workflows/stream_qq.yml"
pull_request:
paths:
- "extractors/qq/*.go"
- ".github/workflows/stream_qq.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/qq
================================================
FILE: .github/workflows/stream_reddit.yml
================================================
name: reddit
on:
push:
paths:
- "extractors/reddit/*.go"
- ".github/workflows/stream_reddit.yml"
pull_request:
paths:
- "extractors/reddit/*.go"
- ".github/workflows/stream_reddit.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/reddit
================================================
FILE: .github/workflows/stream_rumble.yml
================================================
name: rumble
on:
push:
paths:
- "extractors/rumble/*.go"
- ".github/workflows/stream_rumble.yml"
pull_request:
paths:
- "extractors/rumble/*.go"
- ".github/workflows/stream_rumble.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/rumble
================================================
FILE: .github/workflows/stream_streamtape.yml
================================================
name: streamtape
on:
push:
paths:
- "extractors/streamtape/*.go"
- ".github/workflows/stream_streamtape.yml"
pull_request:
paths:
- "extractors/streamtape/*.go"
- ".github/workflows/stream_streamtape.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/streamtape
================================================
FILE: .github/workflows/stream_tangdou.yml
================================================
name: tangdou
on:
push:
paths:
- "extractors/tangdou/*.go"
- ".github/workflows/stream_tangdou.yml"
pull_request:
paths:
- "extractors/tangdou/*.go"
- ".github/workflows/stream_tangdou.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/tangdou
================================================
FILE: .github/workflows/stream_threads.yml
================================================
name: instagram
on:
push:
paths:
- "extractors/threads/*.go"
- ".github/workflows/stream_threads.yml"
pull_request:
paths:
- "extractors/threads/*.go"
- ".github/workflows/stream_threads.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/threads
================================================
FILE: .github/workflows/stream_tiktok.yml
================================================
name: tiktok
on:
push:
paths:
- "extractors/tiktok/*.go"
- ".github/workflows/stream_tiktok.yml"
pull_request:
paths:
- "extractors/tiktok/*.go"
- ".github/workflows/stream_tiktok.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/tiktok
================================================
FILE: .github/workflows/stream_tumblr.yml
================================================
name: tumblr
on:
push:
paths:
- "extractors/tumblr/*.go"
- ".github/workflows/stream_tumblr.yml"
pull_request:
paths:
- "extractors/tumblr/*.go"
- ".github/workflows/stream_tumblr.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/tumblr
================================================
FILE: .github/workflows/stream_twitter.yml
================================================
name: twitter
on:
push:
paths:
- "extractors/twitter/*.go"
- ".github/workflows/stream_twitter.yml"
pull_request:
paths:
- "extractors/twitter/*.go"
- ".github/workflows/stream_twitter.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/twitter
================================================
FILE: .github/workflows/stream_udn.yml
================================================
name: udn
on:
push:
paths:
- "extractors/udn/*.go"
- ".github/workflows/stream_udn.yml"
pull_request:
paths:
- "extractors/udn/*.go"
- ".github/workflows/stream_udn.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/udn
================================================
FILE: .github/workflows/stream_vimeo.yml
================================================
name: vimeo
on:
push:
paths:
- "extractors/vimeo/*.go"
- ".github/workflows/stream_vimeo.yml"
pull_request:
paths:
- "extractors/vimeo/*.go"
- ".github/workflows/stream_vimeo.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/vimeo
================================================
FILE: .github/workflows/stream_vk.yml
================================================
name: vk
on:
push:
paths:
- "extractors/vk/*.go"
- ".github/workflows/stream_vk.yml"
pull_request:
paths:
- "extractors/vk/*.go"
- ".github/workflows/stream_vk.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/vk
================================================
FILE: .github/workflows/stream_weibo.yml
================================================
name: weibo
on:
push:
paths:
- "extractors/weibo/*.go"
- ".github/workflows/stream_weibo.yml"
pull_request:
paths:
- "extractors/weibo/*.go"
- ".github/workflows/stream_weibo.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/weibo
================================================
FILE: .github/workflows/stream_xiaohongshu.yml
================================================
name: xiaohongshu
on:
push:
paths:
- "extractors/xiaohongshu/*.go"
- ".github/workflows/stream_xiaohongshu.yml"
pull_request:
paths:
- "extractors/xiaohongshu/*.go"
- ".github/workflows/stream_xiaohongshu.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/xiaohongshu
================================================
FILE: .github/workflows/stream_ximalaya.yml
================================================
name: ximalaya
on:
push:
paths:
- "extractors/ximalaya/*.go"
- ".github/workflows/stream_ximalaya.yml"
pull_request:
paths:
- "extractors/ximalaya/*.go"
- ".github/workflows/stream_ximalaya.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/ximalaya
================================================
FILE: .github/workflows/stream_xinpianchang.yml
================================================
name: xinpianchang
on:
push:
paths:
- "extractors/xinpianchang/*.go"
- ".github/workflows/stream_xinpianchang.yml"
pull_request:
paths:
- "extractors/xinpianchang/*.go"
- ".github/workflows/stream_xinpianchang.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/xinpianchang
================================================
FILE: .github/workflows/stream_xvideos.yml
================================================
name: xvideos
on:
push:
paths:
- "extractors/xvideos/*.go"
- ".github/workflows/stream_xvideos.yml"
pull_request:
paths:
- "extractors/xvideos/*.go"
- ".github/workflows/stream_xvideos.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/xvideos
================================================
FILE: .github/workflows/stream_yinyuetai.yml
================================================
name: yinyuetai
on:
push:
paths:
- "extractors/yinyuetai/*.go"
- ".github/workflows/stream_yinyuetai.yml"
pull_request:
paths:
- "extractors/yinyuetai/*.go"
- ".github/workflows/stream_yinyuetai.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/yinyuetai
================================================
FILE: .github/workflows/stream_youku.yml
================================================
name: youku
on:
push:
paths:
- "extractors/youku/*.go"
- ".github/workflows/stream_youku.yml"
pull_request:
paths:
- "extractors/youku/*.go"
- ".github/workflows/stream_youku.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/youku
================================================
FILE: .github/workflows/stream_youtube.yml
================================================
name: youtube
on:
push:
paths:
- "extractors/youtube/*.go"
- ".github/workflows/stream_youtube.yml"
pull_request:
paths:
- "extractors/youtube/*.go"
- ".github/workflows/stream_youtube.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/youtube
================================================
FILE: .github/workflows/stream_zhihu.yml
================================================
name: zhihu
on:
push:
paths:
- "extractors/zhihu/*.go"
- ".github/workflows/stream_zhihu.yml"
pull_request:
paths:
- "extractors/zhihu/*.go"
- ".github/workflows/stream_zhihu.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/zhihu
================================================
FILE: .github/workflows/stream_zingmp3.yml
================================================
name: zingmp3
on:
push:
paths:
- "extractors/zingmp3/*.go"
- ".github/workflows/stream_zingmp3.yml"
pull_request:
paths:
- "extractors/zingmp3/*.go"
- ".github/workflows/stream_zingmp3.yml"
schedule:
# run ci weekly
- cron: "0 0 * * 0"
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go: ["1.24"]
os: [ubuntu-latest]
name: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
- name: Test
run: go test -timeout 5m -race -coverpkg=./... -coverprofile=coverage.txt github.com/iawia002/lux/extractors/zingmp3
================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
coverage.txt
# Python
*.pyc
.vscode
.idea
dist/
*_token
downloader/*.jpg
# Ignore compiled binaries
# - native
annie
# - gox builds
annie_*
# macOS
.DS_Store
*.mp4
*.mkv
*.webm
lux
================================================
FILE: .golangci.yml
================================================
run:
concurrency: 2
timeout: 5m
go: 1.24
linter-settings:
goconst:
min-len: 2
min-occurrences: 2
linters:
enable:
- bodyclose
- errcheck
- goconst
- gofmt
- goimports
- gosimple
- govet
- ineffassign
- misspell
- nilerr
- staticcheck
- typecheck
- unconvert
- unparam
- unused
- whitespace
issues:
exclude-use-default: false
exclude-rules:
- path: _test.go
linters:
- errcheck
================================================
FILE: .goreleaser.yml
================================================
project_name: lux
env:
- GO111MODULE=on
- CGO_ENABLED=0
before:
hooks:
- go mod download
builds:
- binary: lux
ldflags: -s -w -X github.com/iawia002/lux/app.version={{ .RawVersion }}
goos:
- windows
- darwin
- linux
- freebsd
- openbsd
- netbsd
goarch:
- "386"
- amd64
- arm
- arm64
ignore:
- goos: freebsd
goarch: arm
goarm: 6
- goos: freebsd
goarch: arm64
- goos: openbsd
goarch: arm
goarm: 6
archives:
- name_template: >-
{{ .ProjectName }}_
{{- .Version }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
format: tar.gz
format_overrides:
- goos: windows
format: zip
files:
- none*
wrap_in_directory: false
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing Guide
* [Style Guide](#style-guide)
* [Build](#build)
* [Features Requested](#features-requested)
## Style Guide
### Code format
Lux uses [gofmt](https://golang.org/cmd/gofmt) to format the code, you must use [gofmt](https://golang.org/cmd/gofmt) to format your code before submitting.
### linter
We recommend using [golint](https://github.com/golang/lint) or [gometalinter](https://github.com/alecthomas/gometalinter) to check your code format.
## Build
Make sure that this folder is in `GOPATH`, then:
```bash
$ go build
```
## Features Requested
There are several [features](https://github.com/iawia002/lux/issues?q=is%3Aissue+is%3Aopen+label%3Afeature-request) requested by the community. If you have any idea, feel free to fork the repo, follow the style guide above, push and merge it after passing the test. Besides, you are welcomed to propose new features through the issue.
================================================
FILE: Cask.toml
================================================
[package]
name = "github.com/iawia002/lux"
bin = "lux"
authors = ["Xinzhao Xu <z2d@jifangcheng.com>"]
keywords = ["go", "golang", "crawler", "scraper", "downloader", "youtube", "video", "download", "tumblr", "bilibili", "qq", "hacktoberfest", "youku", "iqiyi"]
repository = "https://github.com/iawia002/lux"
description = """
👾 Fast and simple video download library and CLI tool written in Go
"""
[darwin]
x86_64 = { url = "https://github.com/iawia002/lux/releases/download/v{version}/lux_{version}_Darwin_x86_64.tar.gz" }
aarch64 = { url = "https://github.com/iawia002/lux/releases/download/v{version}/lux_{version}_Darwin_arm64.tar.gz" }
[windows]
x86_64 = { url = "https://github.com/iawia002/lux/releases/download/v{version}/lux_{version}_Windows_x86_64.zip" }
[linux]
x86_64 = { url = "https://github.com/iawia002/lux/releases/download/v{version}/lux_{version}_Linux_x86_64.tar.gz" }
aarch64 = { url = "https://github.com/iawia002/lux/releases/download/v{version}/lux_{version}_Linux_arm64.tar.gz" }
================================================
FILE: LICENSE
================================================
MIT License
Copyright 2018-present, iawia002
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
<h1 align="center">Lux</h1>
<p align="center"><i>Let there be Lux!</i></p>
<div align="center">
<a href="https://codecov.io/gh/iawia002/lux">
<img src="https://img.shields.io/codecov/c/github/iawia002/lux.svg?style=flat-square" alt="Codecov">
</a>
<a href="https://github.com/iawia002/lux/actions">
<img src="https://img.shields.io/github/actions/workflow/status/iawia002/lux/ci.yml?style=flat-square" alt="GitHub Workflow Status">
</a>
<a href="https://goreportcard.com/report/github.com/iawia002/lux">
<img src="https://goreportcard.com/badge/github.com/iawia002/lux?style=flat-square" alt="Go Report Card">
</a>
<a href="https://github.com/iawia002/lux/releases">
<img src="https://img.shields.io/github/release/iawia002/lux.svg?style=flat-square" alt="GitHub release">
</a>
<a href="https://formulae.brew.sh/formula/lux">
<img src="https://img.shields.io/homebrew/v/lux.svg?style=flat-square" alt="Homebrew">
</a>
</div>
👾 Lux is a fast and simple video downloader built with Go.
- [Installation](#installation)
- [Prerequisites](#prerequisites)
- [Install via `go install`](#install-via-go-install)
- [Homebrew (macOS only)](#homebrew-macos-only)
- [Arch Linux](#arch-linux)
- [Void Linux](#void-linux)
- [Scoop on Windows](#scoop-on-windows)
- [Chocolatey on Windows](#chocolatey-on-windows)
- [Cask on Windows/macOS/Linux](#cask-on-windowsmacoslinux)
- [Getting Started](#getting-started)
- [Download a video](#download-a-video)
- [Download anything else](#download-anything-else)
- [Download playlist](#download-playlist)
- [Multiple inputs](#multiple-inputs)
- [Resume a download](#resume-a-download)
- [Auto retry](#auto-retry)
- [Cookies](#cookies)
- [Proxy](#proxy)
- [Multi-Thread](#multi-thread)
- [Short link](#short-link)
- [bilibili](#bilibili)
- [Use specified Referrer](#use-specified-referrer)
- [Specify the output path and name](#specify-the-output-path-and-name)
- [Debug Mode](#debug-mode)
- [Reuse extracted data](#reuse-extracted-data)
- [Options](#options)
- [Download:](#download)
- [Network:](#network)
- [Playlist:](#playlist)
- [Filesystem:](#filesystem)
- [Subtitle:](#subtitle)
- [Youku:](#youku)
- [aria2:](#aria2)
- [Supported Sites](#supported-sites)
- [Known issues](#known-issues)
- [优酷](#优酷)
- [西瓜/头条视频](#西瓜头条视频)
- [Contributing](#contributing)
- [Authors](#authors)
- [Similar projects](#similar-projects)
- [License](#license)
## Installation
### Prerequisites
The following dependencies are required and must be installed separately.
- **[FFmpeg](https://www.ffmpeg.org)**
> **Note**: FFmpeg does not affect the download, only affects the final file merge.
### Install via `go install`
To install Lux, use `go install`, or download the binary file from [Releases](https://github.com/iawia002/lux/releases) page.
```bash
$ go install github.com/iawia002/lux@latest
```
### Homebrew (macOS only)
For macOS users, you can install `lux` via:
```bash
$ brew install lux
```
### Arch Linux
For Arch Users [AUR](https://aur.archlinux.org/packages/lux-dl/) package is available.
### Void Linux
For Void linux users, you can install `lux` via:
```
$ xbps-install -S lux
```
### [Scoop](https://scoop.sh/) on Windows
```sh
$ scoop install lux
```
### [Chocolatey](https://chocolatey.org/) on Windows
```
$ choco install lux
```
### [Cask](https://github.com/axetroy/cask.rs) on Windows/macOS/Linux
```sh
$ cask install github.com/iawia002/lux
```
## Getting Started
Usage:
```
lux [OPTIONS] URL [URL...]
```
### Download a video
```console
$ lux "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Site: YouTube youtube.com
Title: Rick Astley - Never Gonna Give You Up (Video)
Type: video
Stream:
[248] -------------------
Quality: 1080p video/webm; codecs="vp9"
Size: 63.93 MiB (67038963 Bytes)
# download with: lux -f 248 ...
41.88 MiB / 63.93 MiB [=================>-------------] 65.51% 4.22 MiB/s 00m05s
```
The `-i` option displays all available quality of video without downloading.
```console
$ lux -i "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Site: YouTube youtube.com
Title: Rick Astley - Never Gonna Give You Up (Video)
Type: video
Streams: # All available quality
[248] -------------------
Quality: 1080p video/webm; codecs="vp9"
Size: 49.29 MiB (51687554 Bytes)
# download with: lux -f 248 ...
[137] -------------------
Quality: 1080p video/mp4; codecs="avc1.640028"
Size: 43.45 MiB (45564306 Bytes)
# download with: lux -f 137 ...
[398] -------------------
Quality: 720p video/mp4; codecs="av01.0.05M.08"
Size: 37.12 MiB (38926432 Bytes)
# download with: lux -f 398 ...
[136] -------------------
Quality: 720p video/mp4; codecs="avc1.4d401f"
Size: 31.34 MiB (32867324 Bytes)
# download with: lux -f 136 ...
[247] -------------------
Quality: 720p video/webm; codecs="vp9"
Size: 31.03 MiB (32536181 Bytes)
# download with: lux -f 247 ...
```
Use `lux -f stream "URL"` to download a specific stream listed in the output of `-i` option.
### Download anything else
If Lux is provided the URL of a specific resource, then it will be downloaded directly:
```console
$ lux "https://img9.bcyimg.com/drawer/15294/post/1799t/1f5a87801a0711e898b12b640777720f.jpg"
lux doesn't support this URL right now, but it will try to download it directly
Site: Universal
Title: 1f5a87801a0711e898b12b640777720f
Type: image/jpeg
Stream:
[default] -------------------
Size: 1.00 MiB (1051042 Bytes)
# download with: lux -f default "URL"
1.00 MiB / 1.00 MiB [===================================] 100.00% 1.21 MiB/s 0s
```
### Download playlist
The `-p` option downloads an entire playlist instead of a single video.
```console
$ lux -i -p "https://www.bilibili.com/bangumi/play/ep198061"
Site: 哔哩哔哩 bilibili.com
Title: Doctor X 第四季:第一集
Type: video
Streams: # All available quality
[default] -------------------
Quality: 高清 1080P
Size: 845.66 MiB (886738354 Bytes)
# download with: lux -f default "URL"
Site: 哔哩哔哩 bilibili.com
Title: Doctor X 第四季:第二集
Type: video
Streams: # All available quality
[default] -------------------
Quality: 高清 1080P
Size: 930.71 MiB (975919195 Bytes)
# download with: lux -f default "URL"
......
```
You can use the `-start`, `-end` or `-items` option to specify the download range of the list:
```
-start
Playlist video to start at (default 1)
-end
Playlist video to end at
-items
Playlist video items to download. Separated by commas like: 1,5,6,8-10
```
For bilibili playlists only:
```
-eto
File name of each bilibili episode doesn't include the playlist title
```
### Multiple inputs
You can also download multiple URLs at once:
```console
$ lux -i "https://www.bilibili.com/video/av21877586" "https://www.bilibili.com/video/av21990740"
Site: 哔哩哔哩 bilibili.com
Title: 【莓机会了】甜到虐哭的13集单集MAD「我现在什么都不想干,更不想看14集」
Type: video
Streams: # All available quality
[default] -------------------
Quality: 高清 1080P
Size: 51.88 MiB (54403767 Bytes)
# download with: lux -f default "URL"
Site: 哔哩哔哩 bilibili.com
Title: 【莓救了】甜到虐哭!!!国家队单集MAD-当熟悉的bgm响起,眼泪从脸颊滑下
Type: video
Streams: # All available quality
[default] -------------------
Quality: 高清 1080P
Size: 77.63 MiB (81404093 Bytes)
# download with: lux -f default "URL"
```
These URLs will be downloaded one by one.
You can also use the `-F` option to read URLs from file:
```console
$ lux -F ~/Desktop/u.txt
Site: 微博 weibo.com
Title: 在Google,我们设计什么? via@阑夕
Type: video
Stream:
[default] -------------------
Size: 19.19 MiB (20118196 Bytes)
# download with: lux -f default "URL"
19.19 MiB / 19.19 MiB [=================================] 100.00% 9.69 MiB/s 1s
......
```
You can use the `-start`, `-end` or `-items` option to specify the download range of the list:
```
-start
File line to start at (default 1)
-end
File line to end at
-items
File lines to download. Separated by commas like: 1,5,6,8-10
```
### Resume a download
<kbd>Ctrl</kbd>+<kbd>C</kbd> interrupts a download.
A temporary `.download` file is kept in the output directory. If `lux` is ran with the same arguments, then the download progress will resume from the last session.
### Auto retry
lux will auto retry when the download failed, you can specify the retry times by `-retry` option (default is 100).
### Cookies
Cookies can be provided to `lux` with the `-c` option if they are required for accessing the video.
Cookies can be the following format or [Netscape Cookie](https://curl.haxx.se/rfc/cookie_spec.html) format:
```console
name=value; name2=value2; ...
```
Cookies can be a string or a text file, supply cookies in one of the two following ways.
As a string:
```console
$ lux -c "name=value; name2=value2" "https://www.bilibili.com/video/av20203945"
```
As a text file:
```console
$ lux -c cookies.txt "https://www.bilibili.com/video/av20203945"
```
### Proxy
You can set the HTTP/SOCKS5 proxy using environment variables:
```console
$ HTTP_PROXY="http://127.0.0.1:1087/" lux -i "https://www.youtube.com/watch?v=Gnbch2osEeo"
```
```console
$ HTTP_PROXY="socks5://127.0.0.1:1080/" lux -i "https://www.youtube.com/watch?v=Gnbch2osEeo"
```
### Multi-Thread
Use `--multi-thread` or `-m` multiple threads to download single video.
Use `--thread` or `-n` option to set the number of download threads(default is 10).
> Note: If the video has multi fragment, the number of actual download threads will increase.
>
> For example:
> * If `-n` is set to 10, and the video has 2 fragments, then 20 threads will actually be used.
> * If the video has 20 fragments, only 10 fragments are downloaded in the same time, the actual threads count is 100.
> **Special Tips:** Use too many threads in **mgtv** download will cause HTTP 403 error, we recommend setting the number of threads to **1**.
### Short link
#### bilibili
You can just use `av` or `ep` number to download bilibili's video:
```console
$ lux -i ep198381 av21877586
Site: 哔哩哔哩 bilibili.com
Title: 狐妖小红娘:第79话 南国公主的吃货本色
Type: video
Streams: # All available quality
[default] -------------------
Quality: 高清 1080P
Size: 485.23 MiB (508798478 Bytes)
# download with: lux -f default "URL"
Site: 哔哩哔哩 bilibili.com
Title: 【莓机会了】甜到虐哭的13集单集MAD「我现在什么都不想干,更不想看14集」
Type: video
Streams: # All available quality
[default] -------------------
Quality: 高清 1080P
Size: 51.88 MiB (54403767 Bytes)
# download with: lux -f default "URL"
```
### Use specified Referrer
A Referrer can be used for the request with the `-r` option:
```console
$ lux -r "https://www.bilibili.com/video/av20383055/" "http://cn-scnc1-dx.acgvideo.com/"
```
### Specify the output path and name
The `-o` option sets the path, and `-O` option sets the name of the downloaded file:
```console
$ lux -o ../ -O "hello" "https://example.com"
```
### Debug Mode
The `-d` option outputs network request messages:
```console
$ lux -i -d "http://www.bilibili.com/video/av20088587"
URL: http://www.bilibili.com/video/av20088587
Method: GET
Headers: http.Header{
"Referer": {"http://www.bilibili.com/video/av20088587"},
"Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
"Accept-Charset": {"UTF-8,*;q=0.5"},
"Accept-Encoding": {"gzip,deflate,sdch"},
"Accept-Language": {"en-US,en;q=0.8"},
"User-Agent": {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36"},
}
Status Code: 200
URL: https://interface.bilibili.com/v2/playurl?appkey=84956560bc028eb7&cid=32782944&otype=json&qn=116&quality=116&type=&sign=fb2e3f261fec398652f96d358517e535
Method: GET
Headers: http.Header{
"Accept-Charset": {"UTF-8,*;q=0.5"},
"Accept-Encoding": {"gzip,deflate,sdch"},
"Accept-Language": {"en-US,en;q=0.8"},
"User-Agent": {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36"},
"Referer": {"https://interface.bilibili.com/v2/playurl?appkey=84956560bc028eb7&cid=32782944&otype=json&qn=116&quality=116&type=&sign=fb2e3f261fec398652f96d358517e535"},
"Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
}
Status Code: 200
Site: 哔哩哔哩 bilibili.com
Title: 燃油动力的遥控奥迪R8跑赛道
Type: video
Streams: # All available quality
[default] -------------------
Quality: 高清 1080P
Size: 64.38 MiB (67504795 Bytes)
# download with: lux -f default "URL"
```
### Reuse extracted data
The `-j` option will print the extracted data in JSON format.
```console
$ lux -j "https://www.bilibili.com/video/av20203945"
{
"site": "哔哩哔哩 bilibili.com",
"title": "【2018拜年祭单品】相遇day by day",
"type": "video",
"streams": {
"15": {
"urls": [
{
"url": "...",
"size": 18355205,
"ext": "flv"
}
],
"quality": "流畅 360P",
"size": 18355205
},
"32": {
"urls": [
{
"url": "...",
"size": 40058632,
"ext": "flv"
}
],
"quality": "清晰 480P",
"size": 40058632
},
"64": {
"urls": [
{
"url": "...",
"size": 82691087,
"ext": "flv"
}
],
"quality": "高清 720P",
"size": 82691087
},
"80": {
"urls": [
{
"url": "...",
"size": 121735559,
"ext": "flv"
}
],
"quality": "高清 1080P",
"size": 121735559
}
}
}
```
### Options
```
-i Information only
-F string
URLs file path
-d Debug mode
-j Print extracted data
-s Minimum outputs
-v Show version
```
#### Download:
```
-f string
Select specific stream to download
-p Download playlist
-n int
The number of download thread (only works for multiple-parts video) (default 10)
-c string
Cookie
-r string
Use specified Referrer
-cs int
HTTP chunk size for downloading (in MB) (default 1)
```
#### Network:
```
-retry int
How many times to retry when the download failed (default 10)
```
#### Playlist:
```
-start int
Playlist video to start at (default 1)
-end int
Playlist video to end at
-items string
Playlist video items to download. Separated by commas like: 1,5,6,8-10
```
#### Filesystem:
```
-o string
Specify the output path
-O string
Specify the output file name
```
#### Subtitle:
```
-C Download subtitles
-C -items en,zh
Download specific languages (YouTube only)
-C -items en,zh -embed
Embed subtitles into the video (YouTube only)
```
#### Youku:
```
-ccode string
Youku ccode (default "0502")
-ckey string
Youku ckey (default "7B19C0AB12633B22E7FE81271162026020570708D6CC189E4924503C49D243A0DE6CD84A766832C2C99898FC5ED31F3709BB3CDD82C96492E721BDD381735026")
-password string
Youku password
```
#### aria2:
> Note: If you use aria2 to download, you need to merge the multi-part videos yourself.
```
-aria2
Use Aria2 RPC to download
-aria2addr string
Aria2 Address (default "localhost:6800")
-aria2method string
Aria2 Method (default "http")
-aria2token string
Aria2 RPC Token
```
## Supported Sites
| Site | URL | 🎬 Videos | 🌁 Images | 🔊 Audio | 📚 Playlist | 🍪 VIP adaptation | Build Status |
| ---------------- | ------------------------------------------------------------------------- | -------- | -------- | ------- | ---------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 抖音 | <https://www.douyin.com> | ✓ | ✓ | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_douyin.yml) |
| 哔哩哔哩 | <https://www.bilibili.com> | ✓ | | | ✓ | ✓ | [](https://github.com/iawia002/lux/actions/workflows/stream_bilibili.yml) |
| 半次元 | <https://bcy.net> | | ✓ | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_bcy.yml) |
| pixivision | <https://www.pixivision.net> | | ✓ | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_pixivision.yml) |
| 优酷 | <https://www.youku.com> | ✓ | | | | ✓ | [](https://github.com/iawia002/lux/actions/workflows/stream_youku.yml) |
| YouTube | <https://www.youtube.com> | ✓ | | | ✓ | | [](https://github.com/iawia002/lux/actions/workflows/stream_youtube.yml) |
| 西瓜视频(头条) | <https://m.toutiao.com>, <https://v.ixigua.com>, <https://www.ixigua.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_ixigua.yml) |
| 爱奇艺 | <https://www.iqiyi.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_iqiyi.yml) |
| 新片场 | <https://www.xinpianchang.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_xinpianchang.yml) |
| 芒果 TV | <https://www.mgtv.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_mgtv.yml) |
| 糖豆广场舞 | <https://www.tangdou.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_tangdou.yml) |
| Tumblr | <https://www.tumblr.com> | ✓ | ✓ | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_tumblr.yml) |
| Vimeo | <https://vimeo.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_vimeo.yml) |
| Facebook | <https://facebook.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_facebook.yml) |
| 斗鱼视频 | <https://v.douyu.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_douyu.yml) |
| 秒拍 | <https://www.miaopai.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_miaopai.yml) |
| 微博 | <https://weibo.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_weibo.yml) |
| Instagram | <https://www.instagram.com> | ✓ | ✓ | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_instagram.yml) |
| Threads | <https://www.threads.net> | ✓ | ✓ | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_threads.yml) |
| Twitter | <https://twitter.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_twitter.yml) |
| 腾讯视频 | <https://v.qq.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_qq.yml) |
| 网易云音乐 | <https://music.163.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_netease.yml) |
| 音悦台 | <https://yinyuetai.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_yinyuetai.yml) |
| 极客时间 | <https://time.geekbang.org> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_geekbang.yml) |
| Pornhub | <https://pornhub.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_pornhub.yml) |
| XVIDEOS | <https://xvideos.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_xvideos.yml) |
| 聯合新聞網 | <https://udn.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_udn.yml) |
| TikTok | <https://www.tiktok.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_tiktok.yml) |
| Pinterest | <https://www.pinterest.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_pinterest.yml) |
| 好看视频 | <https://haokan.baidu.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_haokan.yml) |
| AcFun | <https://www.acfun.cn> | ✓ | | | ✓ | | [](https://github.com/iawia002/lux/actions/workflows/stream_acfun.yml) |
| Eporner | <https://eporner.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_eporner.yml) |
| StreamTape | <https://streamtape.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_streamtape.yml) |
| 虎扑 | <https://hupu.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_hupu.yml) |
| 虎牙视频 | <https://v.huya.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_huya.yml) |
| 喜马拉雅 | <https://www.ximalaya.com> | | | ✓ | | | [](https://github.com/iawia002/lux/actions/workflows/stream_ximalaya.yml) |
| 快手 | <https://www.kuaishou.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_kuaishou.yml) |
| Reddit | <https://www.reddit.com> | ✓ | ✓ | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_reddit.yml) |
| VKontakte | <https://vk.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_vk.yml/) |
| 知乎 | <https://zhihu.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_zhihu.yml/) |
| Rumble | <https://rumble.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_rumble.yml/) |
| 小红书 | <https://xiaohongshu.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_xiaohongshu.yml/) |
| Zing MP3 | <https://zingmp3.vn> | ✓ | | ✓ | | | [](https://github.com/iawia002/lux/actions/workflows/stream_zingmp3.yml/) |
| Bitchute | <https://www.bitchute.com> | ✓ | | | | | [](https://github.com/iawia002/lux/actions/workflows/stream_bitchute.yml/) |
| Odysee | <https://odysee.com> | ✓ | | ✓ | | | [](https://github.com/iawia002/lux/actions/workflows/stream_odysee.yml/) |
## Known issues
### 优酷
优酷的 `ccode` 经常变化导致 lux 不可用,如果你知道有新的可用的 `ccode`,可以直接使用 `lux -ccode ...` 而不用等待 lux 更新(当然,也欢迎你给我们提一个 Pull request 来更新默认的 `ccode`)
最好是每次下载都附带登录过的 Cookie 以避免部分 `ccode` 的问题
### 西瓜/头条视频
西瓜/头条视频必须带 Cookie 才能下载成功,西瓜和头条可共用西瓜视频的 Cookie,Cookie 的有效期可能较短,下载失败就更新 Cookie 尝试:
```
$ lux -c "msToken=yoEh0-qLUq4obZ8Sfxsem_CxCo9R3NM6ViTrWaRcM1...; ttwid=1%7C..." "https://m.toutiao.com/is/iYbTfJ79/"
```
## Contributing
Lux is an open source project and built on the top of open-source projects. Check out the [Contributing Guide](./CONTRIBUTING.md) to get started.
## Authors
Code with ❤️ by [iawia002](https://github.com/iawia002) and lovely [contributors](https://github.com/iawia002/lux/graphs/contributors)
## Similar projects
- [youtube](https://github.com/kkdai/youtube)
- [youtube-dl](https://github.com/rg3/youtube-dl)
- [you-get](https://github.com/soimort/you-get)
- [ytdl](https://github.com/rylio/ytdl)
## License
MIT
Copyright (c) 2018-present, iawia002
================================================
FILE: app/app.go
================================================
package app
import (
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"strings"
"github.com/fatih/color"
"github.com/urfave/cli/v2"
"github.com/iawia002/lux/downloader"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/request"
"github.com/iawia002/lux/utils"
)
// Name is the name of this app.
const Name = "lux"
// This value will be injected into the corresponding git tag value at build time using `-ldflags`.
var version = "v0.0.0"
func init() {
cli.VersionPrinter = func(c *cli.Context) {
blue := color.New(color.FgBlue)
cyan := color.New(color.FgCyan)
fmt.Fprintf(
color.Output,
"\n%s: version %s, A fast and simple video downloader.\n\n",
cyan.Sprintf(Name),
blue.Sprintf(c.App.Version),
)
}
}
// New returns the App instance.
func New() *cli.App {
app := &cli.App{
Name: Name,
Usage: "A fast and simple video downloader.",
Version: version,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "debug",
Aliases: []string{"d"},
Usage: "Debug mode",
},
&cli.BoolFlag{
Name: "silent",
Aliases: []string{"s"},
Usage: "Minimum outputs",
},
&cli.BoolFlag{
Name: "info",
Aliases: []string{"i"},
Usage: "Information only",
},
&cli.BoolFlag{
Name: "json",
Aliases: []string{"j"},
Usage: "Print extracted JSON data",
},
&cli.StringFlag{
Name: "cookie",
Aliases: []string{"c"},
Usage: "Cookie",
},
&cli.BoolFlag{
Name: "playlist",
Aliases: []string{"p"},
Usage: "Download playlist",
},
&cli.StringFlag{
Name: "user-agent",
Aliases: []string{"u"},
Usage: "Use specified User-Agent",
},
&cli.StringFlag{
Name: "refer",
Aliases: []string{"r"},
Usage: "Use specified Referrer",
},
&cli.StringFlag{
Name: "stream-format",
Aliases: []string{"f"},
Usage: "Select specific stream to download",
},
&cli.BoolFlag{
Name: "audio-only",
Aliases: []string{"ao"},
Usage: "Download audio only at best quality",
},
&cli.StringFlag{
Name: "file",
Aliases: []string{"F"},
Usage: "URLs file path",
},
&cli.StringFlag{
Name: "output-path",
Aliases: []string{"o"},
Usage: "Specify the output path",
},
&cli.StringFlag{
Name: "output-name",
Aliases: []string{"O"},
Usage: "Specify the output file name",
},
&cli.UintFlag{
Name: "file-name-length",
Value: 255,
Usage: "The maximum length of a file name, 0 means unlimited",
},
&cli.BoolFlag{
Name: "caption",
Aliases: []string{"C"},
Usage: "Download captions",
},
&cli.BoolFlag{
Name: "embed-subtitle",
Aliases: []string{"embed"},
Usage: "Embed subtitles into the video (requires ffmpeg)",
},
&cli.UintFlag{
Name: "start",
Value: 1,
Usage: "Define the starting item of a playlist or a file input",
},
&cli.UintFlag{
Name: "end",
Value: 0,
Usage: "Define the ending item of a playlist or a file input",
},
&cli.StringFlag{
Name: "items",
Usage: "Define wanted items from a file or playlist. Separated by commas like: 1,5,6,8-10",
},
&cli.BoolFlag{
Name: "multi-thread",
Aliases: []string{"m"},
Usage: "Multiple threads to download single video",
},
&cli.UintFlag{
Name: "retry",
Value: 10,
Usage: "How many times to retry when the download failed",
},
&cli.UintFlag{
Name: "chunk-size",
Aliases: []string{"cs"},
Value: 1,
Usage: "HTTP chunk size for downloading (in MB)",
},
&cli.UintFlag{
Name: "thread",
Aliases: []string{"n"},
Value: 10,
Usage: "The number of download thread (only works for multiple-parts video)",
},
// Aria2
&cli.BoolFlag{
Name: "aria2",
Usage: "Use Aria2 RPC to download",
},
&cli.StringFlag{
Name: "aria2-token",
Usage: "Aria2 RPC Token",
},
&cli.StringFlag{
Name: "aria2-addr",
Value: "localhost:6800",
Usage: "Aria2 Address",
},
&cli.StringFlag{
Name: "aria2-method",
Value: "http",
Usage: "Aria2 Method",
},
// youku
&cli.StringFlag{
Name: "youku-ccode",
Aliases: []string{"ccode"},
Value: "0502",
Usage: "Youku ccode",
},
&cli.StringFlag{
Name: "youku-ckey",
Aliases: []string{"ckey"},
Value: "7B19C0AB12633B22E7FE81271162026020570708D6CC189E4924503C49D243A0DE6CD84A766832C2C99898FC5ED31F3709BB3CDD82C96492E721BDD381735026",
Usage: "Youku ckey",
},
&cli.StringFlag{
Name: "youku-password",
Aliases: []string{"password"},
Usage: "Youku password",
},
&cli.BoolFlag{
Name: "episode-title-only",
Aliases: []string{"eto"},
Usage: "File name of each bilibili episode doesn't include the playlist title",
},
},
Action: func(c *cli.Context) error {
args := c.Args().Slice()
if c.Bool("debug") {
cli.VersionPrinter(c)
}
if file := c.String("file"); file != "" {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close() // nolint
fileItems := utils.ParseInputFile(f, c.String("items"), int(c.Uint("start")), int(c.Uint("end")))
args = append(args, fileItems...)
}
if len(args) < 1 {
return errors.New("too few arguments")
}
cookie := c.String("cookie")
if cookie != "" {
// If cookie is a file path, convert it to a string to ensure cookie is always string
if _, fileErr := os.Stat(cookie); fileErr == nil {
// Cookie is a file
data, err := os.ReadFile(cookie)
if err != nil {
return err
}
cookie = strings.TrimSpace(string(data))
}
}
request.SetOptions(request.Options{
RetryTimes: int(c.Uint("retry")),
Cookie: cookie,
UserAgent: c.String("user-agent"),
Refer: c.String("refer"),
Debug: c.Bool("debug"),
Silent: c.Bool("silent"),
})
var isErr bool
for _, videoURL := range args {
if err := download(c, videoURL); err != nil {
fmt.Fprintf(
color.Output,
"Downloading %s error:\n",
color.CyanString("%s", videoURL),
)
fmt.Printf("%+v\n", err)
isErr = true
}
}
if isErr {
return cli.Exit("", 1)
}
return nil
},
EnableBashCompletion: true,
}
sort.Sort(cli.FlagsByName(app.Flags))
return app
}
func download(c *cli.Context, videoURL string) error {
data, err := extractors.Extract(videoURL, extractors.Options{
Playlist: c.Bool("playlist"),
Items: c.String("items"),
ItemStart: int(c.Uint("start")),
ItemEnd: int(c.Uint("end")),
ThreadNumber: int(c.Uint("thread")),
EpisodeTitleOnly: c.Bool("episode-title-only"),
Cookie: c.String("cookie"),
YoukuCcode: c.String("youku-ccode"),
YoukuCkey: c.String("youku-ckey"),
YoukuPassword: c.String("youku-password"),
})
if err != nil {
// if this error occurs, it means that an error occurred before actually starting to extract data
// (there is an error in the preparation step), and the data list is empty.
return err
}
if c.Bool("json") {
e := json.NewEncoder(os.Stdout)
e.SetIndent("", "\t")
e.SetEscapeHTML(false)
if err := e.Encode(data); err != nil {
return err
}
return nil
}
defaultDownloader := downloader.New(downloader.Options{
Silent: c.Bool("silent"),
InfoOnly: c.Bool("info"),
Stream: c.String("stream-format"),
AudioOnly: c.Bool("audio-only"),
Refer: c.String("refer"),
OutputPath: c.String("output-path"),
OutputName: c.String("output-name"),
FileNameLength: int(c.Uint("file-name-length")),
Caption: c.Bool("caption"),
EmbedSubtitle: c.Bool("embed-subtitle"),
MultiThread: c.Bool("multi-thread"),
ThreadNumber: int(c.Uint("thread")),
RetryTimes: int(c.Uint("retry")),
ChunkSizeMB: int(c.Uint("chunk-size")),
UseAria2RPC: c.Bool("aria2"),
Aria2Token: c.String("aria2-token"),
Aria2Method: c.String("aria2-method"),
Aria2Addr: c.String("aria2-addr"),
})
errors := make([]error, 0)
for _, item := range data {
if item.Err != nil {
// if this error occurs, the preparation step is normal, but the data extraction is wrong.
// the data is an empty struct.
errors = append(errors, item.Err)
continue
}
if err = defaultDownloader.Download(item); err != nil {
errors = append(errors, err)
}
}
if len(errors) != 0 {
return errors[0]
}
return nil
}
================================================
FILE: app/register.go
================================================
package app
import (
_ "github.com/iawia002/lux/extractors/acfun"
_ "github.com/iawia002/lux/extractors/bcy"
_ "github.com/iawia002/lux/extractors/bilibili"
_ "github.com/iawia002/lux/extractors/bitchute"
_ "github.com/iawia002/lux/extractors/douyin"
_ "github.com/iawia002/lux/extractors/douyu"
_ "github.com/iawia002/lux/extractors/eporner"
_ "github.com/iawia002/lux/extractors/facebook"
_ "github.com/iawia002/lux/extractors/geekbang"
_ "github.com/iawia002/lux/extractors/haokan"
_ "github.com/iawia002/lux/extractors/hupu"
_ "github.com/iawia002/lux/extractors/huya"
_ "github.com/iawia002/lux/extractors/instagram"
_ "github.com/iawia002/lux/extractors/iqiyi"
_ "github.com/iawia002/lux/extractors/ixigua"
_ "github.com/iawia002/lux/extractors/kuaishou"
_ "github.com/iawia002/lux/extractors/mgtv"
_ "github.com/iawia002/lux/extractors/miaopai"
_ "github.com/iawia002/lux/extractors/netease"
_ "github.com/iawia002/lux/extractors/odysee"
_ "github.com/iawia002/lux/extractors/pinterest"
_ "github.com/iawia002/lux/extractors/pixivision"
_ "github.com/iawia002/lux/extractors/pornhub"
_ "github.com/iawia002/lux/extractors/qq"
_ "github.com/iawia002/lux/extractors/reddit"
_ "github.com/iawia002/lux/extractors/rumble"
_ "github.com/iawia002/lux/extractors/streamtape"
_ "github.com/iawia002/lux/extractors/tangdou"
_ "github.com/iawia002/lux/extractors/threads"
_ "github.com/iawia002/lux/extractors/tiktok"
_ "github.com/iawia002/lux/extractors/tumblr"
_ "github.com/iawia002/lux/extractors/twitter"
_ "github.com/iawia002/lux/extractors/udn"
_ "github.com/iawia002/lux/extractors/universal"
_ "github.com/iawia002/lux/extractors/vimeo"
_ "github.com/iawia002/lux/extractors/vk"
_ "github.com/iawia002/lux/extractors/weibo"
_ "github.com/iawia002/lux/extractors/xiaohongshu"
_ "github.com/iawia002/lux/extractors/ximalaya"
_ "github.com/iawia002/lux/extractors/xinpianchang"
_ "github.com/iawia002/lux/extractors/xvideos"
_ "github.com/iawia002/lux/extractors/yinyuetai"
_ "github.com/iawia002/lux/extractors/youku"
_ "github.com/iawia002/lux/extractors/youtube"
_ "github.com/iawia002/lux/extractors/zhihu"
_ "github.com/iawia002/lux/extractors/zingmp3"
)
================================================
FILE: codecov.yml
================================================
codecov:
token: e0f2d44f-c6a7-469a-a688-37c72c0f18f9
================================================
FILE: config/config.go
================================================
package config
// FakeHeaders fake http headers
var FakeHeaders = map[string]string{
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Charset": "UTF-8,*;q=0.5",
"Accept-Encoding": "gzip,deflate,sdch",
"Accept-Language": "en-US,en;q=0.8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
}
================================================
FILE: downloader/downloader.go
================================================
package downloader
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"time"
"github.com/cheggaaa/pb/v3"
"github.com/pkg/errors"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/request"
"github.com/iawia002/lux/utils"
)
// Options defines options used in downloading.
type Options struct {
InfoOnly bool
Silent bool
Stream string
AudioOnly bool
Refer string
OutputPath string
OutputName string
FileNameLength int
Caption bool
EmbedSubtitle bool
MultiThread bool
ThreadNumber int
RetryTimes int
ChunkSizeMB int
// Aria2
UseAria2RPC bool
Aria2Token string
Aria2Method string
Aria2Addr string
}
// Downloader is the default downloader.
type Downloader struct {
Bar *pb.ProgressBar
option Options
}
const (
DOWNLOAD_FILE_EXT = ".download"
)
func progressBar(size int64) *pb.ProgressBar {
tmpl := `{{counters .}} {{bar . "[" "=" ">" "-" "]"}} {{speed .}} {{percent . | green}} {{rtime .}}`
return pb.New64(size).
Set(pb.Bytes, true).
SetMaxWidth(1000).
SetTemplate(pb.ProgressBarTemplate(tmpl))
}
// New returns a new Downloader implementation.
func New(option Options) *Downloader {
downloader := &Downloader{
option: option,
}
return downloader
}
// caption downloads danmaku, subtitles, etc
func (downloader *Downloader) caption(url, fileName, ext string, transform func([]byte) ([]byte, error)) error {
refer := downloader.option.Refer
if refer == "" {
refer = url
}
body, err := request.GetByte(url, refer, nil)
if err != nil {
return err
}
if transform != nil {
body, err = transform(body)
if err != nil {
return err
}
}
filePath, err := utils.FilePath(fileName, ext, downloader.option.FileNameLength, downloader.option.OutputPath, true)
if err != nil {
return err
}
file, fileError := os.Create(filePath)
if fileError != nil {
return fileError
}
defer file.Close() // nolint
if _, err = file.Write(body); err != nil {
return err
}
return nil
}
func (downloader *Downloader) writeFile(url string, file *os.File, headers map[string]string) (int64, error) {
res, err := request.Request(http.MethodGet, url, nil, headers)
if err != nil {
return 0, err
}
defer res.Body.Close() // nolint
barWriter := downloader.Bar.NewProxyWriter(file)
// Note that io.Copy reads 32kb(maximum) from input and writes them to output, then repeats.
// So don't worry about memory.
written, copyErr := io.Copy(barWriter, res.Body)
if copyErr != nil && copyErr != io.EOF {
return written, errors.Errorf("file copy error: %s", copyErr)
}
return written, nil
}
func (downloader *Downloader) save(part *extractors.Part, refer, fileName string) error {
filePath, err := utils.FilePath(fileName, part.Ext, downloader.option.FileNameLength, downloader.option.OutputPath, false)
if err != nil {
return err
}
fileSize, exists, err := utils.FileSize(filePath)
if err != nil {
return err
}
// Skip segment file
// TODO: Live video URLs will not return the size
if exists && fileSize == part.Size {
downloader.Bar.Add64(fileSize)
return nil
}
tempFilePath := filePath + DOWNLOAD_FILE_EXT
tempFileSize, _, err := utils.FileSize(tempFilePath)
if err != nil {
return err
}
headers := map[string]string{
"Referer": refer,
}
var (
file *os.File
fileError error
)
if tempFileSize > 0 {
// range start from 0, 0-1023 means the first 1024 bytes of the file
headers["Range"] = fmt.Sprintf("bytes=%d-", tempFileSize)
file, fileError = os.OpenFile(tempFilePath, os.O_APPEND|os.O_WRONLY, 0644)
downloader.Bar.Add64(tempFileSize)
} else {
file, fileError = os.Create(tempFilePath)
}
if fileError != nil {
return fileError
}
// close and rename temp file at the end of this function
defer func() {
// must close the file before rename or it will cause
// `The process cannot access the file because it is being used by another process.` error.
file.Close() // nolint
if err == nil {
os.Rename(tempFilePath, filePath) // nolint
}
}()
if downloader.option.ChunkSizeMB > 0 {
var start, end, chunkSize int64
chunkSize = int64(downloader.option.ChunkSizeMB) * 1024 * 1024
remainingSize := part.Size
if tempFileSize > 0 {
start = tempFileSize
remainingSize -= tempFileSize
}
chunk := remainingSize / chunkSize
if remainingSize%chunkSize != 0 {
chunk++
}
var i int64 = 1
for ; i <= chunk; i++ {
end = start + chunkSize - 1
headers["Range"] = fmt.Sprintf("bytes=%d-%d", start, end)
temp := start
for i := 0; ; i++ {
written, err := downloader.writeFile(part.URL, file, headers)
if err == nil {
break
} else if i+1 >= downloader.option.RetryTimes {
return err
}
temp += written
headers["Range"] = fmt.Sprintf("bytes=%d-%d", temp, end)
time.Sleep(1 * time.Second)
}
start = end + 1
}
} else {
temp := tempFileSize
for i := 0; ; i++ {
written, err := downloader.writeFile(part.URL, file, headers)
if err == nil {
break
} else if i+1 >= downloader.option.RetryTimes {
return err
}
temp += written
headers["Range"] = fmt.Sprintf("bytes=%d-", temp)
time.Sleep(1 * time.Second)
}
}
return nil
}
func (downloader *Downloader) multiThreadSave(dataPart *extractors.Part, refer, fileName string) error {
filePath, err := utils.FilePath(fileName, dataPart.Ext, downloader.option.FileNameLength, downloader.option.OutputPath, false)
if err != nil {
return err
}
fileSize, exists, err := utils.FileSize(filePath)
if err != nil {
return err
}
// Skip segment file
// TODO: Live video URLs will not return the size
if exists && fileSize == dataPart.Size {
downloader.Bar.Add64(fileSize)
return nil
}
tmpFilePath := filePath + DOWNLOAD_FILE_EXT
tmpFileSize, tmpExists, err := utils.FileSize(tmpFilePath)
if err != nil {
return err
}
if tmpExists {
if tmpFileSize == dataPart.Size {
downloader.Bar.Add64(dataPart.Size)
return os.Rename(tmpFilePath, filePath)
}
if err = os.Remove(tmpFilePath); err != nil {
return err
}
}
// Scan all parts
parts, err := readDirAllFilePart(filePath, fileName, dataPart.Ext)
if err != nil {
return err
}
var unfinishedPart []*FilePartMeta
savedSize := int64(0)
if len(parts) > 0 {
lastEnd := int64(-1)
for i, part := range parts {
// If some parts are lost, re-insert one part.
if part.Start-lastEnd != 1 {
newPart := &FilePartMeta{
Index: part.Index - 0.000001,
Start: lastEnd + 1,
End: part.Start - 1,
Cur: lastEnd + 1,
}
tmp := append([]*FilePartMeta{}, parts[:i]...)
tmp = append(tmp, newPart)
parts = append(tmp, parts[i:]...)
unfinishedPart = append(unfinishedPart, newPart)
}
// When the part has been downloaded in whole, part.Cur is equal to part.End + 1
if part.Cur <= part.End+1 {
savedSize += part.Cur - part.Start
if part.Cur < part.End+1 {
unfinishedPart = append(unfinishedPart, part)
}
} else {
// The size of this part has been saved greater than the part size, delete it transparently and re-download.
err = os.Remove(filePartPath(filePath, part))
if err != nil {
return err
}
part.Cur = part.Start
unfinishedPart = append(unfinishedPart, part)
}
lastEnd = part.End
}
if lastEnd != dataPart.Size-1 {
newPart := &FilePartMeta{
Index: parts[len(parts)-1].Index + 1,
Start: lastEnd + 1,
End: dataPart.Size - 1,
Cur: lastEnd + 1,
}
parts = append(parts, newPart)
unfinishedPart = append(unfinishedPart, newPart)
}
} else {
var start, end, partSize int64
var i float32
partSize = dataPart.Size / int64(downloader.option.ThreadNumber)
i = 0
for start < dataPart.Size {
end = start + partSize - 1
if end > dataPart.Size {
end = dataPart.Size - 1
} else if int(i+1) == downloader.option.ThreadNumber && end < dataPart.Size {
end = dataPart.Size - 1
}
part := &FilePartMeta{
Index: i,
Start: start,
End: end,
Cur: start,
}
parts = append(parts, part)
unfinishedPart = append(unfinishedPart, part)
start = end + 1
i++
}
}
if savedSize > 0 {
downloader.Bar.Add64(savedSize)
if savedSize == dataPart.Size {
return mergeMultiPart(filePath, parts)
}
}
wgp := utils.NewWaitGroupPool(downloader.option.ThreadNumber)
var errs []error
var mu sync.Mutex
for _, part := range unfinishedPart {
wgp.Add()
go func(part *FilePartMeta) {
file, err := os.OpenFile(filePartPath(filePath, part), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
mu.Lock()
errs = append(errs, err)
mu.Unlock()
return
}
defer func() {
file.Close() // nolint
wgp.Done()
}()
var end, chunkSize int64
headers := map[string]string{
"Referer": refer,
}
if downloader.option.ChunkSizeMB <= 0 {
chunkSize = part.End - part.Start + 1
} else {
chunkSize = int64(downloader.option.ChunkSizeMB) * 1024 * 1024
}
remainingSize := part.End - part.Cur + 1
if part.Cur == part.Start {
// Only write part to new file.
err = writeFilePartMeta(file, part)
if err != nil {
mu.Lock()
errs = append(errs, err)
mu.Unlock()
return
}
}
for remainingSize > 0 {
end = computeEnd(part.Cur, chunkSize, part.End)
headers["Range"] = fmt.Sprintf("bytes=%d-%d", part.Cur, end)
temp := part.Cur
for i := 0; ; i++ {
written, err := downloader.writeFile(dataPart.URL, file, headers)
if err == nil {
remainingSize -= chunkSize
break
} else if i+1 >= downloader.option.RetryTimes {
mu.Lock()
errs = append(errs, err)
mu.Unlock()
return
}
temp += written
headers["Range"] = fmt.Sprintf("bytes=%d-%d", temp, end)
}
part.Cur = end + 1
}
}(part)
}
wgp.Wait()
if len(errs) > 0 {
return errs[0]
}
return mergeMultiPart(filePath, parts)
}
func filePartPath(filepath string, part *FilePartMeta) string {
return fmt.Sprintf("%s.part%f", filepath, part.Index)
}
func computeEnd(s, chunkSize, max int64) int64 {
var end int64
end = s + chunkSize - 1
if end > max {
end = max
}
return end
}
func readDirAllFilePart(filePath, filename, extname string) ([]*FilePartMeta, error) {
dirPath := filepath.Dir(filePath)
dir, err := os.Open(dirPath)
if err != nil {
return nil, errors.WithStack(err)
}
defer dir.Close() // nolint
fns, err := dir.Readdir(0)
if err != nil {
return nil, errors.WithStack(err)
}
var metas []*FilePartMeta
reg := regexp.MustCompile(fmt.Sprintf("%s.%s.part.+", regexp.QuoteMeta(filename), extname))
for _, fn := range fns {
if reg.MatchString(fn.Name()) {
meta, err := parseFilePartMeta(path.Join(dirPath, fn.Name()), fn.Size())
if err != nil {
return nil, errors.WithStack(err)
}
metas = append(metas, meta)
}
}
sort.SliceStable(metas, func(i, j int) bool {
return metas[i].Index < metas[j].Index
})
return metas, nil
}
func parseFilePartMeta(filepath string, fileSize int64) (*FilePartMeta, error) {
meta := new(FilePartMeta)
size := binary.Size(*meta)
file, err := os.OpenFile(filepath, os.O_RDWR, 0666)
if err != nil {
return nil, errors.WithStack(err)
}
defer file.Close() // nolint
var buf [512]byte
readSize, err := file.ReadAt(buf[0:size], 0)
if err != nil && err != io.EOF {
return nil, errors.WithStack(err)
}
if readSize < size {
return nil, errors.Errorf("the file has been broken, please delete all part files and re-download")
}
err = binary.Read(bytes.NewBuffer(buf[:size]), binary.LittleEndian, meta)
if err != nil {
return nil, errors.WithStack(err)
}
savedSize := fileSize - int64(binary.Size(meta))
meta.Cur = meta.Start + savedSize
return meta, nil
}
func writeFilePartMeta(file *os.File, meta *FilePartMeta) error {
return binary.Write(file, binary.LittleEndian, meta)
}
func mergeMultiPart(filepath string, parts []*FilePartMeta) error {
tempFilePath := filepath + DOWNLOAD_FILE_EXT
tempFile, err := os.OpenFile(tempFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return err
}
var partFiles []*os.File
defer func() {
for _, f := range partFiles {
f.Close() // nolint
os.Remove(f.Name()) // nolint
}
}()
for _, part := range parts {
file, err := os.Open(filePartPath(filepath, part))
if err != nil {
return err
}
partFiles = append(partFiles, file)
_, err = file.Seek(int64(binary.Size(part)), 0)
if err != nil {
return err
}
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
}
tempFile.Close() // nolint
err = os.Rename(tempFilePath, filepath)
return err
}
func (downloader *Downloader) aria2(title string, stream *extractors.Stream) error {
rpcData := Aria2RPCData{
JSONRPC: "2.0",
ID: "lux", // can be modified
Method: "aria2.addUri",
}
rpcData.Params[0] = "token:" + downloader.option.Aria2Token
var urls []string
for _, p := range stream.Parts {
urls = append(urls, p.URL)
}
var inputs Aria2Input
inputs.Header = append(inputs.Header, "Referer: "+downloader.option.Refer)
for i := range urls {
rpcData.Params[1] = urls[i : i+1]
inputs.Out = fmt.Sprintf("%s[%d].%s", title, i, stream.Parts[0].Ext)
rpcData.Params[2] = &inputs
jsonData, err := json.Marshal(rpcData)
if err != nil {
return err
}
reqURL := fmt.Sprintf("%s://%s/jsonrpc", downloader.option.Aria2Method, downloader.option.Aria2Addr)
req, err := http.NewRequest(http.MethodPost, reqURL, bytes.NewBuffer(jsonData))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
var client = http.Client{Timeout: 30 * time.Second}
res, err := client.Do(req)
if err != nil {
return err
}
// The http Client and Transport guarantee that Body is always
// non-nil, even on responses without a body or responses with
// a zero-length body.
res.Body.Close() // nolint
}
return nil
}
// Download download urls
func (downloader *Downloader) Download(data *extractors.Data) error {
if len(data.Streams) == 0 {
return errors.Errorf("no streams in title %s", data.Title)
}
sortedStreams := genSortedStreams(data.Streams)
if downloader.option.InfoOnly {
printInfo(data, sortedStreams)
return nil
}
title := downloader.option.OutputName
if title == "" {
title = data.Title
}
title = utils.FileName(title, "", downloader.option.FileNameLength)
streamName := downloader.option.Stream
if streamName == "" {
streamName = sortedStreams[0].ID
}
stream, ok := data.Streams[streamName]
if !ok {
return errors.Errorf("no stream named %s", streamName)
}
if downloader.option.AudioOnly {
var isFound bool
reg, err := regexp.Compile("audio+")
if err != nil {
return err
}
for _, s := range sortedStreams {
// Looking for the best quality
if reg.MatchString(s.Quality) {
isFound = true
stream = data.Streams[s.ID]
break
}
for _, part := range s.Parts {
if part.Ext == "m4a" {
isFound = true
stream = data.Streams[s.ID]
break
}
}
}
if !isFound {
return errors.Errorf("No audio stream found")
}
}
if !downloader.option.Silent {
printStreamInfo(data, stream)
}
// download caption
var subtitlePaths []string
var subtitleLangs []string
var subtitleFilesToDelete []string
if downloader.option.Caption && data.Captions != nil {
fmt.Println("\nDownloading captions...")
for k, v := range data.Captions {
if v != nil {
fmt.Printf("Downloading %s ...\n", k)
if err := downloader.caption(v.URL, title, v.Ext, v.Transform); err != nil {
// nolint
} else if downloader.option.EmbedSubtitle {
subtitlePath, _ := utils.FilePath(title, v.Ext, downloader.option.FileNameLength, downloader.option.OutputPath, true)
subtitleFilesToDelete = append(subtitleFilesToDelete, subtitlePath)
if strings.HasSuffix(v.Ext, "xml") {
if srtPath, err := utils.ConvertXMLFileToSRT(subtitlePath); err == nil {
subtitlePath = srtPath
subtitleFilesToDelete = append(subtitleFilesToDelete, srtPath)
}
}
subtitlePaths = append(subtitlePaths, subtitlePath)
subtitleLangs = append(subtitleLangs, k)
}
}
}
}
// Use aria2 rpc to download
if downloader.option.UseAria2RPC {
return downloader.aria2(title, stream)
}
// Skip the complete file that has been merged
mergedFilePath, err := utils.FilePath(title, stream.Ext, downloader.option.FileNameLength, downloader.option.OutputPath, false)
if err != nil {
return err
}
_, mergedFileExists, err := utils.FileSize(mergedFilePath)
if err != nil {
return err
}
// After the merge, the file size has changed, so we do not check whether the size matches
if mergedFileExists {
fmt.Printf("%s: file already exists, skipping\n", mergedFilePath)
return nil
}
downloader.Bar = progressBar(stream.Size)
if !downloader.option.Silent {
downloader.Bar.Start()
}
if len(stream.Parts) == 1 {
// only one fragment
var err error
if downloader.option.MultiThread {
err = downloader.multiThreadSave(stream.Parts[0], data.URL, title)
} else {
err = downloader.save(stream.Parts[0], data.URL, title)
}
if err != nil {
return err
}
downloader.Bar.Finish()
if downloader.option.EmbedSubtitle && len(subtitlePaths) > 0 {
if !downloader.option.Silent {
fmt.Println("Embedding subtitles...")
}
if err := utils.EmbedSubtitles(mergedFilePath, subtitlePaths, subtitleLangs); err != nil {
return err
}
for _, path := range subtitleFilesToDelete {
os.Remove(path)
}
}
return nil
}
wgp := utils.NewWaitGroupPool(downloader.option.ThreadNumber)
// multiple fragments
errs := make([]error, 0)
lock := sync.Mutex{}
parts := make([]string, len(stream.Parts))
for index, part := range stream.Parts {
if len(errs) > 0 {
break
}
if downloader.option.AudioOnly && (part.Ext != "m4a") {
continue
}
partFileName := fmt.Sprintf("%s[%d]", title, index)
partFilePath, err := utils.FilePath(partFileName, part.Ext, downloader.option.FileNameLength, downloader.option.OutputPath, false)
if err != nil {
return err
}
parts[index] = partFilePath
wgp.Add()
go func(part *extractors.Part, fileName string) {
defer wgp.Done()
var err error
if downloader.option.MultiThread {
err = downloader.multiThreadSave(part, data.URL, fileName)
} else {
err = downloader.save(part, data.URL, fileName)
}
if err != nil {
lock.Lock()
errs = append(errs, err)
lock.Unlock()
}
}(part, partFileName)
}
wgp.Wait()
if len(errs) > 0 {
return errs[0]
}
downloader.Bar.Finish()
if data.Type != extractors.DataTypeVideo || downloader.option.AudioOnly {
return nil
}
if !downloader.option.Silent {
fmt.Printf("Merging video parts into %s\n", mergedFilePath)
}
if stream.Ext != "mp4" || stream.NeedMux {
if err := utils.MergeFilesWithSameExtension(parts, mergedFilePath); err != nil {
return err
}
} else {
if err := utils.MergeToMP4(parts, mergedFilePath, title); err != nil {
return err
}
}
if downloader.option.EmbedSubtitle && len(subtitlePaths) > 0 {
if !downloader.option.Silent {
fmt.Println("Embedding subtitles...")
}
if err := utils.EmbedSubtitles(mergedFilePath, subtitlePaths, subtitleLangs); err != nil {
return err
}
for _, path := range subtitleFilesToDelete {
os.Remove(path)
}
}
return nil
}
================================================
FILE: downloader/downloader_test.go
================================================
package downloader
import (
"testing"
"github.com/iawia002/lux/extractors"
)
func TestDownload(t *testing.T) {
testCases := []struct {
name string
data *extractors.Data
}{
{
name: "normal test",
data: &extractors.Data{
Site: "douyin",
Title: "test",
Type: extractors.DataTypeVideo,
URL: "https://www.douyin.com",
Streams: map[string]*extractors.Stream{
"default": {
ID: "default",
Parts: []*extractors.Part{
{
URL: "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f9a0000bc117isuatl67cees890&line=0",
Size: 4927877,
Ext: "mp4",
},
},
},
},
},
},
{
name: "multi-stream test",
data: &extractors.Data{
Site: "douyin",
Title: "test2",
Type: extractors.DataTypeVideo,
URL: "https://www.douyin.com",
Streams: map[string]*extractors.Stream{
"miaopai": {
ID: "miaopai",
Parts: []*extractors.Part{
{
URL: "https://txycdn.miaopai.com/stream/KwR26jUGh2ySnVjYbQiFmomNjP14LtMU3vi6sQ__.mp4?ssig=6594aa01a78e78f50c65c164d186ba9e&time_stamp=1537070910786",
Size: 4011590,
Ext: "mp4",
},
},
Size: 4011590,
},
"douyin": {
ID: "douyin",
Parts: []*extractors.Part{
{
URL: "https://aweme.snssdk.com/aweme/v1/playwm/?video_id=v0200f9a0000bc117isuatl67cees890&line=0",
Size: 4927877,
Ext: "mp4",
},
},
Size: 4927877,
},
},
},
},
{
name: "image test",
data: &extractors.Data{
Site: "bcy",
Title: "bcy image test",
Type: extractors.DataTypeImage,
URL: "https://www.bcyimg.com",
Streams: map[string]*extractors.Stream{
"default": {
ID: "default",
Parts: []*extractors.Part{
{
URL: "http://img5.bcyimg.com/coser/143767/post/c0j7x/0d713eb41a614053ac6a3b146914f6bc.jpg/w650",
Size: 56107,
Ext: "jpg",
},
{
URL: "http://img9.bcyimg.com/coser/143767/post/c0j7x/d17e9b8587794d939a1363c5f715014b.jpg/w650",
Size: 142100,
Ext: "jpg",
},
},
},
},
},
},
}
for _, testCase := range testCases {
err := New(Options{}).Download(testCase.data)
if err != nil {
t.Error(err)
}
}
}
================================================
FILE: downloader/types.go
================================================
package downloader
// Aria2RPCData defines the data structure of json RPC 2.0 info for Aria2
type Aria2RPCData struct {
// More info about RPC interface please refer to
// https://aria2.github.io/manual/en/html/aria2c.html#rpc-interface
JSONRPC string `json:"jsonrpc"`
ID string `json:"id"`
// For a simple download, only implemented `addUri`
Method string `json:"method"`
// secret, uris, options
Params [3]interface{} `json:"params"`
}
// Aria2Input is options for `aria2.addUri`
// https://aria2.github.io/manual/en/html/aria2c.html#id3
type Aria2Input struct {
// The file name of the downloaded file
Out string `json:"out"`
// For a simple download, only add headers
Header []string `json:"header"`
}
// FilePartMeta defines the data structure of file meta info.
type FilePartMeta struct {
Index float32
Start int64
End int64
Cur int64
}
================================================
FILE: downloader/utils.go
================================================
package downloader
import (
"fmt"
"sort"
"github.com/fatih/color"
"github.com/iawia002/lux/extractors"
)
var (
blue = color.New(color.FgBlue)
cyan = color.New(color.FgCyan)
)
func genSortedStreams(streams map[string]*extractors.Stream) []*extractors.Stream {
sortedStreams := make([]*extractors.Stream, 0, len(streams))
for _, data := range streams {
sortedStreams = append(sortedStreams, data)
}
if len(sortedStreams) > 1 {
sort.SliceStable(
sortedStreams, func(i, j int) bool { return sortedStreams[i].Size > sortedStreams[j].Size },
)
}
return sortedStreams
}
func printHeader(data *extractors.Data) {
fmt.Println()
cyan.Printf(" Site: ") // nolint
fmt.Println(data.Site)
cyan.Printf(" Title: ") // nolint
fmt.Println(data.Title)
cyan.Printf(" Type: ") // nolint
fmt.Println(data.Type)
}
func printStream(stream *extractors.Stream) {
blue.Println(fmt.Sprintf(" [%s] -------------------", stream.ID)) // nolint
if stream.Quality != "" {
cyan.Printf(" Quality: ") // nolint
fmt.Println(stream.Quality)
}
cyan.Printf(" Size: ") // nolint
fmt.Printf("%.2f MiB (%d Bytes)\n", float64(stream.Size)/(1024*1024), stream.Size)
cyan.Printf(" # download with: ") // nolint
fmt.Printf("lux -f %s ...\n\n", stream.ID)
}
func printInfo(data *extractors.Data, sortedStreams []*extractors.Stream) {
printHeader(data)
if len(data.Captions) > 0 {
cyan.Printf(" Captions: ") // nolint
languages := make([]string, 0, len(data.Captions))
for lang := range data.Captions {
languages = append(languages, lang)
}
sort.Strings(languages)
captionList := ""
for _, lang := range languages {
caption := data.Captions[lang]
if caption == nil {
continue
}
captionList += fmt.Sprintf("%s ", lang)
}
fmt.Println(captionList)
}
cyan.Printf(" Streams: ") // nolint
fmt.Println("# All available quality")
for _, stream := range sortedStreams {
printStream(stream)
}
}
func printStreamInfo(data *extractors.Data, stream *extractors.Stream) {
printHeader(data)
cyan.Printf(" Stream: ") // nolint
fmt.Println()
printStream(stream)
}
================================================
FILE: extractors/acfun/acfun.go
================================================
package acfun
import (
"fmt"
"net/url"
"regexp"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/parser"
"github.com/iawia002/lux/request"
"github.com/iawia002/lux/utils"
)
func init() {
extractors.Register("acfun", New())
}
const (
bangumiDataPattern = "window.pageInfo = window.bangumiData = (.*);"
bangumiListPattern = "window.bangumiList = (.*);"
bangumiHTMLURL = "https://www.acfun.cn/bangumi/aa%d_36188_%d"
referer = "https://www.acfun.cn"
)
type extractor struct{}
// New returns a new acfun bangumi extractor
func New() extractors.Extractor {
return &extractor{}
}
// Extract ...
func (e *extractor) Extract(URL string, option extractors.Options) ([]*extractors.Data, error) {
html, err := request.GetByte(URL, referer, nil)
if err != nil {
return nil, errors.WithStack(err)
}
epDatas := make([]*episodeData, 0)
if option.Playlist {
list, err := resolvingEpisodes(html)
if err != nil {
return nil, errors.WithStack(err)
}
items := utils.NeedDownloadList(option.Items, option.ItemStart, option.ItemEnd, len(list.Episodes))
for _, item := range items {
epDatas = append(epDatas, list.Episodes[item-1])
}
} else {
bgData, _, err := resolvingData(html)
if err != nil {
return nil, errors.WithStack(err)
}
epDatas = append(epDatas, &bgData.episodeData)
}
datas := make([]*extractors.Data, 0)
wgp := utils.NewWaitGroupPool(option.ThreadNumber)
for _, epData := range epDatas {
t := epData
wgp.Add()
go func() {
defer wgp.Done()
datas = append(datas, extractBangumi(concatURL(t)))
}()
}
wgp.Wait()
return datas, nil
}
func concatURL(epData *episodeData) string {
return fmt.Sprintf(bangumiHTMLURL, epData.BangumiID, epData.ItemID)
}
func extractBangumi(URL string) *extractors.Data {
var err error
html, err := request.GetByte(URL, referer, nil)
if err != nil {
return extractors.EmptyData(URL, err)
}
_, vInfo, err := resolvingData(html)
if err != nil {
return extractors.EmptyData(URL, err)
}
streams := make(map[string]*extractors.Stream)
for _, stm := range vInfo.AdaptationSet[0].Streams {
m3u8URL, err := url.Parse(stm.URL)
if err != nil {
return extractors.EmptyData(URL, err)
}
urls, err := utils.M3u8URLs(m3u8URL.String())
if err != nil {
_, err = url.Parse(stm.URL)
if err != nil {
return extractors.EmptyData(URL, err)
}
urls, err = utils.M3u8URLs(stm.BackURL)
if err != nil {
return extractors.EmptyData(URL, err)
}
}
// There is no size information in the m3u8 file and the calculation will take too much time, just ignore it.
parts := make([]*extractors.Part, 0)
for _, u := range urls {
parts = append(parts, &extractors.Part{
URL: u,
Ext: "ts",
})
}
streams[stm.QualityLabel] = &extractors.Stream{
ID: stm.QualityType,
Parts: parts,
Quality: stm.QualityType,
NeedMux: false,
}
}
doc, err := parser.GetDoc(string(html))
if err != nil {
return extractors.EmptyData(URL, err)
}
data := &extractors.Data{
Site: "AcFun acfun.cn",
Title: parser.Title(doc),
Type: extractors.DataTypeVideo,
Streams: streams,
URL: URL,
}
return data
}
func resolvingData(html []byte) (*bangumiData, *videoInfo, error) {
bgData := &bangumiData{}
vInfo := &videoInfo{}
pattern, _ := regexp.Compile(bangumiDataPattern)
groups := pattern.FindSubmatch(html)
err := jsoniter.Unmarshal(groups[1], bgData)
if err != nil {
return nil, nil, errors.WithStack(err)
}
err = jsoniter.UnmarshalFromString(bgData.CurrentVideoInfo.KsPlayJSON, vInfo)
if err != nil {
return nil, nil, errors.WithStack(err)
}
return bgData, vInfo, nil
}
func resolvingEpisodes(html []byte) (*episodeList, error) {
list := &episodeList{}
pattern, _ := regexp.Compile(bangumiListPattern)
groups := pattern.FindSubmatch(html)
err := jsoniter.Unmarshal(groups[1], list)
if err != nil {
return nil, errors.WithStack(err)
}
return list, nil
}
================================================
FILE: extractors/acfun/acfun_test.go
================================================
package acfun
import (
"testing"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/test"
)
func TestDownload(t *testing.T) {
tests := []struct {
name string
args test.Args
}{
{
name: "normal test",
args: test.Args{
URL: "https://www.acfun.cn/bangumi/aa6000686_36188_1704167",
Title: "瑞克和莫蒂 第四季 :第2话 注释版",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := New().Extract(tt.args.URL, extractors.Options{})
test.CheckError(t, err)
test.Check(t, tt.args, data[0])
})
}
}
================================================
FILE: extractors/acfun/types.go
================================================
package acfun
type episodeData struct {
ItemID int64 `json:"itemId"`
EpisodeName string `json:"episodeName"`
BangumiID int64 `json:"bangumiId"`
VideoID int64 `json:"videoId"`
}
type bangumiData struct {
episodeData
BangumiTitle string `json:"bangumiTitle"`
CurrentVideoInfo struct {
KsPlayJSON string `json:"ksPlayJson"`
} `json:"currentVideoInfo"`
}
type videoInfo struct {
AdaptationSet []struct {
Streams streams `json:"representation"`
} `json:"adaptationSet"`
}
type streams []stream
type episodeList struct {
Episodes []*episodeData `json:"items"`
}
type stream struct {
ID int64 `json:"id"`
BackURL string `json:"backUrl"`
Codecs string `json:"codecs"`
URL string `json:"url"`
BitRate int64 `json:"avgBitrate"`
QualityType string `json:"qualityType"`
QualityLabel string `json:"qualityLabel"`
}
================================================
FILE: extractors/bcy/bcy.go
================================================
package bcy
import (
"encoding/json"
"strings"
"github.com/pkg/errors"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/parser"
"github.com/iawia002/lux/request"
"github.com/iawia002/lux/utils"
)
func init() {
extractors.Register("bcy", New())
}
type bcyData struct {
Detail struct {
PostData struct {
Multi []struct {
OriginalPath string `json:"original_path"`
} `json:"multi"`
} `json:"post_data"`
} `json:"detail"`
}
type extractor struct{}
// New returns a bcy extractor.
func New() extractors.Extractor {
return &extractor{}
}
// Extract is the main function to extract the data.
func (e *extractor) Extract(url string, option extractors.Options) ([]*extractors.Data, error) {
html, err := request.Get(url, url, nil)
if err != nil {
return nil, errors.WithStack(err)
}
// parse json data
rep := strings.NewReplacer(`\"`, `"`, `\\`, `\`)
realURLs := utils.MatchOneOf(html, `JSON.parse\("(.+?)"\);`)
if realURLs == nil || len(realURLs) < 2 {
return nil, errors.WithStack(extractors.ErrURLParseFailed)
}
jsonString := rep.Replace(realURLs[1])
var data bcyData
if err = json.Unmarshal([]byte(jsonString), &data); err != nil {
return nil, errors.WithStack(err)
}
doc, err := parser.GetDoc(html)
if err != nil {
return nil, errors.WithStack(err)
}
title := strings.Replace(parser.Title(doc), " - 半次元 banciyuan - ACG爱好者社区", "", -1)
parts := make([]*extractors.Part, 0, len(data.Detail.PostData.Multi))
var totalSize int64
for _, img := range data.Detail.PostData.Multi {
size, err := request.Size(img.OriginalPath, url)
if err != nil {
return nil, errors.WithStack(err)
}
totalSize += size
_, ext, err := utils.GetNameAndExt(img.OriginalPath)
if err != nil {
return nil, errors.WithStack(err)
}
parts = append(parts, &extractors.Part{
URL: img.OriginalPath,
Size: size,
Ext: ext,
})
}
streams := map[string]*extractors.Stream{
"default": {
Parts: parts,
Size: totalSize,
},
}
return []*extractors.Data{
{
Site: "半次元 bcy.net",
Title: title,
Type: extractors.DataTypeImage,
Streams: streams,
URL: url,
},
}, nil
}
================================================
FILE: extractors/bcy/bcy_test.go
================================================
package bcy
import (
"testing"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/test"
)
func TestDownload(t *testing.T) {
tests := []struct {
name string
args test.Args
}{
{
name: "normal test",
args: test.Args{
URL: "https://bcy.net/item/detail/6558738153367142664",
Title: "cos正片 命运石之门 牧濑红莉栖 克里斯蒂娜… - 半次元 - ACG爱好者社区",
Size: 13035763,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := New().Extract(tt.args.URL, extractors.Options{})
test.CheckError(t, err)
test.Check(t, tt.args, data[0])
})
}
}
================================================
FILE: extractors/bilibili/bilibili.go
================================================
package bilibili
import (
"encoding/json"
"fmt"
"slices"
"sort"
"strconv"
"strings"
"time"
"github.com/pkg/errors"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/parser"
"github.com/iawia002/lux/request"
"github.com/iawia002/lux/utils"
)
func init() {
bilibiliExtractor := New()
extractors.Register("bilibili", bilibiliExtractor)
extractors.Register("b23", bilibiliExtractor)
}
const (
bilibiliAPI = "https://api.bilibili.com/x/player/playurl?"
bilibiliBangumiAPI = "https://api.bilibili.com/pgc/player/web/playurl?"
bilibiliTokenAPI = "https://api.bilibili.com/x/player/playurl/token?"
)
const referer = "https://www.bilibili.com"
var utoken string
func genAPI(aid, cid, quality int, bvid string, bangumi bool, cookie string) (string, error) {
var (
err error
baseAPIURL string
params string
)
if cookie != "" && utoken == "" {
utoken, err = request.Get(
fmt.Sprintf("%said=%d&cid=%d", bilibiliTokenAPI, aid, cid),
referer,
nil,
)
if err != nil {
return "", err
}
var t token
err = json.Unmarshal([]byte(utoken), &t)
if err != nil {
return "", err
}
if t.Code != 0 {
return "", errors.Errorf("cookie error: %s", t.Message)
}
utoken = t.Data.Token
}
var api string
if bangumi {
// The parameters need to be sorted by name
// qn=0 flag makes the CDN address different every time
// quality=120(4k) is the highest quality so far
params = fmt.Sprintf(
"cid=%d&bvid=%s&qn=%d&type=&otype=json&fourk=1&fnver=0&fnval=16",
cid, bvid, quality,
)
baseAPIURL = bilibiliBangumiAPI
} else {
params = fmt.Sprintf(
"avid=%d&cid=%d&bvid=%s&qn=%d&type=&otype=json&fourk=1&fnver=0&fnval=2000",
aid, cid, bvid, quality,
)
baseAPIURL = bilibiliAPI
}
api = baseAPIURL + params
// bangumi utoken also need to put in params to sign, but the ordinary video doesn't need
if !bangumi && utoken != "" {
api = fmt.Sprintf("%s&utoken=%s", api, utoken)
}
return api, nil
}
type bilibiliOptions struct {
url string
html string
bangumi bool
aid int
cid int
bvid string
page int
subtitle string
}
func extractBangumi(url, html string, extractOption extractors.Options) ([]*extractors.Data, error) {
dataString := utils.MatchOneOf(html, `const playurlSSRData = ({[\s\S]+})`)[1]
epArrayString := utils.MatchOneOf(dataString, `"episode_info"\s*:\s*(.+?)\s*,\s*"season_info"`)[1]
fullVideoIdString := utils.MatchOneOf(dataString, `"videoId"\s*:\s*"(ep|ss)(\d+)"`)
epSsString := fullVideoIdString[1] // "ep" or "ss"
videoIdString := fullVideoIdString[2]
var epArray EpVideoInfo
err := json.Unmarshal([]byte(epArrayString), &epArray)
if err != nil {
return nil, errors.WithStack(err)
}
var data bangumiData
videoId, err := strconv.ParseInt(videoIdString, 10, 0)
if err != nil {
return nil, errors.WithStack(err)
}
if epArray.EpID == int(videoId) || (epSsString == "ss" && epArray.Title == "第1话") {
data.EpInfo = epArray
}
data.EpList = append(data.EpList, epArray)
sort.Slice(data.EpList, func(i, j int) bool {
return data.EpList[i].EpID < data.EpList[j].EpID
})
if !extractOption.Playlist {
aid := data.EpInfo.Aid
cid := data.EpInfo.Cid
bvid := data.EpInfo.Bvid
titleFormat := data.EpInfo.Title
longTitle := data.EpInfo.LongTitle
if aid <= 0 || cid <= 0 || bvid == "" {
aid = data.EpList[0].Aid
cid = data.EpList[0].Cid
bvid = data.EpList[0].Bvid
titleFormat = data.EpList[0].Title
longTitle = data.EpList[0].LongTitle
}
options := bilibiliOptions{
url: url,
html: html,
bangumi: true,
aid: aid,
cid: cid,
bvid: bvid,
subtitle: fmt.Sprintf("%s %s", titleFormat, longTitle),
}
return []*extractors.Data{bilibiliDownload(options, extractOption)}, nil
}
// handle bangumi playlist
needDownloadItems := utils.NeedDownloadList(extractOption.Items, extractOption.ItemStart, extractOption.ItemEnd, len(data.EpList))
extractedData := make([]*extractors.Data, len(needDownloadItems))
wgp := utils.NewWaitGroupPool(extractOption.ThreadNumber)
dataIndex := 0
for index, u := range data.EpList {
if !slices.Contains(needDownloadItems, index+1) {
continue
}
wgp.Add()
id := u.EpID
if id == 0 {
id = u.EpID
}
// html content can't be reused here
options := bilibiliOptions{
url: fmt.Sprintf("https://www.bilibili.com/bangumi/play/ep%d", id),
bangumi: true,
aid: u.Aid,
cid: u.Cid,
bvid: u.Bvid,
subtitle: fmt.Sprintf("%s %s", u.Title, u.LongTitle),
}
go func(index int, options bilibiliOptions, extractedData []*extractors.Data) {
defer wgp.Done()
extractedData[index] = bilibiliDownload(options, extractOption)
}(dataIndex, options, extractedData)
dataIndex++
}
wgp.Wait()
return extractedData, nil
}
func getMultiPageData(html string) (*multiPage, error) {
var data multiPage
multiPageDataString := utils.MatchOneOf(
html, `window.__INITIAL_STATE__=(.+?);\(function`,
)
if multiPageDataString == nil {
return &data, errors.New("this page has no playlist")
}
err := json.Unmarshal([]byte(multiPageDataString[1]), &data)
if err != nil {
return nil, errors.WithStack(err)
}
return &data, nil
}
func extractFestival(url, html string, extractOption extractors.Options) ([]*extractors.Data, error) {
matches := utils.MatchAll(html, "<\\s*script[^>]*>\\s*window\\.__INITIAL_STATE__=([\\s\\S]*?);\\s?\\(function[\\s\\S]*?<\\/\\s*script\\s*>")
if len(matches) < 1 {
return nil, errors.WithStack(extractors.ErrURLParseFailed)
}
if len(matches[0]) < 2 {
return nil, errors.New("could not find video in page")
}
var festivalData festival
err := json.Unmarshal([]byte(matches[0][1]), &festivalData)
if err != nil {
return nil, errors.WithStack(err)
}
options := bilibiliOptions{
url: url,
html: html,
aid: festivalData.VideoInfo.Aid,
bvid: festivalData.VideoInfo.BVid,
cid: festivalData.VideoInfo.Cid,
page: 0,
}
return []*extractors.Data{bilibiliDownload(options, extractOption)}, nil
}
func extractNormalVideo(url, html string, extractOption extractors.Options) ([]*extractors.Data, error) {
pageData, err := getMultiPageData(html)
if err != nil {
return nil, errors.WithStack(err)
}
if !extractOption.Playlist {
// handle URL that has a playlist, mainly for unified titles
// <h1> tag does not include subtitles
// bangumi doesn't need this
pageString := utils.MatchOneOf(url, `\?p=(\d+)`)
var p int
if pageString == nil {
// https://www.bilibili.com/video/av20827366/
p = 1
} else {
// https://www.bilibili.com/video/av20827366/?p=2
p, _ = strconv.Atoi(pageString[1])
}
if len(pageData.VideoData.Pages) < p || p < 1 {
return nil, errors.WithStack(extractors.ErrURLParseFailed)
}
page := pageData.VideoData.Pages[p-1]
options := bilibiliOptions{
url: url,
html: html,
aid: pageData.Aid,
bvid: pageData.BVid,
cid: page.Cid,
page: p,
}
// "part":"" or "part":"Untitled"
if page.Part == "Untitled" || len(pageData.VideoData.Pages) == 1 {
options.subtitle = ""
} else {
options.subtitle = page.Part
}
return []*extractors.Data{bilibiliDownload(options, extractOption)}, nil
}
// handle normal video playlist
if len(pageData.Sections) == 0 {
// https://www.bilibili.com/video/av20827366/?p=* each video in playlist has different p=?
return multiPageDownload(url, html, extractOption, pageData)
}
// handle another kind of playlist
// https://www.bilibili.com/video/av*** each video in playlist has different av/bv id
return multiEpisodeDownload(url, html, extractOption, pageData)
}
// handle multi episode download
func multiEpisodeDownload(url, html string, extractOption extractors.Options, pageData *multiPage) ([]*extractors.Data, error) {
needDownloadItems := utils.NeedDownloadList(extractOption.Items, extractOption.ItemStart, extractOption.ItemEnd, len(pageData.Sections[0].Episodes))
extractedData := make([]*extractors.Data, len(needDownloadItems))
wgp := utils.NewWaitGroupPool(extractOption.ThreadNumber)
dataIndex := 0
for index, u := range pageData.Sections[0].Episodes {
if !slices.Contains(needDownloadItems, index+1) {
continue
}
wgp.Add()
options := bilibiliOptions{
url: url,
html: html,
aid: u.Aid,
bvid: u.BVid,
cid: u.Cid,
subtitle: fmt.Sprintf("%s P%d", u.Title, index+1),
}
go func(index int, options bilibiliOptions, extractedData []*extractors.Data) {
defer wgp.Done()
extractedData[index] = bilibiliDownload(options, extractOption)
}(dataIndex, options, extractedData)
dataIndex++
}
wgp.Wait()
return extractedData, nil
}
// handle multi page download
func multiPageDownload(url, html string, extractOption extractors.Options, pageData *multiPage) ([]*extractors.Data, error) {
needDownloadItems := utils.NeedDownloadList(extractOption.Items, extractOption.ItemStart, extractOption.ItemEnd, len(pageData.VideoData.Pages))
extractedData := make([]*extractors.Data, len(needDownloadItems))
wgp := utils.NewWaitGroupPool(extractOption.ThreadNumber)
dataIndex := 0
for index, u := range pageData.VideoData.Pages {
if !slices.Contains(needDownloadItems, index+1) {
continue
}
wgp.Add()
options := bilibiliOptions{
url: url,
html: html,
aid: pageData.Aid,
bvid: pageData.BVid,
cid: u.Cid,
subtitle: u.Part,
page: u.Page,
}
go func(index int, options bilibiliOptions, extractedData []*extractors.Data) {
defer wgp.Done()
extractedData[index] = bilibiliDownload(options, extractOption)
}(dataIndex, options, extractedData)
dataIndex++
}
wgp.Wait()
return extractedData, nil
}
type extractor struct{}
// New returns a bilibili extractor.
func New() extractors.Extractor {
return &extractor{}
}
// Extract is the main function to extract the data.
func (e *extractor) Extract(url string, option extractors.Options) ([]*extractors.Data, error) {
var err error
html, err := request.Get(url, referer, nil)
if err != nil {
return nil, errors.WithStack(err)
}
// set thread number to 1 manually to avoid http 412 error
option.ThreadNumber = 1
if strings.Contains(url, "bangumi") {
// handle bangumi
return extractBangumi(url, html, option)
} else if strings.Contains(url, "festival") {
return extractFestival(url, html, option)
} else {
// handle normal video
return extractNormalVideo(url, html, option)
}
}
// bilibiliDownload is the download function for a single URL
func bilibiliDownload(options bilibiliOptions, extractOption extractors.Options) *extractors.Data {
var (
err error
html string
)
if options.html != "" {
// reuse html string, but this can't be reused in case of playlist
html = options.html
} else {
html, err = request.Get(options.url, referer, nil)
if err != nil {
return extractors.EmptyData(options.url, err)
}
}
// Get "accept_quality" and "accept_description"
// "accept_description":["超高清 8K","超清 4K","高清 1080P+","高清 1080P","高清 720P","清晰 480P","流畅 360P"],
// "accept_quality":[127,120,112,80,48,32,16],
api, err := genAPI(options.aid, options.cid, 127, options.bvid, options.bangumi, extractOption.Cookie)
if err != nil {
return extractors.EmptyData(options.url, err)
}
jsonString, err := request.Get(api, referer, nil)
if err != nil {
return extractors.EmptyData(options.url, err)
}
var data dash
err = json.Unmarshal([]byte(jsonString), &data)
if err != nil {
return extractors.EmptyData(options.url, err)
}
var dashData dashInfo
if data.Data.Description == nil {
dashData = data.Result
} else {
dashData = data.Data
}
var audioPart *extractors.Part
if dashData.Streams.Audio != nil {
// Get audio part
var audioID int
audios := map[int]string{}
bandwidth := 0
for _, stream := range dashData.Streams.Audio {
if stream.Bandwidth > bandwidth {
audioID = stream.ID
bandwidth = stream.Bandwidth
}
audios[stream.ID] = stream.BaseURL
}
s, err := request.Size(audios[audioID], referer)
if err != nil {
return extractors.EmptyData(options.url, err)
}
audioPart = &extractors.Part{
URL: audios[audioID],
Size: s,
Ext: "m4a",
}
}
streams := make(map[string]*extractors.Stream, len(dashData.Quality))
for _, stream := range dashData.Streams.Video {
s, err := request.Size(stream.BaseURL, referer)
if err != nil {
return extractors.EmptyData(options.url, err)
}
parts := make([]*extractors.Part, 0, 2)
parts = append(parts, &extractors.Part{
URL: stream.BaseURL,
Size: s,
Ext: getExtFromMimeType(stream.MimeType),
})
if audioPart != nil {
parts = append(parts, audioPart)
}
var size int64
for _, part := range parts {
size += part.Size
}
id := fmt.Sprintf("%d-%d", stream.ID, stream.Codecid)
streams[id] = &extractors.Stream{
Parts: parts,
Size: size,
Quality: fmt.Sprintf("%s %s", qualityString[stream.ID], stream.Codecs),
}
if audioPart != nil {
streams[id].NeedMux = true
}
}
for _, durl := range dashData.DURLs {
var ext string
switch dashData.DURLFormat {
case "flv", "flv480":
ext = "flv"
case "mp4", "hdmp4": // nolint
ext = "mp4"
}
parts := make([]*extractors.Part, 0, 1)
parts = append(parts, &extractors.Part{
URL: durl.URL,
Size: durl.Size,
Ext: ext,
})
streams[strconv.Itoa(dashData.CurQuality)] = &extractors.Stream{
Parts: parts,
Size: durl.Size,
Quality: qualityString[dashData.CurQuality],
}
}
// get the title
doc, err := parser.GetDoc(html)
if err != nil {
return extractors.EmptyData(options.url, err)
}
title := parser.Title(doc)
if options.subtitle != "" {
pageString := ""
if options.page > 0 {
pageString = fmt.Sprintf("P%d ", options.page)
}
if extractOption.EpisodeTitleOnly {
title = fmt.Sprintf("%s%s", pageString, options.subtitle)
} else {
title = fmt.Sprintf("%s %s%s", title, pageString, options.subtitle)
}
}
return &extractors.Data{
Site: "哔哩哔哩 bilibili.com",
Title: title,
Type: extractors.DataTypeVideo,
Streams: streams,
Captions: map[string]*extractors.CaptionPart{
"danmaku": {
Part: extractors.Part{
URL: fmt.Sprintf("https://comment.bilibili.com/%d.xml", options.cid),
Ext: "xml",
},
},
"subtitle": getSubTitleCaptionPart(options.aid, options.cid),
},
URL: options.url,
}
}
func getExtFromMimeType(mimeType string) string {
exts := strings.Split(mimeType, "/")
if len(exts) == 2 {
return exts[1]
}
return "mp4"
}
func getSubTitleCaptionPart(aid int, cid int) *extractors.CaptionPart {
jsonString, err := request.Get(
fmt.Sprintf("http://api.bilibili.com/x/player/wbi/v2?aid=%d&cid=%d", aid, cid), referer, nil,
)
if err != nil {
return nil
}
stu := bilibiliWebInterface{}
err = json.Unmarshal([]byte(jsonString), &stu)
if err != nil || len(stu.Data.SubtitleInfo.SubtitleList) == 0 {
return nil
}
return &extractors.CaptionPart{
Part: extractors.Part{
URL: fmt.Sprintf("https:%s", stu.Data.SubtitleInfo.SubtitleList[0].SubtitleUrl),
Ext: "srt",
},
Transform: subtitleTransform,
}
}
func subtitleTransform(body []byte) ([]byte, error) {
bytes := ""
captionData := bilibiliSubtitleFormat{}
err := json.Unmarshal(body, &captionData)
if err != nil {
return nil, errors.WithStack(err)
}
for i := 0; i < len(captionData.Body); i++ {
bytes += fmt.Sprintf("%d\n%s --> %s\n%s\n\n",
i,
time.Unix(0, int64(captionData.Body[i].From*1000)*int64(time.Millisecond)).UTC().Format("15:04:05.000"),
time.Unix(0, int64(captionData.Body[i].To*1000)*int64(time.Millisecond)).UTC().Format("15:04:05.000"),
captionData.Body[i].Content,
)
}
return []byte(bytes), nil
}
================================================
FILE: extractors/bilibili/bilibili_test.go
================================================
package bilibili
import (
"testing"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/test"
)
func TestBilibili(t *testing.T) {
tests := []struct {
name string
args test.Args
playlist bool
}{
{
name: "normal test 1",
args: test.Args{
URL: "https://www.bilibili.com/video/av20203945/",
Title: "【2018拜年祭单品】相遇day by day",
},
playlist: false,
},
{
name: "normal test 2",
args: test.Args{
URL: "https://www.bilibili.com/video/av41301960",
Title: "【英雄联盟】2019赛季CG 《觉醒》",
},
playlist: false,
},
{
name: "bangumi test",
args: test.Args{
URL: "https://www.bilibili.com/bangumi/play/ep167000",
Title: "狐妖小红娘 第70话 苏苏智商上线",
},
},
{
name: "bangumi playlist test",
args: test.Args{
URL: "https://www.bilibili.com/bangumi/play/ss5050",
Title: "一人之下:第1话 异人刀兵起,道炁携阴阳",
},
playlist: true,
},
{
name: "playlist test",
args: test.Args{
URL: "https://www.bilibili.com/video/av16907446/",
Title: "\"不要相信歌词,他们为了押韵什么都干得出来\"",
},
playlist: true,
},
{
name: "8k test",
args: test.Args{
URL: "https://www.bilibili.com/video/BV1qM4y1w716",
Title: "【8K演示片】B站首发!你的设备还顶得住吗?",
},
},
{
name: "b23 test",
args: test.Args{
URL: "https://b23.tv/Fc9i7QF",
Title: "【十年榜】2000-2009年最强华语金曲TOP100 P1 100爱转角-罗志祥",
},
},
{
name: "festival test",
args: test.Args{
URL: "https://www.bilibili.com/festival/lty10th?bvid=BV1dZ4y1Y7bt",
Title: "洛天依十周年官方演唱会",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var (
data []*extractors.Data
err error
)
if tt.playlist {
// for playlist, we don't check the data
_, err = New().Extract(tt.args.URL, extractors.Options{
Playlist: true,
ThreadNumber: 9,
})
test.CheckError(t, err)
} else {
data, err = New().Extract(tt.args.URL, extractors.Options{})
test.CheckError(t, err)
test.Check(t, tt.args, data[0])
}
})
}
}
================================================
FILE: extractors/bilibili/types.go
================================================
package bilibili
// {"code":0,"message":"0","ttl":1,"data":{"token":"aaa"}}
// {"code":-101,"message":"账号未登录","ttl":1}
type tokenData struct {
Token string `json:"token"`
}
type token struct {
Code int `json:"code"`
Message string `json:"message"`
Data tokenData `json:"data"`
}
type Interaction struct {
Interaction bool `json:"interaction"`
}
type EpVideoInfo struct {
Aid int `json:"aid"`
Bvid string `json:"bvid"`
Cid int `json:"cid"`
DeliveryBusinessFragmentVideo bool `json:"delivery_business_fragment_video"`
DeliveryFragmentVideo bool `json:"delivery_fragment_video"`
EpID int `json:"ep_id"`
EpStatus int `json:"ep_status"`
Interaction Interaction `json:"interaction"`
LongTitle string `json:"long_title"`
Title string `json:"title"`
}
type bangumiData struct {
EpInfo EpVideoInfo `json:"epInfo"`
EpList []EpVideoInfo `json:"epList"`
}
type videoPagesData struct {
Cid int `json:"cid"`
Part string `json:"part"`
Page int `json:"page"`
}
type multiPageVideoData struct {
Title string `json:"title"`
Pages []videoPagesData `json:"pages"`
}
type episode struct {
Aid int `json:"aid"`
Cid int `json:"cid"`
Title string `json:"title"`
BVid string `json:"bvid"`
}
type multiEpisodeData struct {
Seasionid int `json:"season_id"`
Episodes []episode `json:"episodes"`
}
type multiPage struct {
Aid int `json:"aid"`
BVid string `json:"bvid"`
Sections []multiEpisodeData `json:"sections"`
VideoData multiPageVideoData `json:"videoData"`
}
type dashStream struct {
ID int `json:"id"`
BaseURL string `json:"baseUrl"`
Bandwidth int `json:"bandwidth"`
MimeType string `json:"mimeType"`
Codecid int `json:"codecid"`
Codecs string `json:"codecs"`
}
type dashStreams struct {
Video []dashStream `json:"video"`
Audio []dashStream `json:"audio"`
}
type dashInfo struct {
CurQuality int `json:"quality"`
Description []string `json:"accept_description"`
Quality []int `json:"accept_quality"`
Streams dashStreams `json:"dash"`
DURLFormat string `json:"format"`
DURLs []dURL `json:"durl"`
}
type dURL struct {
URL string `json:"url"`
Size int64 `json:"size"`
}
type dash struct {
Code int `json:"code"`
Message string `json:"message"`
Data dashInfo `json:"data"`
Result dashInfo `json:"result"`
}
var qualityString = map[int]string{
127: "超高清 8K",
120: "超清 4K",
116: "高清 1080P60",
74: "高清 720P60",
112: "高清 1080P+",
80: "高清 1080P",
64: "高清 720P",
48: "高清 720P",
32: "清晰 480P",
16: "流畅 360P",
15: "流畅 360P",
}
type subtitleData struct {
From float32 `json:"from"`
To float32 `json:"to"`
Location int `json:"location"`
Content string `json:"content"`
}
type bilibiliSubtitleFormat struct {
FontSize float32 `json:"font_size"`
FontColor string `json:"font_color"`
BackgroundAlpha float32 `json:"background_alpha"`
BackgroundColor string `json:"background_color"`
Stroke string `json:"Stroke"`
Body []subtitleData `json:"body"`
}
type subtitleProperty struct {
ID int64 `json:"id"`
Lan string `json:"lan"`
LanDoc string `json:"lan_doc"`
SubtitleUrl string `json:"subtitle_url"`
}
type subtitleInfo struct {
AllowSubmit bool `json:"allow_submit"`
SubtitleList []subtitleProperty `json:"subtitles"`
}
type bilibiliWebInterfaceData struct {
Bvid string `json:"bvid"`
SubtitleInfo subtitleInfo `json:"subtitle"`
}
type bilibiliWebInterface struct {
Code int `json:"code"`
Data bilibiliWebInterfaceData `json:"data"`
}
type festival struct {
VideoSections []struct {
Id int64 `json:"id"`
Title string `json:"title"`
Type int `json:"type"`
} `json:"videoSections"`
Episodes []episode `json:"episodes"`
VideoInfo struct {
Aid int `json:"aid"`
BVid string `json:"bvid"`
Cid int `json:"cid"`
Title string `json:"title"`
Desc string `json:"desc"`
Pages []struct {
Cid int `json:"cid"`
Duration int `json:"duration"`
Page int `json:"page"`
Part string `json:"part"`
Dimension struct {
Width int `json:"width"`
Height int `json:"height"`
Rotate int `json:"rotate"`
} `json:"dimension"`
} `json:"pages"`
} `json:"videoInfo"`
}
================================================
FILE: extractors/bitchute/bitchute.go
================================================
package bitchute
import (
"compress/flate"
"compress/gzip"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"github.com/pkg/errors"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/request"
)
func init() {
extractors.Register("bitchute", New())
}
type extractor struct{}
// New returns a bitchute extractor.
func New() extractors.Extractor {
return &extractor{}
}
// Extract is the main function to extract the data.
func (e *extractor) Extract(u string, option extractors.Options) ([]*extractors.Data, error) {
regVideoID := regexp.MustCompile(`/video/([^?/]+)`)
matchVideoID := regVideoID.FindStringSubmatch(u)
if len(matchVideoID) < 2 {
return nil, errors.New("Invalid video URL: Missing video ID parameter")
}
embedURL := fmt.Sprintf("https://www.bitchute.com/api/beta9/embed/?videoID=%s", matchVideoID[1])
res, err := request.Request(http.MethodGet, embedURL, nil, nil)
if err != nil {
return nil, errors.WithStack(err)
}
defer res.Body.Close() // nolint
var reader io.ReadCloser
switch res.Header.Get("Content-Encoding") {
case "gzip":
reader, _ = gzip.NewReader(res.Body)
case "deflate":
reader = flate.NewReader(res.Body)
default:
reader = res.Body
}
defer reader.Close() // nolint
b, err := io.ReadAll(reader)
if err != nil {
return nil, errors.WithStack(err)
}
// There is also an API that provides meta data
// POST https://api.bitchute.com/api/beta9/video {"video_id": <video_id>}
html := string(b)
regMediaURL := regexp.MustCompile(`media_url\s*=\s*['|"](https:\/\/[^.]+\.bitchute\.com\/.*\.mp4)`)
matchMediaURL := regMediaURL.FindStringSubmatch(html)
if len(matchMediaURL) < 2 {
return nil, errors.New("Could not extract media URL from page")
}
mediaURL := matchMediaURL[1]
regVideoName := regexp.MustCompile(`(?m)video_name\s*=\s*["|']\\?"?(.*)["|'];?$`)
matchVideoName := regVideoName.FindStringSubmatch(html)
if len(matchVideoName) < 2 {
return nil, errors.New("Could not extract media name from page")
}
videoName := strings.ReplaceAll(matchVideoName[1], `\"`, "")
streams := make(map[string]*extractors.Stream, 1)
size, err := request.Size(mediaURL, u)
if err != nil {
return nil, errors.WithStack(err)
}
streams["Default"] = &extractors.Stream{
Parts: []*extractors.Part{
{
URL: mediaURL,
Size: size,
Ext: "mp4",
},
},
Size: size,
Quality: "Default",
}
return []*extractors.Data{
{
Site: "Bitchute bitchute.com",
Title: videoName,
Type: extractors.DataTypeVideo,
Streams: streams,
URL: u,
},
}, nil
}
================================================
FILE: extractors/bitchute/bitchute_test.go
================================================
package bitchute
import (
"testing"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/test"
)
func TestDownload(t *testing.T) {
tests := []struct {
name string
args test.Args
}{
{
name: "video test 1",
args: test.Args{
URL: "https://www.bitchute.com/video/C17naZ6WlWPo",
Title: "Everybody Dance Now",
Size: 1794720,
},
},
{
name: "video test 2",
args: test.Args{
URL: "https://www.bitchute.com/video/HFgoUz6HrvQd",
Title: "Bear Level 1",
Size: 971698,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := New().Extract(tt.args.URL, extractors.Options{})
test.CheckError(t, err)
test.Check(t, tt.args, data[0])
})
}
}
================================================
FILE: extractors/douyin/douyin.go
================================================
package douyin
import (
"crypto/rand"
_ "embed"
"encoding/json"
"fmt"
"net/http"
netURL "net/url"
"regexp"
"strings"
"github.com/dop251/goja"
"github.com/pkg/errors"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/request"
"github.com/iawia002/lux/utils"
)
func init() {
e := New()
extractors.Register("douyin", e)
extractors.Register("iesdouyin", e)
}
//go:embed sign.js
var script string
type extractor struct{}
// New returns a douyin extractor.
func New() extractors.Extractor {
return &extractor{}
}
// Extract is the main function to extract the data.
func (e *extractor) Extract(url string, option extractors.Options) ([]*extractors.Data, error) {
if strings.Contains(url, "v.douyin.com") {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, errors.WithStack(err)
}
c := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
resp, err := c.Do(req)
if err != nil {
return nil, errors.WithStack(err)
}
defer resp.Body.Close() // nolint
url = resp.Header.Get("location")
}
itemIds := utils.MatchOneOf(url, `/video/(\d+)`)
if len(itemIds) == 0 {
return nil, errors.New("unable to get video ID")
}
if itemIds == nil || len(itemIds) < 2 {
return nil, errors.WithStack(extractors.ErrURLParseFailed)
}
itemId := itemIds[len(itemIds)-1]
// dynamic generate cookie
cookie, err := createCookie()
if err != nil {
return nil, errors.WithStack(err)
}
api := "https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id=" + itemId
// parse api query params string
query, err := netURL.Parse(api)
if err != nil {
return nil, errors.WithStack(extractors.ErrURLQueryParamsParseFailed)
}
// define request headers and sign agent
headers := map[string]string{}
headers["Cookie"] = cookie
headers["Referer"] = "https://www.douyin.com/"
headers["User-Agent"] = "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Mobile Safari/537.36 Edg/87.0.664.66"
// init JavaScripts runtime
vm := goja.New()
// load sign scripts
_, _ = vm.RunString(script)
// sign
sign, err := vm.RunString(fmt.Sprintf("sign('%s', '%s')", query.RawQuery, headers["User-Agent"]))
if err != nil {
return nil, errors.WithStack(err)
}
api = fmt.Sprintf("%s&X-Bogus=%s", api, sign)
jsonData, err := request.Get(api, url, headers)
if err != nil {
return nil, errors.WithStack(err)
}
var douyin douyinData
if err = json.Unmarshal([]byte(jsonData), &douyin); err != nil {
return nil, errors.WithStack(err)
}
urlData := make([]*extractors.Part, 0)
var douyinType extractors.DataType
var totalSize int64
// AwemeType: 0:video 68:image
if douyin.AwemeDetail.AwemeType == 68 {
douyinType = extractors.DataTypeImage
for _, img := range douyin.AwemeDetail.Images {
realURL := img.URLList[len(img.URLList)-1]
size, err := request.Size(realURL, url)
if err != nil {
return nil, errors.WithStack(err)
}
totalSize += size
_, ext, err := utils.GetNameAndExt(realURL)
if err != nil {
return nil, errors.WithStack(err)
}
urlData = append(urlData, &extractors.Part{
URL: realURL,
Size: size,
Ext: ext,
})
}
} else {
douyinType = extractors.DataTypeVideo
realURL := douyin.AwemeDetail.Video.PlayAddr.URLList[0]
totalSize, err = request.Size(realURL, url)
if err != nil {
return nil, errors.WithStack(err)
}
urlData = append(urlData, &extractors.Part{
URL: realURL,
Size: totalSize,
Ext: "mp4",
})
}
streams := map[string]*extractors.Stream{
"default": {
Parts: urlData,
Size: totalSize,
},
}
return []*extractors.Data{
{
Site: "抖音 douyin.com",
Title: douyin.AwemeDetail.Desc,
Type: douyinType,
Streams: streams,
URL: url,
},
}, nil
}
func createCookie() (string, error) {
v1, err := msToken(107)
if err != nil {
return "", err
}
v2, err := ttwid()
if err != nil {
return "", err
}
v3 := "324fb4ea4a89c0c05827e18a1ed9cf9bf8a17f7705fcc793fec935b637867e2a5a9b8168c885554d029919117a18ba69"
v4 := "eyJiZC10aWNrZXQtZ3VhcmQtdmVyc2lvbiI6MiwiYmQtdGlja2V0LWd1YXJkLWNsaWVudC1jc3IiOiItLS0tLUJFR0lOIENFUlRJRklDQVRFIFJFUVVFU1QtLS0tLVxyXG5NSUlCRFRDQnRRSUJBREFuTVFzd0NRWURWUVFHRXdKRFRqRVlNQllHQTFVRUF3d1BZbVJmZEdsamEyVjBYMmQxXHJcbllYSmtNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVKUDZzbjNLRlFBNUROSEcyK2F4bXAwNG5cclxud1hBSTZDU1IyZW1sVUE5QTZ4aGQzbVlPUlI4NVRLZ2tXd1FJSmp3Nyszdnc0Z2NNRG5iOTRoS3MvSjFJc3FBc1xyXG5NQ29HQ1NxR1NJYjNEUUVKRGpFZE1Cc3dHUVlEVlIwUkJCSXdFSUlPZDNkM0xtUnZkWGxwYmk1amIyMHdDZ1lJXHJcbktvWkl6ajBFQXdJRFJ3QXdSQUlnVmJkWTI0c0RYS0c0S2h3WlBmOHpxVDRBU0ROamNUb2FFRi9MQnd2QS8xSUNcclxuSURiVmZCUk1PQVB5cWJkcytld1QwSDZqdDg1czZZTVNVZEo5Z2dmOWlmeTBcclxuLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tXHJcbiJ9"
cookie := fmt.Sprintf("msToken=%s;ttwid=%s;odin_tt=%s;bd_ticket_guard_client_data=%s;", v1, v2, v3, v4)
return cookie, nil
}
func msToken(length int) (string, error) {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
randomBytes := make([]byte, length)
if _, err := rand.Read(randomBytes); err != nil {
return "", err
}
token := make([]byte, length)
for i, b := range randomBytes {
token[i] = characters[int(b)%len(characters)]
}
return string(token), nil
}
func ttwid() (string, error) {
body := map[string]interface{}{
"aid": 1768,
"union": true,
"needFid": false,
"region": "cn",
"cbUrlProtocol": "https",
"service": "www.ixigua.com",
"migrate_info": map[string]string{"ticket": "", "source": "node"},
}
bytes, err := json.Marshal(body)
if err != nil {
return "", err
}
payload := strings.NewReader(string(bytes))
resp, err := request.Request(http.MethodPost, "https://ttwid.bytedance.com/ttwid/union/register/", payload, nil)
if err != nil {
return "", err
}
defer resp.Body.Close() // nolint
cookie := resp.Header.Get("Set-Cookie")
re := regexp.MustCompile(`ttwid=([^;]+)`)
if match := re.FindStringSubmatch(cookie); match != nil {
return match[1], nil
}
return "", errors.New("douyin ttwid request failed")
}
================================================
FILE: extractors/douyin/douyin_test.go
================================================
package douyin
import (
"testing"
"github.com/iawia002/lux/extractors"
"github.com/iawia002/lux/test"
)
func TestDownload(t *testing.T) {
tests := []struct {
name string
args test.Args
}{
{
name: "normal test",
args: test.Args{
URL: "https://www.douyin.com/video/6967223681286278436?previous_page=main_page&tab_name=home",
Title: "是爱情,让父子相认#陈翔六点半 #关于爱情",
},
},
{
name: "image test",
args: test.Args{
URL: "https://v.douyin.com/LvCYKvV",
Title: "黑发限定#开春必备",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
data, err := New().Extract(tt.args.URL, extractors.Options{})
test.CheckError(t, err)
test.Check(t, tt.args, data[0])
})
}
}
================================================
FILE: extractors/douyin/sign.js
================================================
var window = null;
function _0x5cd844(e) {
var b = {
exports: {}
};
return e(b, b.exports), b.exports
}
jsvmp = function(e, b, a) {
function f(e, b, a) {
return (f = function() {
if ("undefined" == typeof Reflect || !Reflect.construct || Reflect.construct.sham) return !1;
if ("function" == typeof Proxy) return !0;
try {
return Date.prototype.toString.call(Reflect.construct(Date, [], function() {})), !0
} catch (e) {
return !1
}
}() ? Reflect.construct : function(e, b, a) {
var f = [null];
f.push.apply(f, b);
var c = new(Function.bind.apply(e, f));
return a && function(e, b) {
(Object.setPrototypeOf || function(e, b) {
return e.__proto__ = b, e
})(e, b)
}(c, a.prototype), c
}).apply(null, arguments)
}
function c(e) {
return function(e) {
if (Array.isArray(e)) {
for (var b = 0, a = new Array(e.length); b < e.length; b++) a[b] = e[b];
return a
}
}(e) || function(e) {
if (Symbol.iterator in Object(e) || "[object Arguments]" === Object.prototype.toString.call(e)) return Array.from(e)
}(e) || function() {
throw new TypeError("Invalid attempt to spread non-iterable instance")
}()
}
for (var r = [], t = 0, d = [], i = 0, n = function(e, b) {
var a = e[b++],
f = e[b],
c = parseInt("" + a + f, 16);
if (c >> 7 == 0) return [1, c];
if (c >> 6 == 2) {
var r = parseInt("" + e[++b] + e[++b], 16);
return c &= 63, [2, r = (c <<= 8) + r]
}
if (c >> 6 == 3) {
var t = parseInt("" + e[++b] + e[++b], 16),
d = parseInt("" + e[++b] + e[++b], 16);
return c &= 63, [3, d = (c <<= 16) + (t <<= 8) + d]
}
}, s = function(e, b) {
var a = parseInt("" + e[b] + e[b + 1], 16);
return a > 127 ? -256 + a : a
}, o = function(e, b) {
var a = parseInt("" + e[b] + e[b + 1] + e[b + 2] + e[b + 3], 16);
return a > 32767 ? -65536 + a : a
}, l = function(e, b) {
var a = parseInt("" + e[b] + e[b + 1] + e[b + 2] + e[b + 3] + e[b + 4] + e[b + 5] + e[b + 6] + e[b + 7], 16);
return a > 2147483647 ? 0 + a : a
}, _ = function(e, b) {
return parseInt("" + e[b] + e[b + 1], 16)
}, x = function(e, b) {
return parseInt("" + e[b] + e[b + 1] + e[b + 2] + e[b + 3], 16)
}, u = u || this || window, h = (e.length, 0), p = "", y = h; y < h + 16; y++) {
var v = "" + e[y++] + e[y];
v = parseInt(v, 16), p += String.fromCharCode(v)
}
if ("HNOJ@?RC" != p) throw new Error("error magic number " + p);
parseInt("" + e[h += 16] + e[h + 1], 16), h += 8, t = 0;
for (var g = 0; g < 4; g++) {
var w = h + 2 * g,
A = parseInt("" + e[w++] + e[w], 16);
t += (3 & A) << 2 * g
}
h += 16;
var C = parseInt("" + e[h += 8] + e[h + 1] + e[h + 2] + e[h + 3] + e[h + 4] + e[h + 5] + e[h + 6] + e[h + 7], 16),
m = C,
S = h += 8,
z = x(e, h += C);
z[1], h += 4, r = {
p: [],
q: []
};
for (var B = 0; B < z; B++) {
for (var R = n(e, h), q = h += 2 * R[0], I = r.p.length, k = 0; k < R[1]; k++) {
var j = n(e, q);
r.p.push(j[1]), q += 2 * j[0]
}
h = q, r.q.push([I, r.p.length])
}
var O = {
5: 1,
6: 1,
70: 1,
22: 1,
23: 1,
37: 1,
73: 1
},
U = {
72: 1
},
D = {
74: 1
},
N = {
11: 1,
12: 1,
24: 1,
26: 1,
27: 1,
31: 1
},
J = {
10: 1
},
L = {
2: 1,
29: 1,
30: 1,
20: 1
},
T = [],
E = [];
function M(e, b, a) {
for (var f = b; f < b + a;) {
var c = _(e, f);
T[f] = c, f += 2, U[c] ? (E[f] = s(e, f), f += 2) : O[c] ? (E[f] = o(e, f), f += 4) : D[c] ? (E[f] = l(e, f), f += 8) : N[c] ? (E[f] = _(e, f), f += 2) : J[c] ? (E[f] = x(e, f), f += 4) : L[c] && (E[f] = x(e, f), f += 4)
}
}
return F(e, S, m / 2, [], b, a);
function P(e, b, a, n, h, p, y, v) {
null == p && (p = this);
var g, w, A, C, m = [],
S = 0;
y && (w = y);
var z, B, R = b,
q = R + 2 * a;
if (!v)
for (; R < q;) {
var I = parseInt("" + e[R] + e[R + 1], 16);
R += 2;
var j = 3 & (z = 13 * I % 241);
if (z >>= 2, j < 1)
if (j = 3 & z, z >>= 2, j < 1) {
if ((j = z) < 1) return [1, m[S--]];
j < 5 ? (w = m[S--], m[S] = m[S] * w) : j < 7 ? (w = m[S--], m[S] = m[S] != w) : j < 14 ? (A = m[S--], C = m[S--], (j = m[S--]).x === P ? j.y >= 1 ? m[++S] = F(e, j.c, j.l, A, j.z, C, null, 1) : (m[++S] = F(e, j.c, j.l, A, j.z, C, null, 0), j.y++) : m[++S] = j.apply(C, A)) : j < 16 && (B = o(e, R), (g = function b() {
var a = arguments;
return b.y > 0 || b.y++, F(e, b.c, b.l, a, b.z, this, null, 0)
}).c = R + 4, g.l = B - 2, g.x = P, g.y = 0, g.z = h, m[S] = g, R += 2 * B - 2)
} else if (j < 2)(j = z) > 8 ? (w = m[S--], m[S] = typeof w) : j > 4 ? m[S -= 1] = m[S][m[S + 1]] : j > 2 && (A = m[S--], (j = m[S]).x === P ? j.y >= 1 ? m[S] = F(e, j.c, j.l, [A], j.z, C, null, 1) : (m[S] = F(e, j.c, j.l, [A], j.z, C, null, 0), j.y++) : m[S] = j(A));
else if (j < 3) {
if ((j = z) < 9) {
for (w = m[S--], B = x(e, R), j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);
R += 4, m[S--][j] = w
} else if (j < 13) throw m[S--]
} else(j = z) < 1 ? m[++S] = null : j < 3 ? (w = m[S--], m[S] = m[S] >= w) : j < 12 && (m[++S] = void 0);
else if (j < 2)
if (j = 3 & z, z >>= 2, j < 1)
if ((j = z) < 5) {
B = o(e, R);
try {
if (d[i][2] = 1, 1 == (w = P(e, R + 4, B - 3, [], h, p, null, 0))[0]) return w
} catch (b) {
if (d[i] && d[i][1] && 1 == (w = P(e, d[i][1][0], d[i][1][1], [], h, p, b, 0))[0]) return w
} finally {
if (d[i] && d[i][0] && 1 == (w = P(e, d[i][0][0], d[i][0][1], [], h, p, null, 0))[0]) return w;
d[i] = 0, i--
}
R += 2 * B - 2
} else j < 7 ? (B = _(e, R), R += 2, m[S -= B] = 0 === B ? new m[S] : f(m[S], c(m.slice(S + 1, S + B + 1)))) : j < 9 && (w = m[S--], m[S] = m[S] & w);
else if (j < 2)
if ((j = z) > 12) m[++S] = s(e, R), R += 2;
else if (j > 10) w = m[S--], m[S] = m[S] << w;
else if (j > 8) {
for (B = x(e, R), j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);
R += 4, m[S] = m[S][j]
} else j > 6 && (A = m[S--], w = delete m[S--][A]);
else if (j < 3)(j = z) < 2 ? m[++S] = w : j < 11 ? (w = m[S -= 2][m[S + 1]] = m[S + 2], S--) : j < 13 && (w = m[S], m[++S] = w);
else if ((j = z) > 12) m[++S] = p;
else if (j > 5) w = m[S--], m[S] = m[S] !== w;
else if (j > 3) w = m[S--], m[S] = m[S] / w;
else if (j > 1) {
if ((B = o(e, R)) < 0) {
v = 1, M(e, b, 2 * a), R += 2 * B - 2;
break
}
R += 2 * B - 2
} else j > -1 && (m[S] = !m[S]);
else if (j < 3)
if (j = 3 & z, z >>= 2, j < 1)(j = z) > 13 ? (m[++S] = o(e, R), R += 4) : j > 11 ? (w = m[S--], m[S] = m[S] >> w) : j > 9 ? (B = _(e, R), R += 2, w = m[S--], h[B] = w) : j > 7 ? (B = x(e, R), R += 4, A = S + 1, m[S -= B - 1] = B ? m.slice(S, A) : []) : j > 0 && (w = m[S--], m[S] = m[S] > w);
else if (j < 2)(j = z) > 12 ? (w = m[S - 1], A = m[S], m[++S] = w, m[++S] = A) : j > 3 ? (w = m[S--], m[S] = m[S] == w) : j > 1 ? (w = m[S--], m[S] = m[S] + w) : j > -1 && (m[++S] = u);
else if (j < 3) {
if ((j = z) > 13) m[++S] = !1;
else if (j > 6) w = m[S--], m[S] = m[S] instanceof w;
else if (j > 4) w = m[S--], m[S] = m[S] % w;
else if (j > 2)
if (m[S--]) R += 4;
else {
if ((B = o(e, R)) < 0) {
v = 1, M(e, b, 2 * a), R += 2 * B - 2;
break
}
R += 2 * B - 2
}
else if (j > 0) {
for (B = x(e, R), w = "", k = r.q[B][0]; k < r.q[B][1]; k++) w += String.fromCharCode(t ^ r.p[k]);
m[++S] = w, R += 4
}
} else(j = z) > 7 ? (w = m[S--], m[S] = m[S] | w) : j > 5 ? (B = _(e, R), R += 2, m[++S] = h["$" + B]) : j > 3 && (B = o(e, R), d[i][0] && !d[i][2] ? d[i][1] = [R + 4, B - 3] : d[i++] = [0, [R + 4, B - 3], 0], R += 2 * B - 2);
else if (j = 3 & z, z >>= 2, j > 2)(j = z) > 13 ? (m[++S] = l(e, R), R += 8) : j > 11 ? (w = m[S--], m[S] = m[S] >>> w) : j > 9 ? m[++S] = !0 : j > 7 ? (B = _(e, R), R += 2, m[S] = m[S][B]) : j > 0 && (w = m[S--], m[S] = m[S] < w);
else if (j > 1)(j = z) > 10 ? (B = o(e, R), d[++i] = [
[R + 4, B - 3], 0, 0
], R += 2 * B - 2) : j > 8 ? (w = m[S--], m[S] = m[S] ^ w) : j > 6 && (w = m[S--]);
else if (j > 0) {
if ((j = z) > 7) w = m[S--], m[S] = m[S] in w;
else if (j > 5) m[S] = ++m[S];
else if (j > 3) B = _(e, R), R += 2, w = h[B], m[++S] = w;
else if (j > 1) {
var O = 0,
U = m[S].length,
D = m[S];
m[++S] = function() {
var e = O < U;
if (e) {
var b = D[O++];
m[++S] = b
}
m[++S] = e
}
}
} else if ((j = z) > 13) w = m[S], m[S] = m[S - 1], m[S - 1] = w;
else if (j > 4) w = m[S--], m[S] = m[S] === w;
else if (j > 2) w = m[S--], m[S] = m[S] - w;
else if (j > 0) {
for (B = x(e, R), j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);
j = +j, R += 4, m[++S] = j
}
}
if (v)
for (; R < q;)
if (I = T[R], R += 2, j = 3 & (z = 13 * I % 241), z >>= 2, j > 2)
if (j = 3 & z, z >>= 2, j > 2)(j = z) < 2 ? (w = m[S--], m[S] = m[S] < w) : j < 9 ? (B = E[R], R += 2, m[S] = m[S][B]) : j < 11 ? m[++S] = !0 : j < 13 ? (w = m[S--], m[S] = m[S] >>> w) : j < 15 && (m[++S] = E[R], R += 8);
else if (j > 1)(j = z) < 6 || (j < 8 ? w = m[S--] : j < 10 ? (w = m[S--], m[S] = m[S] ^ w) : j < 12 && (B = E[R], d[++i] = [
[R + 4, B - 3], 0, 0
], R += 2 * B - 2));
else if (j > 0)(j = z) > 7 ? (w = m[S--], m[S] = m[S] in w) : j > 5 ? m[S] = ++m[S] : j > 3 ? (B = E[R], R += 2, w = h[B], m[++S] = w) : j > 1 && (O = 0, U = m[S].length, D = m[S], m[++S] = function() {
var e = O < U;
if (e) {
var b = D[O++];
m[++S] = b
}
m[++S] = e
});
else if ((j = z) < 2) {
for (B = E[R], j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);
j = +j, R += 4, m[++S] = j
} else j < 4 ? (w = m[S--], m[S] = m[S] - w) : j < 6 ? (w = m[S--], m[S] = m[S] === w) : j < 15 && (w = m[S], m[S] = m[S - 1], m[S - 1] = w);
else if (j > 1)
if (j = 3 & z, z >>= 2, j < 1)(j = z) > 13 ? (m[++S] = E[R], R += 4) : j > 11 ? (w = m[S--], m[S] = m[S] >> w) : j > 9 ? (B = E[R], R += 2, w = m[S--], h[B] = w) : j > 7 ? (B = E[R], R += 4, A = S + 1, m[S -= B - 1] = B ? m.slice(S, A) : []) : j > 0 && (w = m[S--], m[S] = m[S] > w);
else if (j < 2)(j = z) < 1 ? m[++S] = u : j < 3 ? (w = m[S--], m[S] = m[S] + w) : j < 5 ? (w = m[S--], m[S] = m[S] == w) : j < 14 && (w = m[S - 1], A = m[S], m[++S] = w, m[++S] = A);
else if (j < 3) {
if ((j = z) > 13) m[++S] = !1;
else if (j > 6) w = m[S--], m[S] = m[S] instanceof w;
else if (j > 4) w = m[S--], m[S] = m[S] % w;
else if (j > 2) m[S--] ? R += 4 : R += 2 * (B = E[R]) - 2;
else if (j > 0) {
for (B = E[R], w = "", k = r.q[B][0]; k < r.q[B][1]; k++) w += String.fromCharCode(t ^ r.p[k]);
m[++S] = w, R += 4
}
} else(j = z) > 7 ? (w = m[S--], m[S] = m[S] | w) : j > 5 ? (B = E[R], R += 2, m[++S] = h["$" + B]) : j > 3 && (B = E[R], d[i][0] && !d[i][2] ? d[i][1] = [R + 4, B - 3] : d[i++] = [0, [R + 4, B - 3], 0], R += 2 * B - 2);
else if (j > 0)
if (j = 3 & z, z >>= 2, j < 1) {
if ((j = z) > 9);
else if (j > 7) w = m[S--], m[S] = m[S] & w;
else if (j > 5) B = E[R], R += 2, m[S -= B] = 0 === B ? new m[S] : f(m[S], c(m.slice(S + 1, S + B + 1)));
else if (j > 3) {
B = E[R];
try {
if (d[i][2] = 1, 1 == (w = P(e, R + 4, B - 3, [], h, p, null, 0))[0]) return w
} catch (b) {
if (d[i] && d[i][1] && 1 == (w = P(e, d[i][1][0], d[i][1][1], [], h, p, b, 0))[0]) return w
} finally {
if (d[i] && d[i][0] && 1 == (w = P(e, d[i][0][0], d[i][0][1], [], h, p, null, 0))[0]) return w;
d[i] = 0, i--
}
R += 2 * B - 2
}
} else if (j < 2)
if ((j = z) < 8) A = m[S--], w = delete m[S--][A];
else if (j < 10) {
for (B = E[R], j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);
R += 4, m[S] = m[S][j]
} else j < 12 ? (w = m[S--], m[S] = m[S] << w) : j < 14 && (m[++S] = E[R], R += 2);
else j < 3 ? (j = z) < 2 ? m[++S] = w : j < 11 ? (w = m[S -= 2][m[S + 1]] = m[S + 2], S--) : j < 13 && (w = m[S], m[++S] = w) : (j = z) > 12 ? m[++S] = p : j > 5 ? (w = m[S--], m[S] = m[S] !== w) : j > 3 ? (w = m[S--], m[S] = m[S] / w) : j > 1 ? R += 2 * (B = E[R]) - 2 : j > -1 && (m[S] = !m[S]);
else if (j = 3 & z, z >>= 2, j < 1) {
if ((j = z) < 1) return [1, m[S--]];
j < 5 ? (w = m[S--], m[S] = m[S] * w) : j < 7 ? (w = m[S--], m[S] = m[S] != w) : j < 14 ? (A = m[S--], C = m[S--], (j = m[S--]).x === P ? j.y >= 1 ? m[++S] = F(e, j.c, j.l, A, j.z, C, null, 1) : (m[++S] = F(e, j.c, j.l, A, j.z, C, null, 0), j.y++) : m[++S] = j.apply(C, A)) : j < 16 && (B = E[R], (g = function b() {
var a = arguments;
return b.y > 0 || b.y++, F(e, b.c, b.l, a, b.z, this, null, 0)
}).c = R + 4, g.l = B - 2, g.x = P, g.y = 0, g.z = h, m[S] = g, R += 2 * B - 2)
} else if (j < 2)(j = z) > 8 ? (w = m[S--], m[S] = typeof w) : j > 4 ? m[S -= 1] = m[S][m[S + 1]] : j > 2 && (A = m[S--], (j = m[S]).x === P ? j.y >= 1 ? m[S] = F(e, j.c, j.l, [A], j.z, C, null, 1) : (m[S] = F(e, j.c, j.l, [A], j.z, C, null, 0), j.y++) : m[S] = j(A));
else if (j < 3) {
if ((j = z) < 9) {
for (w = m[S--], B = E[R], j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]);
R += 4, m[S--][j] = w
} else if (j < 13) throw m[S--]
} else(j = z) < 1 ? m[++S] = null : j < 3 ? (w = m[S--], m[S] = m[S] >= w) : j < 12 && (m[++S] = void 0);
return [0, null]
}
function F(e, b, a, f, c, r, t, d) {
null == r && (r = this), c && !c.d && (c.d = 0, c.$0 = c, c[1] = {});
var i, n, s = {},
o = s.d = c ? c.d + 1 : 0;
for (s["$" + o] = s, n = 0; n < o; n++) s[i = "$" + n] = c[i];
for (n = 0, o = s.length = f.length; n < o; n++) s[n] = f[n];
return d && !T[b] && M(e, b, 2 * a), T[b] ? P(e, b, a, 0, s, r, null, 1)[1] : P(e, b, a, 0, s, r, null, 0)[1]
}
};
var _0x397dc7 = "undefined" != typeof globalThis ? globalThis : void 0 !== window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : {},
_0x124d1a = _0x5cd844(function(_0x770f81) {
! function() {
var _0x250d36 = "input is invalid type",
_0x4cfaee = !1,
_0x1702f9 = {},
_0x5ccbb3 = !_0x4cfaee && "object" == typeof self,
_0x54d876 = !_0x1702f9.JS_MD5_NO_NODE_JS && "object" == typeof process && process.versions && process.versions.node,
_0x185caf;
_0x54d876 ? _0x1702f9 = _0x397dc7 : _0x5ccbb3 && (_0x1702f9 = self);
var _0x17dcbf = !_0x1702f9.JS_MD5_NO_COMMON_JS && _0x770f81.exports,
_0x554fed = !1,
_0x2de28f = !_0x1702f9.JS_MD5_NO_ARRAY_BUFFER && "undefined" != typeof ArrayBuffer,
_0x3a9a1b = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"],
_0x465562 = [128, 32768, 8388608, -2147483648],
_0x20b37e = [0, 8, 16, 24],
_0x323604 = ["hex", "array", "digest", "buffer", "arrayBuffer", "base64"],
_0x2c185e = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"],
_0x4b59e0 = [];
if (_0x2de28f) {
var _0x395837 = new ArrayBuffer(68);
_0x185caf = new Uint8Array(_0x395837), _0x4b59e0 = new Uint32Array(_0x395837)
}!_0x1702f9.JS_MD5_NO_NODE_JS && Array.isArray || (Array.isArray = function(e) {
return "[object Array]" === Object.prototype.toString.call(e)
}), _0x2de28f && (_0x1702f9.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView) && (ArrayBuffer.isView = function(e) {
return "object" == typeof e && e.buffer && e.buffer.constructor === ArrayBuffer
});
var _0x4e9930 = function(e) {
return function(b) {
return new _0x5887c8(!0).update(b)[e]()
}
},
_0x38ba77 = function() {
var e = _0x4e9930("hex");
_0x54d876 && (e = _0x474989(e)), e.create = function() {
return new _0x5887c8
}, e.update = function(b) {
return e.create().update(b)
};
for (var b = 0; b < _0x323604.length; ++b) {
var a = _0x323604[b];
e[a] = _0x4e9930(a)
}
return e
},
_0x474989 = function(_0x57eeaa) {
var _0x114910, _0x226465 = eval("require('crypto');"),
_0x1f6ae0 = eval("require('buffer')['Buffer'];");
return function(e) {
if ("string" == typeof e) return _0x226465.createHash("md5").update(e, "utf8").digest("hex");
if (null == e) throw _0x250d36;
return e.constructor === ArrayBuffer && (e = new Uint8Array(e)), Array.isArray(e) || ArrayBuffer.isView(e) || e.constructor === _0x1f6ae0 ? _0x226465.createHash("md5").update(new _0x1f6ae0.from(e)).digest("hex") : _0x57eeaa(e)
}
};
function _0x5887c8(e) {
if (e) _0x4b59e0[0] = _0x4b59e0[16] = _0x4b59e0[1] = _0x4b59e0[2] = _0x4b59e0[3] = _0x4b59e0[4] = _0x4b59e0[5] = _0x4b59e0[6] = _0x4b59e0[7] = _0x4b59e0[8] = _0x4b59e0[9] = _0x4b59e0[10] = _0x4b59e0[11] = _0x4b59e0[12] = _0x4b59e0[13] = _0x4b59e0[14] = _0x4b59e0[15] = 0, this.blocks = _0x4b59e0, this.buffer8 = _0x185caf;
else if (_0x2de28f) {
var b = new ArrayBuffer(68);
this.buffer8 = new Uint8Array(b), this.blocks = new Uint32Array(b)
} else this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0, this.finalized = this.hashed = !1, this.first = !0
}
_0x5887c8.prototype.update = function(e) {
if (!this.finalized) {
var b, a = typeof e;
if ("string" !== a) {
if ("object" !== a || null === e) throw _0x250d36;
if (_0x2de28f && e.constructor === ArrayBuffer) e = new Uint8Array(e);
else if (!(Array.isArray(e) || _0x2de28f && ArrayBuffer.isView(e))) throw _0x250d36;
b = !0
}
for (var f, c, r = 0, t = e.length, d = this.blocks, i = this.buffer8; r < t;) {
if (this.hashed && (this.hashed = !1, d[0] = d[16], d[16] = d[1] = d[2] = d[3] = d[4] = d[5] = d[6] = d[7] = d[8] = d[9] = d[10] = d[11] = d[12] = d[13] = d[14] = d[15] = 0), b)
if (_0x2de28f)
for (c = this.start; r < t && c < 64; ++r) i[c++] = e[r];
else
for (c = this.start; r < t && c < 64; ++r) d[c >> 2] |= e[r] << _0x20b37e[3 & c++];
else if (_0x2de28f)
for (c = this.start; r < t && c < 64; ++r)(f = e.charCodeAt(r)) < 128 ? i[c++] = f : f < 2048 ? (i[c++] = 192 | f >> 6, i[c++] = 128 | 63 & f) : f < 55296 || f >= 57344 ? (i[c++] = 224 | f >> 12, i[c++] = 128 | f >> 6 & 63, i[c++] = 128 | 63 & f) : (f = 65536 + ((1023 & f) << 10 | 1023 & e.charCodeAt(++r)), i[c++] = 240 | f >> 18, i[c++] = 128 | f >> 12 & 63, i[c++] = 128 | f >> 6 & 63, i[c++] = 128 | 63 & f);
else
for (c = this.start; r < t && c < 64; ++r)(f = e.charCodeAt(r)) < 128 ? d[c >> 2] |= f << _0x20b37e[3 & c++] : f < 2048 ? (d[c >> 2] |= (192 | f >> 6) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]) : f < 55296 || f >= 57344 ? (d[c >> 2] |= (224 | f >> 12) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 6 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]) : (f = 65536 + ((1023 & f) << 10 | 1023 & e.charCodeAt(++r)), d[c >> 2] |= (240 | f >> 18) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 12 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 6 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]);
this.lastByteIndex = c, this.bytes += c - this.start, c >= 64 ? (this.start = c - 64, this.hash(), this.hashed = !0) : this.start = c
}
return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0, this.bytes = this.bytes % 4294967296), this
}
}, _0x5887c8.prototype.finalize = function() {
if (!this.finalized) {
this.finalized = !0;
var e = this.blocks,
b = this.lastByteIndex;
e[b >> 2] |= _0x465562[3 & b], b >= 56 && (this.hashed || this.hash(), e[0] = e[16], e[16] = e[1] = e[2] = e[3] = e[4] = e[5] = e[6] = e[7] = e[8] = e[9] = e[10] = e[11] = e[12] = e[13] = e[14] = e[15] = 0), e[14] = this.bytes << 3, e[15] = this.hBytes << 3 | this.bytes >>> 29, this.hash()
}
}, _0x5887c8.prototype.hash = function() {
var e, b, a, f, c, r, t = this.blocks;
this.first ? b = ((b = ((e = ((e = t[0] - 680876937) << 7 | e >>> 25) - 271733879 << 0) ^ (a = ((a = (-271733879 ^ (f = ((f = (-1732584194 ^ 2004318071 & e) + t[1] - 117830708) << 12 | f >>> 20) + e << 0) & (-271733879 ^ e)) + t[2] - 1126478375) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[3] - 1316259209) << 22 | b >>> 10) + a << 0 : (e = this.h0, b = this.h1, a = this.h2, b = ((b += ((e = ((e += ((f = this.h3) ^ b & (a ^ f)) + t[0] - 680876936) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[1] - 389564586) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[2] + 606105819) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[3] - 1044525330) << 22 | b >>> 10) + a << 0), b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[4] - 176418897) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[5] + 1200080426) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[6] - 1473231341) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[7] - 45705983) << 22 | b >>> 10) + a << 0, b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[8] + 1770035416) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[9] - 1958414417) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[10] - 42063) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[11] - 1990404162) << 22 | b >>> 10) + a << 0, b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[12] + 1804603682) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[13] - 40341101) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[14] - 1502002290) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[15] + 1236535329) << 22 | b >>> 10) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[1] - 165796510) << 5 | e >>> 27) + b << 0) ^ b)) + t[6] - 1069501632) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[11] + 643717713) << 14 | a >>> 18) + f << 0) ^ f)) + t[0] - 373897302) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[5] - 701558691) << 5 | e >>> 27) + b << 0) ^ b)) + t[10] + 38016083) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[15] - 660478335) << 14 | a >>> 18) + f << 0) ^ f)) + t[4] - 405537848) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[9] + 568446438) << 5 | e >>> 27) + b << 0) ^ b)) + t[14] - 1019803690) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[3] - 187363961) << 14 | a >>> 18) + f << 0) ^ f)) + t[8] + 1163531501) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[13] - 1444681467) << 5 | e >>> 27) + b << 0) ^ b)) + t[2] - 51403784) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[7] + 1735328473) << 14 | a >>> 18) + f << 0) ^ f)) + t[12] - 1926607734) << 20 | b >>> 12) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[5] - 378558) << 4 | e >>> 28) + b << 0)) + t[8] - 2022574463) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[11] + 1839030562) << 16 | a >>> 16) + f << 0)) + t[14] - 35309556) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[1] - 1530992060) << 4 | e >>> 28) + b << 0)) + t[4] + 1272893353) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[7] - 155497632) << 16 | a >>> 16) + f << 0)) + t[10] - 1094730640) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[13] + 681279174) << 4 | e >>> 28) + b << 0)) + t[0] - 358537222) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[3] - 722521979) << 16 | a >>> 16) + f << 0)) + t[6] + 76029189) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[9] - 640364487) << 4 | e >>> 28) + b << 0)) + t[12] - 421815835) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[15] + 530742520) << 16 | a >>> 16) + f << 0)) + t[2] - 995338651) << 23 | b >>> 9) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[0] - 198630844) << 6 | e >>> 26) + b << 0) | ~a)) + t[7] + 1126891415) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[14] - 1416354905) << 15 | a >>> 17) + f << 0) | ~e)) + t[5] - 57434055) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[12] + 1700485571) << 6 | e >>> 26) + b << 0) | ~a)) + t[3] - 1894986606) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[10] - 1051523) << 15 | a >>> 17) + f << 0) | ~e)) + t[1] - 2054922799) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[8] + 1873313359) << 6 | e >>> 26) + b << 0) | ~a)) + t[15] - 30611744) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[6] - 1560198380) << 15 | a >>> 17) + f << 0) | ~e)) + t[13] + 1309151649) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[4] - 145523070) << 6 | e >>> 26) + b << 0) | ~a)) + t[11] - 1120210379) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[2] + 718787259) << 15 | a >>> 17) + f << 0) | ~e)) + t[9] - 343485551) << 21 | b >>> 11) + a << 0, this.first ? (this.h0 = e + 1732584193 << 0, this.h1 = b - 271733879 << 0, this.h2 = a - 1732584194 << 0, this.h3 = f + 271733878 << 0, this.first = !1) : (this.h0 = this.h0 + e << 0, this.h1 = this.h1 + b << 0, this.h2 = this.h2 + a << 0, this.h3 = this.h3 + f << 0)
}, _0x5887c8.prototype.hex = function() {
this.finalize();
var e = this.h0,
b = this.h1,
a = this.h2,
f = this.h3;
return _0x3a9a1b[e >> 4 & 15] + _0x3a9a1b[15 & e] + _0x3a9a1b[e >> 12 & 15] + _0x3a9a1b[e >> 8 & 15] + _0x3a9a1b[e >> 20 & 15] + _0x3a9a1b[e >> 16 & 15] + _0x3a9a1b[e >> 28 & 15] + _0x3a9a1b[e >> 24 & 15] + _0x3a9a1b[b >> 4 & 15] + _0x3a9a1b[15 & b] + _0x3a9a1b[b >> 12 & 15] + _0x3a9a1b[b >> 8 & 15] + _0x3a9a1b[b >> 20 & 15] + _0x3a9a1b[b >> 16 & 15] + _0x3a9a1b[b >> 28 & 15] + _0x3a9a1b[b >> 24 & 15] + _0x3a9a1b[a >> 4 & 15] + _0x3a9a1b[15 & a] + _0x3a9a1b[a >> 12 & 15] + _0x3a9a1b[a >> 8 & 15] + _0x3a9a1b[a >> 20 & 15] + _0x3a9a1b[a >> 16 & 15] + _0x3a9a1b[a >> 28 & 15] + _0x3a9a1b[a >> 24 & 15] + _0x3a9a1b[f >> 4 & 15] + _0x3a9a1b[15 & f] + _0x3a9a1b[f >> 12 & 15] + _0x3a9a1b[f >> 8 & 15] + _0x3a9a1b[f >> 20 & 15] + _0x3a9a1b[f >> 16 & 15] + _0x3a9a1b[f >> 28 & 15] + _0x3a9a1b[f >> 24 & 15]
}, _0x5887c8.prototype.toString = _0x5887c8.prototype.hex, _0x5887c8.prototype.digest = function() {
this.finalize();
var e = this.h0,
b = this.h1,
a = this.h2,
f = this.h3;
return [255 & e, e >> 8 & 255, e >> 16 & 255, e >> 24 & 255, 255 & b, b >> 8 & 255, b >> 16 & 255, b >> 24 & 255, 255 & a, a >> 8 & 255, a >> 16 & 255, a >> 24 & 255, 255 & f, f >> 8 & 255, f >> 16 & 255, f >> 24 & 255]
}, _0x5887c8.prototype.array = _0x5887c8.prototype.digest, _0x5887c8.prototype.arrayBuffer = function() {
this.finalize();
var e = new ArrayBuffer(16),
b = new Uint32Array(e);
return b[0] = this.h0, b[1] = this.h1, b[2] = this.h2, b[3] = this.h3, e
}, _0x5887c8.prototype.buffer = _0x5887c8.prototype.arrayBuffer, _0x5887c8.prototype.base64 = function() {
for (var e, b, a, f = "", c = this.array(), r = 0; r < 15;) e = c[r++], b = c[r++], a = c[r++], f += _0x2c185e[e >>> 2] + _0x2c185e[63 & (e << 4 | b >>> 4)] + _0x2c185e[63 & (b << 2 | a >>> 6)] + _0x2c185e[63 & a];
return f + (_0x2c185e[(e = c[r]) >>> 2] + _0x2c185e[e << 4 & 63] + "==")
};
var _0x4dd781 = _0x38ba77();
_0x17dcbf ? _0x770f81.exports = _0x4dd781 : (_0x1702f9.md5 = _0x4dd781, _0x554fed && (void 0)(function() {
return _0x4dd781
}))
}()
});
function _0x178cef(e) {
return jsvmp("484e4f4a403f52430038001eab0015840e8ee21a00000000000000621b000200001d000146000306000e271f001b000200021d00010500121b001b000b021b000b04041d0001071b000b0500000003000126207575757575757575757575757575757575757575757575757575757575757575", [, , void 0 !== _0x124d1a ? _0x124d1a : void 0, _0x178cef, e])
}
for (var _0xb55f3e = {
boe: !1,
aid: 0,
dfp: !1,
sdi: !1,
enablePathList: [],
_enablePathListRegex: [],
urlRewriteRules: [],
_urlRewriteRules: [],
initialized: !1,
enableTrack: !1,
track: {
unitTime: 0,
unitAmount: 0,
fre: 0
},
triggerUnload: !1,
region: "",
regionConf: {},
umode: 0,
v: !1,
perf: !1,
xxbg: !0
}, _0x3eaf64 = {
debug: function(e, b) {
let a = !1;
a = !1
}
}, _0x233455 = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"], _0x2e9f6d = [], _0x511f86 = [], _0x3d35de = 0; _0x3d35de < 256; _0x3d35de++) _0x2e9f6d[_0x3d35de] = _0x233455[_0x3d35de >> 4 & 15] + _0x233455[15 & _0x3d35de], _0x3d35de < 16 && (_0x3d35de < 10 ? _0x511f86[48 + _0x3d35de] = _0x3d35de : _0x511f86[87 + _0x3d35de] = _0x3d35de);
var _0x2ce54d = function(e) {
for (var b = e.length, a = "", f = 0; f < b;) a += _0x2e9f6d[e[f++]];
return a
},
_0x5960a2 = function(e) {
for (var b = e.length >> 1, a = b << 1, f = new Uint8Array(b), c = 0, r = 0; r < a;) f[c++] = _0x511f86[e.charCodeAt(r++)] << 4 | _0x511f86[e.charCodeAt(r++)];
return f
},
_0x4e46b6 = {
encode: _0x2ce54d,
decode: _0x5960a2
};
function sign(e, b) {
return jsvmp("484e4f4a403f5243001f240fbf2031ccf317480300000000000007181b0002012e1d00921b000b171b000b02402217000a1c1b000b1726402217000c1c1b000b170200004017002646000306000e271f001b000200021d00920500121b001b000b031b000b17041d0092071b000b041e012f17000d1b000b05260a0000101c1b000b06260a0000101c1b001b000b071e01301d00931b001b000b081e00081d00941b0048021d00951b001b000b1b1d00961b0048401d009e1b001b000b031b000b16041d009f1b001b000b09221e0131241b000b031b000b09221e0131241b000b1e0a000110040a0001101d00d51b001b000b09221e0131241b000b031b000b09221e0131241b000b180a000110040a0001101d00d71b001b000b0a1e00101d00d91b001b000b0b261b0
gitextract_1wxrt7hr/
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── can-not-download-video.md
│ │ └── support-a-new-website.md
│ └── workflows/
│ ├── builder.yml
│ ├── ci.yml
│ ├── goreleaser.yml
│ ├── stream_acfun.yml
│ ├── stream_bcy.yml
│ ├── stream_bilibili.yml
│ ├── stream_bitchute.yml
│ ├── stream_douyin.yml
│ ├── stream_douyu.yml
│ ├── stream_eporner.yml
│ ├── stream_facebook.yml
│ ├── stream_geekbang.yml
│ ├── stream_haokan.yml
│ ├── stream_hupu.yml
│ ├── stream_huya.yml
│ ├── stream_instagram.yml
│ ├── stream_iqiyi.yml
│ ├── stream_ixigua.yml
│ ├── stream_kuaishou.yml
│ ├── stream_mgtv.yml
│ ├── stream_miaopai.yml
│ ├── stream_netease.yml
│ ├── stream_odysee.yml
│ ├── stream_pinterest.yml
│ ├── stream_pixivision.yml
│ ├── stream_pornhub.yml
│ ├── stream_qq.yml
│ ├── stream_reddit.yml
│ ├── stream_rumble.yml
│ ├── stream_streamtape.yml
│ ├── stream_tangdou.yml
│ ├── stream_threads.yml
│ ├── stream_tiktok.yml
│ ├── stream_tumblr.yml
│ ├── stream_twitter.yml
│ ├── stream_udn.yml
│ ├── stream_vimeo.yml
│ ├── stream_vk.yml
│ ├── stream_weibo.yml
│ ├── stream_xiaohongshu.yml
│ ├── stream_ximalaya.yml
│ ├── stream_xinpianchang.yml
│ ├── stream_xvideos.yml
│ ├── stream_yinyuetai.yml
│ ├── stream_youku.yml
│ ├── stream_youtube.yml
│ ├── stream_zhihu.yml
│ └── stream_zingmp3.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── CONTRIBUTING.md
├── Cask.toml
├── LICENSE
├── README.md
├── app/
│ ├── app.go
│ └── register.go
├── codecov.yml
├── config/
│ └── config.go
├── downloader/
│ ├── downloader.go
│ ├── downloader_test.go
│ ├── types.go
│ └── utils.go
├── extractors/
│ ├── acfun/
│ │ ├── acfun.go
│ │ ├── acfun_test.go
│ │ └── types.go
│ ├── bcy/
│ │ ├── bcy.go
│ │ └── bcy_test.go
│ ├── bilibili/
│ │ ├── bilibili.go
│ │ ├── bilibili_test.go
│ │ └── types.go
│ ├── bitchute/
│ │ ├── bitchute.go
│ │ └── bitchute_test.go
│ ├── douyin/
│ │ ├── douyin.go
│ │ ├── douyin_test.go
│ │ ├── sign.js
│ │ └── types.go
│ ├── douyu/
│ │ ├── douyu.go
│ │ └── douyu_test.go
│ ├── eporner/
│ │ ├── eporner.go
│ │ └── eporner_test.go
│ ├── errors.go
│ ├── extractors.go
│ ├── facebook/
│ │ ├── facebook.go
│ │ └── facebook_test.go
│ ├── geekbang/
│ │ ├── geekbang.go
│ │ └── geekbang_test.go
│ ├── haokan/
│ │ ├── haokan.go
│ │ └── haokan_test.go
│ ├── hupu/
│ │ ├── hupu.go
│ │ └── hupu_test.go
│ ├── huya/
│ │ ├── huya.go
│ │ └── huya_test.go
│ ├── instagram/
│ │ ├── instagram.go
│ │ └── instagram_test.go
│ ├── iqiyi/
│ │ ├── iqiyi.go
│ │ └── iqiyi_test.go
│ ├── ixigua/
│ │ ├── ixigua.go
│ │ ├── ixigua_test.go
│ │ └── types.go
│ ├── kuaishou/
│ │ ├── kuaishou.go
│ │ └── kuaishou_test.go
│ ├── mgtv/
│ │ ├── mgtv.go
│ │ └── mgtv_test.go
│ ├── miaopai/
│ │ ├── miaopai.go
│ │ └── miaopai_test.go
│ ├── netease/
│ │ ├── netease.go
│ │ └── netease_test.go
│ ├── odysee/
│ │ ├── odysee.go
│ │ └── odysee_test.go
│ ├── pinterest/
│ │ ├── pinterest.go
│ │ └── pinterest_test.go
│ ├── pixivision/
│ │ ├── pixivision.go
│ │ └── pixivision_test.go
│ ├── pornhub/
│ │ ├── pornhub.go
│ │ └── pornhub_test.go
│ ├── qq/
│ │ ├── qq.go
│ │ └── qq_test.go
│ ├── reddit/
│ │ ├── reddit.go
│ │ └── reddit_test.go
│ ├── rumble/
│ │ ├── rumble.go
│ │ └── rumble_test.go
│ ├── streamtape/
│ │ ├── streamtape.go
│ │ └── streamtape_test.go
│ ├── tangdou/
│ │ ├── tangdou.go
│ │ └── tangdou_test.go
│ ├── threads/
│ │ ├── threads.go
│ │ └── threads_test.go
│ ├── tiktok/
│ │ ├── tiktok.go
│ │ └── tiktok_test.go
│ ├── tumblr/
│ │ ├── tumblr.go
│ │ └── tumblr_test.go
│ ├── twitter/
│ │ ├── twitter.go
│ │ └── twitter_test.go
│ ├── types.go
│ ├── udn/
│ │ ├── udn.go
│ │ └── udn_test.go
│ ├── universal/
│ │ ├── universal.go
│ │ └── universal_test.go
│ ├── vimeo/
│ │ ├── vimeo.go
│ │ └── vimeo_test.go
│ ├── vk/
│ │ ├── vk.go
│ │ └── vk_test.go
│ ├── weibo/
│ │ ├── weibo.go
│ │ └── weibo_test.go
│ ├── xiaohongshu/
│ │ ├── xiaohongshu.go
│ │ └── xiaohongshu_test.go
│ ├── ximalaya/
│ │ ├── types.go
│ │ ├── ximalaya.go
│ │ └── ximalaya_test.go
│ ├── xinpianchang/
│ │ ├── xinpianchang.go
│ │ └── xinpianchang_test.go
│ ├── xvideos/
│ │ ├── xvideos.go
│ │ └── xvideos_test.go
│ ├── yinyuetai/
│ │ ├── types.go
│ │ ├── yinyuetai.go
│ │ └── yinyuetai_test.go
│ ├── youku/
│ │ ├── youku.go
│ │ └── youku_test.go
│ ├── youtube/
│ │ ├── youtube.go
│ │ └── youtube_test.go
│ ├── zhihu/
│ │ ├── types.go
│ │ ├── zhihu.go
│ │ └── zhihu_test.go
│ └── zingmp3/
│ ├── zingmp3.go
│ └── zingmp3_test.go
├── go.mod
├── go.sum
├── main.go
├── parser/
│ ├── parser.go
│ └── parser_test.go
├── request/
│ ├── request.go
│ └── request_test.go
├── script/
│ ├── generate_github_action_template.js
│ └── github_action_template.yml
├── test/
│ └── utils.go
└── utils/
├── download.go
├── download_test.go
├── ffmpeg.go
├── pool.go
├── pool_test.go
├── utils.go
└── utils_test.go
SYMBOL INDEX (554 symbols across 121 files)
FILE: app/app.go
constant Name (line 21) | Name = "lux"
function init (line 26) | func init() {
function New (line 40) | func New() *cli.App {
function download (line 279) | func download(c *cli.Context, videoURL string) error {
FILE: downloader/downloader.go
type Options (line 28) | type Options struct
type Downloader (line 52) | type Downloader struct
method caption (line 78) | func (downloader *Downloader) caption(url, fileName, ext string, trans...
method writeFile (line 111) | func (downloader *Downloader) writeFile(url string, file *os.File, hea...
method save (line 128) | func (downloader *Downloader) save(part *extractors.Part, refer, fileN...
method multiThreadSave (line 226) | func (downloader *Downloader) multiThreadSave(dataPart *extractors.Par...
method aria2 (line 510) | func (downloader *Downloader) aria2(title string, stream *extractors.S...
method Download (line 552) | func (downloader *Downloader) Download(data *extractors.Data) error {
constant DOWNLOAD_FILE_EXT (line 58) | DOWNLOAD_FILE_EXT = ".download"
function progressBar (line 61) | func progressBar(size int64) *pb.ProgressBar {
function New (line 70) | func New(option Options) *Downloader {
function filePartPath (line 407) | func filePartPath(filepath string, part *FilePartMeta) string {
function computeEnd (line 411) | func computeEnd(s, chunkSize, max int64) int64 {
function readDirAllFilePart (line 420) | func readDirAllFilePart(filePath, filename, extname string) ([]*FilePart...
function parseFilePartMeta (line 448) | func parseFilePartMeta(filepath string, fileSize int64) (*FilePartMeta, ...
function writeFilePartMeta (line 473) | func writeFilePartMeta(file *os.File, meta *FilePartMeta) error {
function mergeMultiPart (line 477) | func mergeMultiPart(filepath string, parts []*FilePartMeta) error {
FILE: downloader/downloader_test.go
function TestDownload (line 9) | func TestDownload(t *testing.T) {
FILE: downloader/types.go
type Aria2RPCData (line 4) | type Aria2RPCData struct
type Aria2Input (line 17) | type Aria2Input struct
type FilePartMeta (line 25) | type FilePartMeta struct
FILE: downloader/utils.go
function genSortedStreams (line 17) | func genSortedStreams(streams map[string]*extractors.Stream) []*extracto...
function printHeader (line 30) | func printHeader(data *extractors.Data) {
function printStream (line 40) | func printStream(stream *extractors.Stream) {
function printInfo (line 52) | func printInfo(data *extractors.Data, sortedStreams []*extractors.Stream) {
function printStreamInfo (line 78) | func printStreamInfo(data *extractors.Data, stream *extractors.Stream) {
FILE: extractors/acfun/acfun.go
function init (line 17) | func init() {
constant bangumiDataPattern (line 22) | bangumiDataPattern = "window.pageInfo = window.bangumiData = (.*);"
constant bangumiListPattern (line 23) | bangumiListPattern = "window.bangumiList = (.*);"
constant bangumiHTMLURL (line 25) | bangumiHTMLURL = "https://www.acfun.cn/bangumi/aa%d_36188_%d"
constant referer (line 27) | referer = "https://www.acfun.cn"
type extractor (line 30) | type extractor struct
method Extract (line 38) | func (e *extractor) Extract(URL string, option extractors.Options) ([]...
function New (line 33) | func New() extractors.Extractor {
function concatURL (line 79) | func concatURL(epData *episodeData) string {
function extractBangumi (line 83) | func extractBangumi(URL string) *extractors.Data {
function resolvingData (line 146) | func resolvingData(html []byte) (*bangumiData, *videoInfo, error) {
function resolvingEpisodes (line 165) | func resolvingEpisodes(html []byte) (*episodeList, error) {
FILE: extractors/acfun/acfun_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/acfun/types.go
type episodeData (line 3) | type episodeData struct
type bangumiData (line 10) | type bangumiData struct
type videoInfo (line 18) | type videoInfo struct
type streams (line 24) | type streams
type episodeList (line 26) | type episodeList struct
type stream (line 30) | type stream struct
FILE: extractors/bcy/bcy.go
function init (line 15) | func init() {
type bcyData (line 19) | type bcyData struct
type extractor (line 29) | type extractor struct
method Extract (line 37) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 32) | func New() extractors.Extractor {
FILE: extractors/bcy/bcy_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/bilibili/bilibili.go
function init (line 20) | func init() {
constant bilibiliAPI (line 27) | bilibiliAPI = "https://api.bilibili.com/x/player/playurl?"
constant bilibiliBangumiAPI (line 28) | bilibiliBangumiAPI = "https://api.bilibili.com/pgc/player/web/playurl?"
constant bilibiliTokenAPI (line 29) | bilibiliTokenAPI = "https://api.bilibili.com/x/player/playurl/token?"
constant referer (line 32) | referer = "https://www.bilibili.com"
function genAPI (line 36) | func genAPI(aid, cid, quality int, bvid string, bangumi bool, cookie str...
type bilibiliOptions (line 86) | type bilibiliOptions struct
function extractBangumi (line 97) | func extractBangumi(url, html string, extractOption extractors.Options) ...
function getMultiPageData (line 184) | func getMultiPageData(html string) (*multiPage, error) {
function extractFestival (line 199) | func extractFestival(url, html string, extractOption extractors.Options)...
function extractNormalVideo (line 226) | func extractNormalVideo(url, html string, extractOption extractors.Optio...
function multiEpisodeDownload (line 278) | func multiEpisodeDownload(url, html string, extractOption extractors.Opt...
function multiPageDownload (line 307) | func multiPageDownload(url, html string, extractOption extractors.Option...
type extractor (line 336) | type extractor struct
method Extract (line 344) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 339) | func New() extractors.Extractor {
function bilibiliDownload (line 366) | func bilibiliDownload(options bilibiliOptions, extractOption extractors....
function getExtFromMimeType (line 518) | func getExtFromMimeType(mimeType string) string {
function getSubTitleCaptionPart (line 526) | func getSubTitleCaptionPart(aid int, cid int) *extractors.CaptionPart {
function subtitleTransform (line 547) | func subtitleTransform(body []byte) ([]byte, error) {
FILE: extractors/bilibili/bilibili_test.go
function TestBilibili (line 10) | func TestBilibili(t *testing.T) {
FILE: extractors/bilibili/types.go
type tokenData (line 5) | type tokenData struct
type token (line 9) | type token struct
type Interaction (line 15) | type Interaction struct
type EpVideoInfo (line 19) | type EpVideoInfo struct
type bangumiData (line 32) | type bangumiData struct
type videoPagesData (line 37) | type videoPagesData struct
type multiPageVideoData (line 43) | type multiPageVideoData struct
type episode (line 48) | type episode struct
type multiEpisodeData (line 55) | type multiEpisodeData struct
type multiPage (line 60) | type multiPage struct
type dashStream (line 67) | type dashStream struct
type dashStreams (line 76) | type dashStreams struct
type dashInfo (line 81) | type dashInfo struct
type dURL (line 90) | type dURL struct
type dash (line 95) | type dash struct
type subtitleData (line 116) | type subtitleData struct
type bilibiliSubtitleFormat (line 123) | type bilibiliSubtitleFormat struct
type subtitleProperty (line 132) | type subtitleProperty struct
type subtitleInfo (line 139) | type subtitleInfo struct
type bilibiliWebInterfaceData (line 144) | type bilibiliWebInterfaceData struct
type bilibiliWebInterface (line 149) | type bilibiliWebInterface struct
type festival (line 154) | type festival struct
FILE: extractors/bitchute/bitchute.go
function init (line 18) | func init() {
type extractor (line 22) | type extractor struct
method Extract (line 30) | func (e *extractor) Extract(u string, option extractors.Options) ([]*e...
function New (line 25) | func New() extractors.Extractor {
FILE: extractors/bitchute/bitchute_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/douyin/douyin.go
function init (line 21) | func init() {
type extractor (line 30) | type extractor struct
method Extract (line 38) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 33) | func New() extractors.Extractor {
function createCookie (line 158) | func createCookie() (string, error) {
function msToken (line 173) | func msToken(length int) (string, error) {
function ttwid (line 186) | func ttwid() (string, error) {
FILE: extractors/douyin/douyin_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/douyin/sign.js
function _0x5cd844 (line 3) | function _0x5cd844(e) {
function f (line 11) | function f(e, b, a) {
function c (line 32) | function c(e) {
function M (line 133) | function M(e, b, a) {
function P (line 141) | function P(e, b, a, n, h, p, y, v) {
function F (line 321) | function F(e, b, a, f, c, r, t, d) {
function _0x5887c8 (line 385) | function _0x5887c8(e) {
function _0x178cef (line 456) | function _0x178cef(e) {
function sign (line 501) | function sign(e, b) {
FILE: extractors/douyin/types.go
type douyinData (line 3) | type douyinData struct
FILE: extractors/douyu/douyu.go
function init (line 13) | func init() {
type douyuData (line 17) | type douyuData struct
type douyuURLInfo (line 24) | type douyuURLInfo struct
function douyuM3u8 (line 29) | func douyuM3u8(url string) ([]douyuURLInfo, int64, error) {
type extractor (line 55) | type extractor struct
method Extract (line 63) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 58) | func New() extractors.Extractor {
FILE: extractors/douyu/douyu_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/eporner/eporner.go
function init (line 17) | func init() {
constant downloadclass (line 22) | downloadclass = ".dloaddivcol"
type src (line 25) | type src struct
function getSrcMeta (line 32) | func getSrcMeta(text string) *src {
function getSrc (line 69) | func getSrc(html string) []*src {
type extractor (line 99) | type extractor struct
method Extract (line 107) | func (e *extractor) Extract(u string, option extractors.Options) ([]*e...
function New (line 102) | func New() extractors.Extractor {
FILE: extractors/eporner/eporner_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/extractors.go
function Register (line 17) | func Register(domain string, e Extractor) {
function Extract (line 24) | func Extract(u string, option Options) ([]*Data, error) {
FILE: extractors/facebook/facebook.go
function init (line 14) | func init() {
type extractor (line 18) | type extractor struct
method Extract (line 26) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 21) | func New() extractors.Extractor {
FILE: extractors/facebook/facebook_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/geekbang/geekbang.go
function init (line 16) | func init() {
type geekData (line 20) | type geekData struct
type videoPlayAuth (line 30) | type videoPlayAuth struct
type playInfo (line 38) | type playInfo struct
type geekURLInfo (line 53) | type geekURLInfo struct
function geekM3u8 (line 58) | func geekM3u8(url string) ([]geekURLInfo, error) {
type extractor (line 79) | type extractor struct
method Extract (line 87) | func (e *extractor) Extract(url string, _ extractors.Options) ([]*extr...
function New (line 82) | func New() extractors.Extractor {
FILE: extractors/geekbang/geekbang_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/haokan/haokan.go
function init (line 13) | func init() {
type extractor (line 17) | type extractor struct
method Extract (line 25) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 20) | func New() extractors.Extractor {
FILE: extractors/haokan/haokan_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/hupu/hupu.go
function init (line 11) | func init() {
type extractor (line 15) | type extractor struct
method Extract (line 22) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 18) | func New() extractors.Extractor {
FILE: extractors/hupu/hupu_test.go
function TestHupu (line 10) | func TestHupu(t *testing.T) {
FILE: extractors/huya/huya.go
function init (line 11) | func init() {
type extractor (line 15) | type extractor struct
method Extract (line 24) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
constant huyaVideoHost (line 17) | huyaVideoHost = "https://videotx-platform.cdn.huya.com/"
function New (line 20) | func New() extractors.Extractor {
FILE: extractors/huya/huya_test.go
function TestHuya (line 10) | func TestHuya(t *testing.T) {
FILE: extractors/instagram/instagram.go
function init (line 24) | func init() {
type sliderItemNode (line 38) | type sliderItemNode struct
method extractMediaURL (line 45) | func (s sliderItemNode) extractMediaURL() string {
type instagramPayload (line 53) | type instagramPayload struct
method isEmpty (line 64) | func (s instagramPayload) isEmpty() bool {
function getPostWithCode (line 68) | func getPostWithCode(code string) ([]string, error) {
function extractShortCodeFromLink (line 129) | func extractShortCodeFromLink(link string) (string, error) {
type extractor (line 138) | type extractor struct
method Extract (line 146) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 141) | func New() extractors.Extractor {
FILE: extractors/instagram/instagram_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/iqiyi/iqiyi.go
function init (line 19) | func init() {
type iqiyi (line 24) | type iqiyi struct
type iqiyiURL (line 45) | type iqiyiURL struct
type SiteType (line 50) | type SiteType
constant SiteTypeIQ (line 54) | SiteTypeIQ SiteType = iota
constant SiteTypeIqiyi (line 56) | SiteTypeIqiyi
constant iqReferer (line 57) | iqReferer = "https://www.iq.com"
constant iqiyiReferer (line 58) | iqiyiReferer = "https://www.iqiyi.com"
function getMacID (line 61) | func getMacID() string {
function getVF (line 74) | func getVF(params string) string {
function getVPS (line 93) | func getVPS(tvid, vid, refer string) (*iqiyi, error) {
type extractor (line 113) | type extractor struct
method Extract (line 125) | func (e *extractor) Extract(url string, _ extractors.Options) ([]*extr...
function New (line 118) | func New(siteType SiteType) extractors.Extractor {
FILE: extractors/iqiyi/iqiyi_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/ixigua/ixigua.go
function init (line 19) | func init() {
type extractor (line 24) | type extractor struct
method Extract (line 42) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
type Video (line 26) | type Video struct
function New (line 37) | func New() extractors.Extractor {
function base64Decode (line 122) | func base64Decode(t string) string {
FILE: extractors/ixigua/ixigua_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/ixigua/types.go
type xiguanData (line 3) | type xiguanData struct
FILE: extractors/kuaishou/kuaishou.go
function init (line 15) | func init() {
type extractor (line 19) | type extractor struct
method Extract (line 46) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 22) | func New() extractors.Extractor {
function fetchCookies (line 27) | func fetchCookies(url string, headers map[string]string) (string, error) {
FILE: extractors/kuaishou/kuaishou_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/mgtv/mgtv.go
function init (line 19) | func init() {
type mgtvVideoStream (line 23) | type mgtvVideoStream struct
type mgtvVideoInfo (line 29) | type mgtvVideoInfo struct
type mgtvVideoData (line 34) | type mgtvVideoData struct
type mgtv (line 40) | type mgtv struct
type mgtvVideoAddr (line 44) | type mgtvVideoAddr struct
type mgtvURLInfo (line 48) | type mgtvURLInfo struct
type mgtvPm2Data (line 53) | type mgtvPm2Data struct
function mgtvM3u8 (line 62) | func mgtvM3u8(url string) ([]mgtvURLInfo, int64, error) {
function encodeTk2 (line 91) | func encodeTk2(str string) string {
type extractor (line 103) | type extractor struct
method Extract (line 111) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 106) | func New() extractors.Extractor {
FILE: extractors/mgtv/mgtv_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/miaopai/miaopai.go
function init (line 16) | func init() {
type miaopaiData (line 20) | type miaopaiData struct
function getRandomString (line 31) | func getRandomString(l int) string {
type extractor (line 43) | type extractor struct
method Extract (line 51) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 46) | func New() extractors.Extractor {
FILE: extractors/miaopai/miaopai_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/netease/netease.go
function init (line 14) | func init() {
type extractor (line 18) | type extractor struct
method Extract (line 26) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 21) | func New() extractors.Extractor {
FILE: extractors/netease/netease_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/odysee/odysee.go
function init (line 16) | func init() {
type extractor (line 20) | type extractor struct
method Extract (line 35) | func (e *extractor) Extract(u string, option extractors.Options) ([]*e...
type odyseePayload (line 22) | type odyseePayload struct
function New (line 31) | func New() extractors.Extractor {
FILE: extractors/odysee/odysee_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/pinterest/pinterest.go
function init (line 13) | func init() {
type extractor (line 17) | type extractor struct
method Extract (line 25) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 20) | func New() extractors.Extractor {
FILE: extractors/pinterest/pinterest_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/pixivision/pixivision.go
function init (line 12) | func init() {
type extractor (line 16) | type extractor struct
method Extract (line 24) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 19) | func New() extractors.Extractor {
FILE: extractors/pixivision/pixivision_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/pornhub/pornhub.go
function init (line 21) | func init() {
type pornhubData (line 25) | type pornhubData struct
type extractor (line 32) | type extractor struct
method Extract (line 40) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 35) | func New() extractors.Extractor {
FILE: extractors/pornhub/pornhub_test.go
function TestPornhub (line 10) | func TestPornhub(t *testing.T) {
FILE: extractors/qq/qq.go
function init (line 17) | func init() {
type qqVideoInfo (line 21) | type qqVideoInfo struct
type qqKeyInfo (line 51) | type qqKeyInfo struct
constant qqPlayerVersion (line 55) | qqPlayerVersion string = "3.2.19.333"
function getVinfo (line 57) | func getVinfo(vid, defn, refer string) (qqVideoInfo, error) {
function genStreams (line 79) | func genStreams(vid, cdn string, data qqVideoInfo) (map[string]*extracto...
type extractor (line 177) | type extractor struct
method Extract (line 185) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 180) | func New() extractors.Extractor {
FILE: extractors/qq/qq_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/reddit/reddit.go
function init (line 14) | func init() {
constant referer (line 19) | referer = "https://www.reddit.com"
constant siteName (line 20) | siteName = "Reddit reddit.com"
constant redditMP4API (line 22) | redditMP4API = "https://v.redd.it/"
constant redditIMGAPI (line 23) | redditIMGAPI = "https://i.redd.it/"
constant audioURLPart (line 24) | audioURLPart = "/DASH_audio.mp4"
type extractor (line 35) | type extractor struct
method Extract (line 41) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 37) | func New() extractors.Extractor {
FILE: extractors/reddit/reddit_test.go
function TestReddit (line 10) | func TestReddit(t *testing.T) {
FILE: extractors/rumble/rumble.go
function init (line 22) | func init() {
type extractor (line 26) | type extractor struct
method Extract (line 44) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 29) | func New() extractors.Extractor {
type rumbleData (line 33) | type rumbleData struct
function readPayload (line 104) | func readPayload(html string) (*rumbleData, error) {
function getVideoID (line 124) | func getVideoID(embedURL string) (string, error) {
type rumbleResponse (line 134) | type rumbleResponse struct
type streamInfo (line 139) | type streamInfo struct
type videoQualities (line 150) | type videoQualities struct
type rumbleStreams (line 162) | type rumbleStreams struct
method UnmarshalJSON (line 177) | func (r *rumbleStreams) UnmarshalJSON(b []byte) error {
method makeAllVODStreams (line 205) | func (rs *rumbleStreams) makeAllVODStreams(m map[string]*extractors.St...
method makeAllLiveStreams (line 220) | func (rs *rumbleStreams) makeAllLiveStreams(m map[string]*extractors.S...
method makeAllNewVodStreams (line 273) | func (rs *rumbleStreams) makeAllNewVodStreams(m map[string]*extractors...
function fetchVideoQuality (line 304) | func fetchVideoQuality(videoID string) (map[string]*extractors.Stream, e...
function makeStreamMeta (line 341) | func makeStreamMeta(q, ext string, info *streamInfo) *extractors.Stream {
FILE: extractors/rumble/rumble_test.go
function TestRumble (line 10) | func TestRumble(t *testing.T) {
FILE: extractors/streamtape/streamtape.go
function init (line 14) | func init() {
type extractor (line 20) | type extractor struct
method Extract (line 28) | func (e *extractor) Extract(url string, _ extractors.Options) ([]*extr...
function New (line 23) | func New() extractors.Extractor {
FILE: extractors/streamtape/streamtape_test.go
function TestStreamtape (line 10) | func TestStreamtape(t *testing.T) {
FILE: extractors/tangdou/tangdou.go
function init (line 13) | func init() {
type extractor (line 17) | type extractor struct
method Extract (line 33) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 20) | func New() extractors.Extractor {
function tangdouDownload (line 38) | func tangdouDownload(uri string) *extractors.Data {
FILE: extractors/tangdou/tangdou_test.go
function TestTangDou (line 10) | func TestTangDou(t *testing.T) {
FILE: extractors/threads/threads.go
function init (line 19) | func init() {
type extractor (line 23) | type extractor struct
method Extract (line 48) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 28) | func New() extractors.Extractor {
type media (line 42) | type media struct
FILE: extractors/threads/threads_test.go
function TestDownload (line 11) | func TestDownload(t *testing.T) {
FILE: extractors/tiktok/tiktok.go
function init (line 13) | func init() {
type extractor (line 17) | type extractor struct
method Extract (line 25) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 20) | func New() extractors.Extractor {
FILE: extractors/tiktok/tiktok_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/tumblr/tumblr.go
function init (line 15) | func init() {
type imageList (line 19) | type imageList struct
type tumblrImageList (line 23) | type tumblrImageList struct
type tumblrImage (line 27) | type tumblrImage struct
function genURLData (line 31) | func genURLData(url, referer string) (*extractors.Part, int64, error) {
function tumblrImageDownload (line 47) | func tumblrImageDownload(url, html, title string) ([]*extractors.Data, e...
function tumblrVideoDownload (line 103) | func tumblrVideoDownload(url, html, title string) ([]*extractors.Data, e...
type extractor (line 146) | type extractor struct
method Extract (line 154) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 149) | func New() extractors.Extractor {
FILE: extractors/tumblr/tumblr_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/twitter/twitter.go
function init (line 16) | func init() {
type twitter (line 20) | type twitter struct
type extractor (line 28) | type extractor struct
method Extract (line 36) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 31) | func New() extractors.Extractor {
function download (line 78) | func download(data twitter, uri string) ([]*extractors.Data, error) {
FILE: extractors/twitter/twitter_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/types.go
type Part (line 4) | type Part struct
type CaptionPart (line 10) | type CaptionPart struct
type Stream (line 16) | type Stream struct
type DataType (line 34) | type DataType
constant DataTypeVideo (line 38) | DataTypeVideo DataType = "video"
constant DataTypeImage (line 40) | DataTypeImage DataType = "image"
constant DataTypeAudio (line 42) | DataTypeAudio DataType = "audio"
type Data (line 46) | type Data struct
method FillUpStreamsData (line 61) | func (d *Data) FillUpStreamsData() {
function EmptyData (line 94) | func EmptyData(url string, err error) *Data {
type Options (line 102) | type Options struct
type Extractor (line 125) | type Extractor interface
FILE: extractors/udn/udn.go
function init (line 13) | func init() {
constant startFlag (line 18) | startFlag = `',
constant endFlag (line 20) | endFlag = `'
function getCDNUrl (line 25) | func getCDNUrl(html string) string {
function prepareEmbedURL (line 32) | func prepareEmbedURL(url string) string {
type extractor (line 43) | type extractor struct
method Extract (line 51) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 46) | func New() extractors.Extractor {
FILE: extractors/udn/udn_test.go
function TestExtract (line 10) | func TestExtract(t *testing.T) {
FILE: extractors/universal/universal.go
function init (line 11) | func init() {
type extractor (line 15) | type extractor struct
method Extract (line 23) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 18) | func New() extractors.Extractor {
FILE: extractors/universal/universal_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/vimeo/vimeo.go
function init (line 14) | func init() {
type vimeoProgressive (line 18) | type vimeoProgressive struct
type vimeoFiles (line 26) | type vimeoFiles struct
type vimeoRequest (line 30) | type vimeoRequest struct
type vimeoVideo (line 34) | type vimeoVideo struct
type vimeo (line 38) | type vimeo struct
type extractor (line 43) | type extractor struct
method Extract (line 51) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 46) | func New() extractors.Extractor {
FILE: extractors/vimeo/vimeo_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/vk/vk.go
function init (line 14) | func init() {
type extractor (line 27) | type extractor struct
method Extract (line 33) | func (e extractor) Extract(url string, option extractors.Options) ([]*...
function New (line 29) | func New() extractors.Extractor {
FILE: extractors/vk/vk_test.go
function TestVK (line 10) | func TestVK(t *testing.T) {
FILE: extractors/weibo/weibo.go
function init (line 20) | func init() {
type playInfo (line 24) | type playInfo struct
type playData (line 29) | type playData struct
type weiboData (line 33) | type weiboData struct
function getXSRFToken (line 39) | func getXSRFToken() (string, error) {
function downloadWeiboVideo (line 69) | func downloadWeiboVideo(url string) ([]*extractors.Data, error) {
function downloadWeiboTV (line 142) | func downloadWeiboTV(url string) ([]*extractors.Data, error) {
type extractor (line 225) | type extractor struct
method Extract (line 233) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 228) | func New() extractors.Extractor {
FILE: extractors/weibo/weibo_test.go
function TestToken (line 10) | func TestToken(t *testing.T) {
function TestDownload (line 16) | func TestDownload(t *testing.T) {
FILE: extractors/xiaohongshu/xiaohongshu.go
function init (line 17) | func init() {
type extractor (line 21) | type extractor struct
method Extract (line 31) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 24) | func New() extractors.Extractor {
constant mp4VideoType (line 28) | mp4VideoType = "mp4"
FILE: extractors/xiaohongshu/xiaohongshu_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/ximalaya/types.go
type ximalayaData (line 3) | type ximalayaData struct
FILE: extractors/ximalaya/ximalaya.go
function init (line 14) | func init() {
type extractor (line 18) | type extractor struct
method Extract (line 26) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 21) | func New() extractors.Extractor {
FILE: extractors/ximalaya/ximalaya_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/xinpianchang/xinpianchang.go
function init (line 16) | func init() {
type extractor (line 20) | type extractor struct
method Extract (line 38) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
type Video (line 22) | type Video struct
function New (line 33) | func New() extractors.Extractor {
FILE: extractors/xinpianchang/xinpianchang_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/xvideos/xvideos.go
function init (line 14) | func init() {
constant lowFlag (line 19) | lowFlag = "html5player.setVideoUrlLow('"
constant lowFinalFlag (line 20) | lowFinalFlag = `');
constant highFlag (line 22) | highFlag = "html5player.setVideoUrlHigh('"
constant highFinalFlag (line 23) | highFinalFlag = `');
constant qualityLow (line 25) | qualityLow = "low"
constant qualityHigh (line 26) | qualityHigh = "high"
type src (line 34) | type src struct
function getSrc (line 39) | func getSrc(html string) []*src {
type extractor (line 84) | type extractor struct
method Extract (line 92) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 87) | func New() extractors.Extractor {
FILE: extractors/xvideos/xvideos_test.go
function TestExtract (line 10) | func TestExtract(t *testing.T) {
FILE: extractors/yinyuetai/types.go
type yinyuetaiMvData (line 3) | type yinyuetaiMvData struct
type videoInfo (line 9) | type videoInfo struct
type coreVideoInfo (line 13) | type coreVideoInfo struct
type videoURLModel (line 23) | type videoURLModel struct
FILE: extractors/yinyuetai/yinyuetai.go
function init (line 14) | func init() {
constant yinyuetaiAPI (line 18) | yinyuetaiAPI = "https://ext.yinyuetai.com/main/"
constant actionGetMvInfo (line 21) | actionGetMvInfo = "get-h-mv-info"
function genAPI (line 24) | func genAPI(action string, param string) string {
type extractor (line 28) | type extractor struct
method Extract (line 36) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 31) | func New() extractors.Extractor {
FILE: extractors/yinyuetai/yinyuetai_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/youku/youku.go
function init (line 23) | func init() {
type errorData (line 27) | type errorData struct
type segs (line 32) | type segs struct
type stream (line 37) | type stream struct
type youkuVideo (line 46) | type youkuVideo struct
type youkuShow (line 50) | type youkuShow struct
type data (line 54) | type data struct
type youkuData (line 61) | type youkuData struct
constant youkuReferer (line 65) | youkuReferer = "https://v.youku.com"
function getAudioLang (line 67) | func getAudioLang(lang string) string {
function youkuUps (line 85) | func youkuUps(vid string, option extractors.Options) (*youkuData, error) {
function getBytes (line 136) | func getBytes(val int32) []byte {
function hashCode (line 142) | func hashCode(s string) int32 {
function hmacSha1 (line 150) | func hmacSha1(key []byte, msg []byte) []byte {
function generateUtdid (line 156) | func generateUtdid() string {
function genData (line 170) | func genData(youkuData data) map[string]*extractors.Stream {
type extractor (line 211) | type extractor struct
method Extract (line 219) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 214) | func New() extractors.Extractor {
FILE: extractors/youku/youku_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/youtube/youtube.go
function init (line 18) | func init() {
constant referer (line 24) | referer = "https://www.youtube.com"
type extractor (line 26) | type extractor struct
method Extract (line 44) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
method youtubeDownload (line 95) | func (e *extractor) youtubeDownload(url string, video *youtube.Video) ...
method genPartByFormat (line 163) | func (e *extractor) genPartByFormat(video *youtube.Video, f *youtube.F...
function New (line 31) | func New() extractors.Extractor {
function getVideoAudio (line 180) | func getVideoAudio(v *youtube.Video, mimeType string) (*youtube.Format, ...
function getStreamExt (line 189) | func getStreamExt(streamType string) string {
FILE: extractors/youtube/youtube_test.go
function TestYoutube (line 10) | func TestYoutube(t *testing.T) {
FILE: extractors/zhihu/types.go
type video (line 4) | type video struct
type resolution (line 13) | type resolution struct
FILE: extractors/zhihu/zhihu.go
constant videoURL (line 16) | videoURL = "www.zhihu.com/zvideo"
constant api (line 17) | api = "https://lens.zhihu.com/api/v4/videos/"
function init (line 20) | func init() {
type extractor (line 24) | type extractor struct
method Extract (line 30) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 26) | func New() extractors.Extractor {
FILE: extractors/zhihu/zhihu_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: extractors/zingmp3/zingmp3.go
function init (line 23) | func init() {
type extractor (line 29) | type extractor struct
method Extract (line 49) | func (e *extractor) Extract(url string, option extractors.Options) ([]...
function New (line 32) | func New() extractors.Extractor {
type params (line 36) | type params
constant Domain (line 46) | Domain = "https://zingmp3.vn"
function callApi (line 145) | func callApi(urlType string, p params) []byte {
function updatingCookies (line 152) | func updatingCookies() error {
function generateApi (line 176) | func generateApi(urlType string, p params) string {
function generateSig (line 194) | func generateSig(slugApi string, p params) string {
function sortedParams (line 209) | func sortedParams(p params) params {
FILE: extractors/zingmp3/zingmp3_test.go
function TestDownload (line 10) | func TestDownload(t *testing.T) {
FILE: main.go
function main (line 12) | func main() {
FILE: parser/parser.go
function GetDoc (line 12) | func GetDoc(html string) (*goquery.Document, error) {
function GetImages (line 21) | func GetImages(html, imgClass string, urlHandler func(string) string) (s...
function Title (line 42) | func Title(doc *goquery.Document) string {
FILE: parser/parser_test.go
function TestGetDoc (line 8) | func TestGetDoc(t *testing.T) {
function TestGetImages (line 36) | func TestGetImages(t *testing.T) {
function TestGetTitle (line 65) | func TestGetTitle(t *testing.T) {
FILE: request/request.go
type Options (line 32) | type Options struct
function SetOptions (line 42) | func SetOptions(opt Options) {
function Request (line 51) | func Request(method, url string, body io.Reader, headers map[string]stri...
function Get (line 142) | func Get(url, refer string, headers map[string]string) (string, error) {
function GetByte (line 148) | func GetByte(url, refer string, headers map[string]string) ([]byte, erro...
function Headers (line 180) | func Headers(url, refer string) (http.Header, error) {
function Size (line 193) | func Size(url, refer string) (int64, error) {
function ContentType (line 210) | func ContentType(url, refer string) (string, error) {
FILE: request/request_test.go
function TestGet (line 7) | func TestGet(t *testing.T) {
function TestHeaders (line 61) | func TestHeaders(t *testing.T) {
function TestSize (line 89) | func TestSize(t *testing.T) {
function TestContentType (line 117) | func TestContentType(t *testing.T) {
FILE: script/generate_github_action_template.js
function generateCITemplate (line 10) | function generateCITemplate(moduleName) {
FILE: test/utils.go
type Args (line 12) | type Args struct
function CheckData (line 20) | func CheckData(args, data Args) bool {
function Check (line 35) | func Check(t *testing.T, args Args, data *extractors.Data) {
function CheckError (line 58) | func CheckError(t *testing.T, err error) {
FILE: utils/download.go
function NeedDownloadList (line 9) | func NeedDownloadList(items string, itemStart, itemEnd, length int) []int {
FILE: utils/download_test.go
function TestNeedDownloadList (line 8) | func TestNeedDownloadList(t *testing.T) {
FILE: utils/ffmpeg.go
function findFFmpegExecutable (line 15) | func findFFmpegExecutable() string {
function runMergeCmd (line 31) | func runMergeCmd(cmd *exec.Cmd, paths []string, mergeFilePath string) er...
function MergeFilesWithSameExtension (line 51) | func MergeFilesWithSameExtension(paths []string, mergedFilePath string) ...
function MergeToMP4 (line 64) | func MergeToMP4(paths []string, mergedFilePath string, filename string) ...
function toISO639 (line 90) | func toISO639(lang string) string {
function subtitleCodec (line 105) | func subtitleCodec(ext string) string {
function EmbedSubtitles (line 117) | func EmbedSubtitles(videoPath string, subtitlePaths []string, langs []st...
FILE: utils/pool.go
type WaitGroupPool (line 9) | type WaitGroupPool struct
method Add (line 27) | func (p *WaitGroupPool) Add() {
method Done (line 34) | func (p *WaitGroupPool) Done() {
method Wait (line 41) | func (p *WaitGroupPool) Wait() {
function NewWaitGroupPool (line 15) | func NewWaitGroupPool(size int) *WaitGroupPool {
FILE: utils/pool_test.go
function TestWaitGroupPool (line 8) | func TestWaitGroupPool(t *testing.T) {
FILE: utils/utils.go
function ConvertXMLToSRT (line 24) | func ConvertXMLToSRT(xmlContent []byte) (string, error) {
function ConvertXMLFileToSRT (line 72) | func ConvertXMLFileToSRT(xmlPath string) (string, error) {
function formatSRTTime (line 85) | func formatSRTTime(ms int) string {
function MatchOneOf (line 96) | func MatchOneOf(text string, patterns ...string) []string {
function MatchAll (line 115) | func MatchAll(text, pattern string) [][]string {
function FileSize (line 122) | func FileSize(filePath string) (int64, bool, error) {
function Domain (line 134) | func Domain(url string) string {
function LimitLength (line 147) | func LimitLength(s string, length int) string {
function FileName (line 162) | func FileName(name, ext string, length int) string {
function FilePath (line 177) | func FilePath(name, ext string, length int, outputPath string, escape bo...
function FileLineCounter (line 193) | func FileLineCounter(r io.Reader) (int, error) {
function ParseInputFile (line 213) | func ParseInputFile(r io.Reader, items string, itemStart, itemEnd int) [...
function GetNameAndExt (line 239) | func GetNameAndExt(uri string) (string, string, error) {
function Md5 (line 260) | func Md5(text string) string {
function M3u8URLs (line 267) | func M3u8URLs(uri string) ([]string, error) {
function Reverse (line 300) | func Reverse(s string) string {
function Range (line 309) | func Range(min, max int) []int {
FILE: utils/utils_test.go
function TestMatchOneOf (line 9) | func TestMatchOneOf(t *testing.T) {
function TestMatchAll (line 47) | func TestMatchAll(t *testing.T) {
function TestFileSize (line 82) | func TestFileSize(t *testing.T) {
function TestDomain (line 108) | func TestDomain(t *testing.T) {
function TestLimitLength (line 169) | func TestLimitLength(t *testing.T) {
function TestFileName (line 205) | func TestFileName(t *testing.T) {
function TestFilePath (line 245) | func TestFilePath(t *testing.T) {
function TestGetNameAndExt (line 284) | func TestGetNameAndExt(t *testing.T) {
function TestMd5 (line 332) | func TestMd5(t *testing.T) {
function TestReverse (line 358) | func TestReverse(t *testing.T) {
function TestRange (line 384) | func TestRange(t *testing.T) {
function TestLineCount (line 420) | func TestLineCount(t *testing.T) {
function TestParsingFile (line 455) | func TestParsingFile(t *testing.T) {
function TestConvertXMLToSRT (line 535) | func TestConvertXMLToSRT(t *testing.T) {
Condensed preview — 186 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (516K chars).
[
{
"path": ".gitattributes",
"chars": 34,
"preview": "*.go text eol=lf\n*.md text eol=lf\n"
},
{
"path": ".github/ISSUE_TEMPLATE/can-not-download-video.md",
"chars": 744,
"preview": "---\nname: Can not download video\nabout: If you find that a website download is not working\ntitle: \"[download fail]: the "
},
{
"path": ".github/ISSUE_TEMPLATE/support-a-new-website.md",
"chars": 432,
"preview": "---\nname: Support a new website\nabout: If you want to request support for a new website\ntitle: \"[new website require]: t"
},
{
"path": ".github/workflows/builder.yml",
"chars": 3392,
"preview": "name: Builder\n\non:\n push:\n branches: \"*\"\n paths:\n - \"**/*.go\"\n - \"go.mod\"\n - \"go.sum\"\n - \".gi"
},
{
"path": ".github/workflows/ci.yml",
"chars": 867,
"preview": "name: ci\non:\n push:\n pull_request:\n schedule:\n # run ci weekly\n - cron: \"0 0 * * 0\"\njobs:\n ci:\n runs-on: ${"
},
{
"path": ".github/workflows/goreleaser.yml",
"chars": 551,
"preview": "name: goreleaser\non:\n push:\n tags:\n - '*'\npermissions:\n contents: write\njobs:\n goreleaser:\n runs-on: ubunt"
},
{
"path": ".github/workflows/stream_acfun.yml",
"chars": 697,
"preview": "name: acfun\n\non:\n push:\n paths:\n - \"extractors/acfun/*.go\"\n - \".github/workflows/stream_acfun.yml\"\n pull_"
},
{
"path": ".github/workflows/stream_bcy.yml",
"chars": 685,
"preview": "name: bcy\n\non:\n push:\n paths:\n - \"extractors/bcy/*.go\"\n - \".github/workflows/stream_bcy.yml\"\n pull_reques"
},
{
"path": ".github/workflows/stream_bilibili.yml",
"chars": 715,
"preview": "name: bilibili\n\non:\n push:\n paths:\n - \"extractors/bilibili/*.go\"\n - \".github/workflows/stream_bilibili.yml"
},
{
"path": ".github/workflows/stream_bitchute.yml",
"chars": 715,
"preview": "name: bitchute\n\non:\n push:\n paths:\n - \"extractors/bitchute/*.go\"\n - \".github/workflows/stream_bitchute.yml"
},
{
"path": ".github/workflows/stream_douyin.yml",
"chars": 703,
"preview": "name: douyin\n\non:\n push:\n paths:\n - \"extractors/douyin/*.go\"\n - \".github/workflows/stream_douyin.yml\"\n pu"
},
{
"path": ".github/workflows/stream_douyu.yml",
"chars": 697,
"preview": "name: douyu\n\non:\n push:\n paths:\n - \"extractors/douyu/*.go\"\n - \".github/workflows/stream_douyu.yml\"\n pull_"
},
{
"path": ".github/workflows/stream_eporner.yml",
"chars": 709,
"preview": "name: eporner\n\non:\n push:\n paths:\n - \"extractors/eporner/*.go\"\n - \".github/workflows/stream_eporner.yml\"\n "
},
{
"path": ".github/workflows/stream_facebook.yml",
"chars": 715,
"preview": "name: facebook\n\non:\n push:\n paths:\n - \"extractors/facebook/*.go\"\n - \".github/workflows/stream_facebook.yml"
},
{
"path": ".github/workflows/stream_geekbang.yml",
"chars": 715,
"preview": "name: geekbang\n\non:\n push:\n paths:\n - \"extractors/geekbang/*.go\"\n - \".github/workflows/stream_geekbang.yml"
},
{
"path": ".github/workflows/stream_haokan.yml",
"chars": 703,
"preview": "name: haokan\n\non:\n push:\n paths:\n - \"extractors/haokan/*.go\"\n - \".github/workflows/stream_haokan.yml\"\n pu"
},
{
"path": ".github/workflows/stream_hupu.yml",
"chars": 691,
"preview": "name: hupu\n\non:\n push:\n paths:\n - \"extractors/hupu/*.go\"\n - \".github/workflows/stream_hupu.yml\"\n pull_req"
},
{
"path": ".github/workflows/stream_huya.yml",
"chars": 691,
"preview": "name: huya\n\non:\n push:\n paths:\n - \"extractors/huya/*.go\"\n - \".github/workflows/stream_huya.yml\"\n pull_req"
},
{
"path": ".github/workflows/stream_instagram.yml",
"chars": 721,
"preview": "name: instagram\n\non:\n push:\n paths:\n - \"extractors/instagram/*.go\"\n - \".github/workflows/stream_instagram."
},
{
"path": ".github/workflows/stream_iqiyi.yml",
"chars": 697,
"preview": "name: iqiyi\n\non:\n push:\n paths:\n - \"extractors/iqiyi/*.go\"\n - \".github/workflows/stream_iqiyi.yml\"\n pull_"
},
{
"path": ".github/workflows/stream_ixigua.yml",
"chars": 703,
"preview": "name: ixigua\n\non:\n push:\n paths:\n - \"extractors/ixigua/*.go\"\n - \".github/workflows/stream_ixigua.yml\"\n pu"
},
{
"path": ".github/workflows/stream_kuaishou.yml",
"chars": 715,
"preview": "name: kuaishou\n\non:\n push:\n paths:\n - \"extractors/kuaishou/*.go\"\n - \".github/workflows/stream_kuaishou.yml"
},
{
"path": ".github/workflows/stream_mgtv.yml",
"chars": 691,
"preview": "name: mgtv\n\non:\n push:\n paths:\n - \"extractors/mgtv/*.go\"\n - \".github/workflows/stream_mgtv.yml\"\n pull_req"
},
{
"path": ".github/workflows/stream_miaopai.yml",
"chars": 709,
"preview": "name: miaopai\n\non:\n push:\n paths:\n - \"extractors/miaopai/*.go\"\n - \".github/workflows/stream_miaopai.yml\"\n "
},
{
"path": ".github/workflows/stream_netease.yml",
"chars": 709,
"preview": "name: netease\n\non:\n push:\n paths:\n - \"extractors/netease/*.go\"\n - \".github/workflows/stream_netease.yml\"\n "
},
{
"path": ".github/workflows/stream_odysee.yml",
"chars": 703,
"preview": "name: odysee\n\non:\n push:\n paths:\n - \"extractors/odysee/*.go\"\n - \".github/workflows/stream_odysee.yml\"\n pu"
},
{
"path": ".github/workflows/stream_pinterest.yml",
"chars": 721,
"preview": "name: pinterest\n\non:\n push:\n paths:\n - \"extractors/pinterest/*.go\"\n - \".github/workflows/stream_pinterest."
},
{
"path": ".github/workflows/stream_pixivision.yml",
"chars": 727,
"preview": "name: pixivision\n\non:\n push:\n paths:\n - \"extractors/pixivision/*.go\"\n - \".github/workflows/stream_pixivisi"
},
{
"path": ".github/workflows/stream_pornhub.yml",
"chars": 709,
"preview": "name: pornhub\n\non:\n push:\n paths:\n - \"extractors/pornhub/*.go\"\n - \".github/workflows/stream_pornhub.yml\"\n "
},
{
"path": ".github/workflows/stream_qq.yml",
"chars": 679,
"preview": "name: qq\n\non:\n push:\n paths:\n - \"extractors/qq/*.go\"\n - \".github/workflows/stream_qq.yml\"\n pull_request:\n"
},
{
"path": ".github/workflows/stream_reddit.yml",
"chars": 703,
"preview": "name: reddit\n\non:\n push:\n paths:\n - \"extractors/reddit/*.go\"\n - \".github/workflows/stream_reddit.yml\"\n pu"
},
{
"path": ".github/workflows/stream_rumble.yml",
"chars": 703,
"preview": "name: rumble\n\non:\n push:\n paths:\n - \"extractors/rumble/*.go\"\n - \".github/workflows/stream_rumble.yml\"\n pu"
},
{
"path": ".github/workflows/stream_streamtape.yml",
"chars": 727,
"preview": "name: streamtape\n\non:\n push:\n paths:\n - \"extractors/streamtape/*.go\"\n - \".github/workflows/stream_streamta"
},
{
"path": ".github/workflows/stream_tangdou.yml",
"chars": 709,
"preview": "name: tangdou\n\non:\n push:\n paths:\n - \"extractors/tangdou/*.go\"\n - \".github/workflows/stream_tangdou.yml\"\n "
},
{
"path": ".github/workflows/stream_threads.yml",
"chars": 711,
"preview": "name: instagram\n\non:\n push:\n paths:\n - \"extractors/threads/*.go\"\n - \".github/workflows/stream_threads.yml\""
},
{
"path": ".github/workflows/stream_tiktok.yml",
"chars": 703,
"preview": "name: tiktok\n\non:\n push:\n paths:\n - \"extractors/tiktok/*.go\"\n - \".github/workflows/stream_tiktok.yml\"\n pu"
},
{
"path": ".github/workflows/stream_tumblr.yml",
"chars": 703,
"preview": "name: tumblr\n\non:\n push:\n paths:\n - \"extractors/tumblr/*.go\"\n - \".github/workflows/stream_tumblr.yml\"\n pu"
},
{
"path": ".github/workflows/stream_twitter.yml",
"chars": 709,
"preview": "name: twitter\n\non:\n push:\n paths:\n - \"extractors/twitter/*.go\"\n - \".github/workflows/stream_twitter.yml\"\n "
},
{
"path": ".github/workflows/stream_udn.yml",
"chars": 685,
"preview": "name: udn\n\non:\n push:\n paths:\n - \"extractors/udn/*.go\"\n - \".github/workflows/stream_udn.yml\"\n pull_reques"
},
{
"path": ".github/workflows/stream_vimeo.yml",
"chars": 697,
"preview": "name: vimeo\n\non:\n push:\n paths:\n - \"extractors/vimeo/*.go\"\n - \".github/workflows/stream_vimeo.yml\"\n pull_"
},
{
"path": ".github/workflows/stream_vk.yml",
"chars": 679,
"preview": "name: vk\n\non:\n push:\n paths:\n - \"extractors/vk/*.go\"\n - \".github/workflows/stream_vk.yml\"\n pull_request:\n"
},
{
"path": ".github/workflows/stream_weibo.yml",
"chars": 697,
"preview": "name: weibo\n\non:\n push:\n paths:\n - \"extractors/weibo/*.go\"\n - \".github/workflows/stream_weibo.yml\"\n pull_"
},
{
"path": ".github/workflows/stream_xiaohongshu.yml",
"chars": 733,
"preview": "name: xiaohongshu\n\non:\n push:\n paths:\n - \"extractors/xiaohongshu/*.go\"\n - \".github/workflows/stream_xiaoho"
},
{
"path": ".github/workflows/stream_ximalaya.yml",
"chars": 715,
"preview": "name: ximalaya\n\non:\n push:\n paths:\n - \"extractors/ximalaya/*.go\"\n - \".github/workflows/stream_ximalaya.yml"
},
{
"path": ".github/workflows/stream_xinpianchang.yml",
"chars": 739,
"preview": "name: xinpianchang\n\non:\n push:\n paths:\n - \"extractors/xinpianchang/*.go\"\n - \".github/workflows/stream_xinp"
},
{
"path": ".github/workflows/stream_xvideos.yml",
"chars": 709,
"preview": "name: xvideos\n\non:\n push:\n paths:\n - \"extractors/xvideos/*.go\"\n - \".github/workflows/stream_xvideos.yml\"\n "
},
{
"path": ".github/workflows/stream_yinyuetai.yml",
"chars": 721,
"preview": "name: yinyuetai\n\non:\n push:\n paths:\n - \"extractors/yinyuetai/*.go\"\n - \".github/workflows/stream_yinyuetai."
},
{
"path": ".github/workflows/stream_youku.yml",
"chars": 697,
"preview": "name: youku\n\non:\n push:\n paths:\n - \"extractors/youku/*.go\"\n - \".github/workflows/stream_youku.yml\"\n pull_"
},
{
"path": ".github/workflows/stream_youtube.yml",
"chars": 709,
"preview": "name: youtube\n\non:\n push:\n paths:\n - \"extractors/youtube/*.go\"\n - \".github/workflows/stream_youtube.yml\"\n "
},
{
"path": ".github/workflows/stream_zhihu.yml",
"chars": 697,
"preview": "name: zhihu\n\non:\n push:\n paths:\n - \"extractors/zhihu/*.go\"\n - \".github/workflows/stream_zhihu.yml\"\n pull_"
},
{
"path": ".github/workflows/stream_zingmp3.yml",
"chars": 709,
"preview": "name: zingmp3\n\non:\n push:\n paths:\n - \"extractors/zingmp3/*.go\"\n - \".github/workflows/stream_zingmp3.yml\"\n "
},
{
"path": ".gitignore",
"chars": 379,
"preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Ou"
},
{
"path": ".golangci.yml",
"chars": 484,
"preview": "run:\n concurrency: 2\n timeout: 5m\n go: 1.24\n\nlinter-settings:\n goconst:\n min-len: 2\n min-occurrences: 2\n\nlinte"
},
{
"path": ".goreleaser.yml",
"chars": 865,
"preview": "project_name: lux\nenv:\n - GO111MODULE=on\n - CGO_ENABLED=0\nbefore:\n hooks:\n - go mod download\nbuilds:\n- binary: lux"
},
{
"path": "CONTRIBUTING.md",
"chars": 906,
"preview": "# Contributing Guide\n\n* [Style Guide](#style-guide)\n* [Build](#build)\n* [Features Requested](#features-requested)\n\n## St"
},
{
"path": "Cask.toml",
"chars": 1009,
"preview": "[package]\nname = \"github.com/iawia002/lux\"\nbin = \"lux\"\nauthors = [\"Xinzhao Xu <z2d@jifangcheng.com>\"]\nkeywords = [\"go\", "
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright 2018-present, iawia002\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "README.md",
"chars": 33251,
"preview": "<h1 align=\"center\">Lux</h1>\n\n<p align=\"center\"><i>Let there be Lux!</i></p>\n\n<div align=\"center\">\n <a href=\"https://cod"
},
{
"path": "app/app.go",
"chars": 8608,
"preview": "package app\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/fatih/color\"\n\t\"github.com"
},
{
"path": "app/register.go",
"chars": 2212,
"preview": "package app\n\nimport (\n\t_ \"github.com/iawia002/lux/extractors/acfun\"\n\t_ \"github.com/iawia002/lux/extractors/bcy\"\n\t_ \"gith"
},
{
"path": "codecov.yml",
"chars": 55,
"preview": "codecov:\n token: e0f2d44f-c6a7-469a-a688-37c72c0f18f9\n"
},
{
"path": "config/config.go",
"chars": 435,
"preview": "package config\n\n// FakeHeaders fake http headers\nvar FakeHeaders = map[string]string{\n\t\"Accept\": \"text/html,app"
},
{
"path": "downloader/downloader.go",
"chars": 19533,
"preview": "package downloader\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"path/"
},
{
"path": "downloader/downloader_test.go",
"chars": 2316,
"preview": "package downloader\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n)\n\nfunc TestDownload(t *testing.T) {\n\ttes"
},
{
"path": "downloader/types.go",
"chars": 871,
"preview": "package downloader\n\n// Aria2RPCData defines the data structure of json RPC 2.0 info for Aria2\ntype Aria2RPCData struct {"
},
{
"path": "downloader/utils.go",
"chars": 2152,
"preview": "package downloader\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/iawia002/lux/extractors\"\n)\n\nvar (\n\t"
},
{
"path": "extractors/acfun/acfun.go",
"chars": 4027,
"preview": "package acfun\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"regexp\"\n\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/pkg/errors\"\n\n\t"
},
{
"path": "extractors/acfun/acfun_test.go",
"chars": 564,
"preview": "package acfun\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDo"
},
{
"path": "extractors/acfun/types.go",
"chars": 886,
"preview": "package acfun\n\ntype episodeData struct {\n\tItemID int64 `json:\"itemId\"`\n\tEpisodeName string `json:\"episodeName\"`\n\tB"
},
{
"path": "extractors/bcy/bcy.go",
"chars": 2169,
"preview": "package bcy\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"git"
},
{
"path": "extractors/bcy/bcy_test.go",
"chars": 601,
"preview": "package bcy\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDown"
},
{
"path": "extractors/bilibili/bilibili.go",
"chars": 15751,
"preview": "package bilibili\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/err"
},
{
"path": "extractors/bilibili/bilibili_test.go",
"chars": 2033,
"preview": "package bilibili\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Tes"
},
{
"path": "extractors/bilibili/types.go",
"chars": 4698,
"preview": "package bilibili\n\n// {\"code\":0,\"message\":\"0\",\"ttl\":1,\"data\":{\"token\":\"aaa\"}}\n// {\"code\":-101,\"message\":\"账号未登录\",\"ttl\":1}\n"
},
{
"path": "extractors/bitchute/bitchute.go",
"chars": 2572,
"preview": "package bitchute\n\nimport (\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.co"
},
{
"path": "extractors/bitchute/bitchute_test.go",
"chars": 744,
"preview": "package bitchute\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Tes"
},
{
"path": "extractors/douyin/douyin.go",
"chars": 6209,
"preview": "package douyin\n\nimport (\n\t\"crypto/rand\"\n\t_ \"embed\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\tnetURL \"net/url\"\n\t\"regexp\"\n\t\"str"
},
{
"path": "extractors/douyin/douyin_test.go",
"chars": 728,
"preview": "package douyin\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestD"
},
{
"path": "extractors/douyin/sign.js",
"chars": 50688,
"preview": "var window = null;\n\nfunction _0x5cd844(e) {\n var b = {\n exports: {}\n };\n return e(b, b.exports), b.expor"
},
{
"path": "extractors/douyin/types.go",
"chars": 21693,
"preview": "package douyin\n\ntype douyinData struct {\n\tStatusCode int `json:\"status_code\"`\n\tAwemeDetail struct {\n\t\tAdmireAuth struct"
},
{
"path": "extractors/douyu/douyu.go",
"chars": 2631,
"preview": "package douyu\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/i"
},
{
"path": "extractors/douyu/douyu_test.go",
"chars": 523,
"preview": "package douyu\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDo"
},
{
"path": "extractors/eporner/eporner.go",
"chars": 3004,
"preview": "package eporner\n\nimport (\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/pkg/errors\"\n\n"
},
{
"path": "extractors/eporner/eporner_test.go",
"chars": 658,
"preview": "package eporner\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "extractors/errors.go",
"chars": 380,
"preview": "package extractors\n\nimport (\n\t\"errors\"\n)\n\nvar (\n\t// ErrURLParseFailed defines url parse failed error.\n\tErrURLParseFailed"
},
{
"path": "extractors/extractors.go",
"chars": 1369,
"preview": "package extractors\n\nimport (\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/utils\"\n)"
},
{
"path": "extractors/facebook/facebook.go",
"chars": 1797,
"preview": "package facebook\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"githu"
},
{
"path": "extractors/facebook/facebook_test.go",
"chars": 665,
"preview": "package facebook\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Tes"
},
{
"path": "extractors/geekbang/geekbang.go",
"chars": 4436,
"preview": "package geekbang\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia0"
},
{
"path": "extractors/geekbang/geekbang_test.go",
"chars": 588,
"preview": "package geekbang\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Tes"
},
{
"path": "extractors/haokan/haokan.go",
"chars": 1751,
"preview": "package haokan\n\nimport (\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia0"
},
{
"path": "extractors/haokan/haokan_test.go",
"chars": 592,
"preview": "package haokan\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestD"
},
{
"path": "extractors/hupu/hupu.go",
"chars": 1471,
"preview": "package hupu\n\nimport (\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/request"
},
{
"path": "extractors/hupu/hupu_test.go",
"chars": 598,
"preview": "package hupu\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestHup"
},
{
"path": "extractors/huya/huya.go",
"chars": 1510,
"preview": "package huya\n\nimport (\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/request"
},
{
"path": "extractors/huya/huya_test.go",
"chars": 568,
"preview": "package huya\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestHuy"
},
{
"path": "extractors/instagram/instagram.go",
"chars": 4811,
"preview": "package instagram\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\tnetURL \"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t"
},
{
"path": "extractors/instagram/instagram_test.go",
"chars": 916,
"preview": "package instagram\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Te"
},
{
"path": "extractors/iqiyi/iqiyi.go",
"chars": 5633,
"preview": "package iqiyi\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pkg/errors\"\n\n\t\""
},
{
"path": "extractors/iqiyi/iqiyi_test.go",
"chars": 1073,
"preview": "package iqiyi\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDo"
},
{
"path": "extractors/ixigua/ixigua.go",
"chars": 3140,
"preview": "package ixigua\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\n\tbrowser \"github.c"
},
{
"path": "extractors/ixigua/ixigua_test.go",
"chars": 995,
"preview": "package ixigua\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestD"
},
{
"path": "extractors/ixigua/types.go",
"chars": 1154,
"preview": "package ixigua\n\ntype xiguanData struct {\n\tAnyVideo struct {\n\t\tGidInformation struct {\n\t\t\tGid string `json:\"gid\"`\n"
},
{
"path": "extractors/kuaishou/kuaishou.go",
"chars": 2468,
"preview": "package kuaishou\n\nimport (\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extract"
},
{
"path": "extractors/kuaishou/kuaishou_test.go",
"chars": 706,
"preview": "package kuaishou\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Tes"
},
{
"path": "extractors/mgtv/mgtv.go",
"chars": 4967,
"preview": "package mgtv\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/"
},
{
"path": "extractors/mgtv/mgtv_test.go",
"chars": 929,
"preview": "package mgtv\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDow"
},
{
"path": "extractors/miaopai/miaopai.go",
"chars": 2374,
"preview": "package miaopai\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia0"
},
{
"path": "extractors/miaopai/miaopai_test.go",
"chars": 592,
"preview": "package miaopai\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "extractors/netease/netease.go",
"chars": 1803,
"preview": "package netease\n\nimport (\n\tnetURL \"net/url\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n"
},
{
"path": "extractors/netease/netease_test.go",
"chars": 759,
"preview": "package netease\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "extractors/odysee/odysee.go",
"chars": 2080,
"preview": "package odysee\n\nimport (\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\n\t\"github.com/i"
},
{
"path": "extractors/odysee/odysee_test.go",
"chars": 828,
"preview": "package odysee\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestD"
},
{
"path": "extractors/pinterest/pinterest.go",
"chars": 2027,
"preview": "package pinterest\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"gith"
},
{
"path": "extractors/pinterest/pinterest_test.go",
"chars": 834,
"preview": "package pinterest\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Te"
},
{
"path": "extractors/pixivision/pixivision.go",
"chars": 1382,
"preview": "package pixivision\n\nimport (\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/p"
},
{
"path": "extractors/pixivision/pixivision_test.go",
"chars": 587,
"preview": "package pixivision\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc T"
},
{
"path": "extractors/pornhub/pornhub.go",
"chars": 4046,
"preview": "package pornhub\n\nimport (\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strin"
},
{
"path": "extractors/pornhub/pornhub_test.go",
"chars": 522,
"preview": "package pornhub\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "extractors/qq/qq.go",
"chars": 5595,
"preview": "package qq\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iaw"
},
{
"path": "extractors/qq/qq_test.go",
"chars": 1066,
"preview": "package qq\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDownl"
},
{
"path": "extractors/reddit/reddit.go",
"chars": 4224,
"preview": "package reddit\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com"
},
{
"path": "extractors/reddit/reddit_test.go",
"chars": 1682,
"preview": "package reddit\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestR"
},
{
"path": "extractors/rumble/rumble.go",
"chars": 8578,
"preview": "package rumble\n\nimport (\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\""
},
{
"path": "extractors/rumble/rumble_test.go",
"chars": 1017,
"preview": "package rumble\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestR"
},
{
"path": "extractors/streamtape/streamtape.go",
"chars": 1973,
"preview": "package streamtape\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/pkg/errors\"\n\t\"github.com/robertkrimen/otto\"\n\n\t\"github.com/iawia002/lux"
},
{
"path": "extractors/streamtape/streamtape_test.go",
"chars": 614,
"preview": "package streamtape\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc T"
},
{
"path": "extractors/tangdou/tangdou.go",
"chars": 2068,
"preview": "package tangdou\n\nimport (\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia"
},
{
"path": "extractors/tangdou/tangdou_test.go",
"chars": 978,
"preview": "package tangdou\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "extractors/threads/threads.go",
"chars": 3165,
"preview": "package threads\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\tnetURL \"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gocolly/colly/v2\""
},
{
"path": "extractors/threads/threads_test.go",
"chars": 1217,
"preview": "package threads_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/extractors/th"
},
{
"path": "extractors/tiktok/tiktok.go",
"chars": 2351,
"preview": "package tiktok\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github."
},
{
"path": "extractors/tiktok/tiktok_test.go",
"chars": 944,
"preview": "package tiktok\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestD"
},
{
"path": "extractors/tumblr/tumblr.go",
"chars": 3989,
"preview": "package tumblr\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\""
},
{
"path": "extractors/tumblr/tumblr_test.go",
"chars": 982,
"preview": "package tumblr\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestD"
},
{
"path": "extractors/twitter/twitter.go",
"chars": 3557,
"preview": "package twitter\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002"
},
{
"path": "extractors/twitter/twitter_test.go",
"chars": 1086,
"preview": "package twitter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "extractors/types.go",
"chars": 3690,
"preview": "package extractors\n\n// Part is the data structure for a single part of the video stream information.\ntype Part struct {\n"
},
{
"path": "extractors/udn/udn.go",
"chars": 2093,
"preview": "package udn\n\nimport (\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/"
},
{
"path": "extractors/udn/udn_test.go",
"chars": 567,
"preview": "package udn\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestExtr"
},
{
"path": "extractors/universal/universal.go",
"chars": 1145,
"preview": "package universal\n\nimport (\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/re"
},
{
"path": "extractors/universal/universal_test.go",
"chars": 634,
"preview": "package universal\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Te"
},
{
"path": "extractors/vimeo/vimeo.go",
"chars": 2422,
"preview": "package vimeo\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"g"
},
{
"path": "extractors/vimeo/vimeo_test.go",
"chars": 706,
"preview": "package vimeo\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDo"
},
{
"path": "extractors/vk/vk.go",
"chars": 2611,
"preview": "package vk\n\nimport (\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/config\"\n\t\"github.com/iawia002/lux/e"
},
{
"path": "extractors/vk/vk_test.go",
"chars": 589,
"preview": "package vk\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestVK(t "
},
{
"path": "extractors/weibo/weibo.go",
"chars": 7504,
"preview": "package weibo\n\nimport (\n\t\"compress/gzip\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\tnetURL \"net/url\"\n\t\"strconv\"\n\t\"string"
},
{
"path": "extractors/weibo/weibo_test.go",
"chars": 1356,
"preview": "package weibo\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestTo"
},
{
"path": "extractors/xiaohongshu/xiaohongshu.go",
"chars": 2259,
"preview": "package xiaohongshu\n\nimport (\n\t\"encoding/json\"\n\tneturl \"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"gith"
},
{
"path": "extractors/xiaohongshu/xiaohongshu_test.go",
"chars": 603,
"preview": "package xiaohongshu\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc "
},
{
"path": "extractors/ximalaya/types.go",
"chars": 335,
"preview": "package ximalaya\n\ntype ximalayaData struct {\n\tStatusCode int `json:\"ret\"`\n\tData struct {\n\t\tTrackId int "
},
{
"path": "extractors/ximalaya/ximalaya.go",
"chars": 2001,
"preview": "package ximalaya\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.co"
},
{
"path": "extractors/ximalaya/ximalaya_test.go",
"chars": 566,
"preview": "package ximalaya\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Tes"
},
{
"path": "extractors/xinpianchang/xinpianchang.go",
"chars": 2654,
"preview": "package xinpianchang\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/itchyny/gojq\"\n\t\"github.com/pkg"
},
{
"path": "extractors/xinpianchang/xinpianchang_test.go",
"chars": 620,
"preview": "package xinpianchang\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc"
},
{
"path": "extractors/xvideos/xvideos.go",
"chars": 2579,
"preview": "package xvideos\n\nimport (\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.c"
},
{
"path": "extractors/xvideos/xvideos_test.go",
"chars": 695,
"preview": "package xvideos\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "extractors/yinyuetai/types.go",
"chars": 1011,
"preview": "package yinyuetai\n\ntype yinyuetaiMvData struct {\n\tError bool `json:\"error\"`\n\tMessage string `json:\"message"
},
{
"path": "extractors/yinyuetai/yinyuetai.go",
"chars": 2295,
"preview": "package yinyuetai\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"g"
},
{
"path": "extractors/yinyuetai/yinyuetai_test.go",
"chars": 523,
"preview": "package yinyuetai\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Te"
},
{
"path": "extractors/youku/youku.go",
"chars": 6366,
"preview": "package youku\n\nimport (\n\t\"bytes\"\n\t\"crypto/hmac\"\n\t\"crypto/sha1\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\""
},
{
"path": "extractors/youku/youku_test.go",
"chars": 584,
"preview": "package youku\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDo"
},
{
"path": "extractors/youtube/youtube.go",
"chars": 5053,
"preview": "package youtube\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/kkdai/youtube/v2\"\n\t\"github.co"
},
{
"path": "extractors/youtube/youtube_test.go",
"chars": 1570,
"preview": "package youtube\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "extractors/zhihu/types.go",
"chars": 326,
"preview": "package zhihu\n\n// minimum field\ntype video struct {\n\tPlayList struct {\n\t\tFHD resolution `json:\"FHD\"`\n\t\tHD resolution `j"
},
{
"path": "extractors/zhihu/zhihu.go",
"chars": 1819,
"preview": "package zhihu\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/iawia002/lux/extracto"
},
{
"path": "extractors/zhihu/zhihu_test.go",
"chars": 570,
"preview": "package zhihu\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc TestDo"
},
{
"path": "extractors/zingmp3/zingmp3.go",
"chars": 5767,
"preview": "package zingmp3\n\nimport (\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"maps\"\n\t\"net/http\"\n\tn"
},
{
"path": "extractors/zingmp3/zingmp3_test.go",
"chars": 900,
"preview": "package zingmp3\n\nimport (\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n\t\"github.com/iawia002/lux/test\"\n)\n\nfunc Test"
},
{
"path": "go.mod",
"chars": 2489,
"preview": "module github.com/iawia002/lux\n\ngo 1.24\n\nrequire (\n\tgithub.com/EDDYCJY/fake-useragent v0.2.0\n\tgithub.com/MercuryEngineer"
},
{
"path": "go.sum",
"chars": 18452,
"preview": "github.com/EDDYCJY/fake-useragent v0.2.0 h1:Jcnkk2bgXmDpX0z+ELlUErTkoLb/mxFBNd2YdcpvJBs=\ngithub.com/EDDYCJY/fake-userage"
},
{
"path": "main.go",
"chars": 305,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/fatih/color\"\n\n\t\"github.com/iawia002/lux/app\"\n)\n\nfunc main() {\n\tif err "
},
{
"path": "parser/parser.go",
"chars": 1382,
"preview": "package parser\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/PuerkitoBio/goquery\"\n\t\"github.com/pkg/errors\"\n)\n\n// GetDoc retu"
},
{
"path": "parser/parser_test.go",
"chars": 2178,
"preview": "package parser\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestGetDoc(t *testing.T) {\n\ttype args struct {\n\t\thtml string\n\t}\n\t"
},
{
"path": "request/request.go",
"chars": 4909,
"preview": "package request\n\nimport (\n\t\"compress/flate\"\n\t\"compress/gzip\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/cookiejar"
},
{
"path": "request/request_test.go",
"chars": 2370,
"preview": "package request\n\nimport (\n\t\"testing\"\n)\n\nfunc TestGet(t *testing.T) {\n\tvar err error\n\ttype args struct {\n\t\turl string"
},
{
"path": "script/generate_github_action_template.js",
"chars": 808,
"preview": "const path = require(\"path\");\nconst fs = require(\"fs\");\n\nconst extractorDir = path.join(__dirname, \"..\", \"extractors\");\n"
},
{
"path": "script/github_action_template.yml",
"chars": 727,
"preview": "name: {{module}}\n\non:\n push:\n paths:\n - \"extractors/{{module}}/*.go\"\n - \".github/workflows/stream_{{module"
},
{
"path": "test/utils.go",
"chars": 1356,
"preview": "package test\n\nimport (\n\t\"fmt\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/iawia002/lux/extractors\"\n)\n\n// Args Arguments for extract"
},
{
"path": "utils/download.go",
"chars": 843,
"preview": "package utils\n\nimport (\n\t\"strconv\"\n\t\"strings\"\n)\n\n// NeedDownloadList return the indices of playlist that need download\nf"
},
{
"path": "utils/download_test.go",
"chars": 1471,
"preview": "package utils\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestNeedDownloadList(t *testing.T) {\n\ttype args struct {\n\t\tlen int"
},
{
"path": "utils/ffmpeg.go",
"chars": 4040,
"preview": "package utils\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\t\"github.com/pkg/errors"
},
{
"path": "utils/pool.go",
"chars": 889,
"preview": "package utils\n\nimport (\n\t\"math\"\n\t\"sync\"\n)\n\n// WaitGroupPool pool of WaitGroup\ntype WaitGroupPool struct {\n\tpool chan str"
},
{
"path": "utils/pool_test.go",
"chars": 384,
"preview": "package utils\n\nimport (\n\t\"sync/atomic\"\n\t\"testing\"\n)\n\nfunc TestWaitGroupPool(t *testing.T) {\n\twgp := NewWaitGroupPool(10)"
},
{
"path": "utils/utils.go",
"chars": 7266,
"preview": "package utils\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"crypto/md5\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n\t\"os\"\n\t\"path/filepath\"\n\t"
},
{
"path": "utils/utils_test.go",
"chars": 10105,
"preview": "package utils\n\nimport (\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestMatchOneOf(t *testing.T) {\n\ttype args struct {\n\t\tpattern"
}
]
About this extraction
This page contains the full source code of the iawia002/lux GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 186 files (449.2 KB), approximately 156.3k tokens, and a symbol index with 554 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.