Compare commits
6 commits
main
...
feat/oauth
Author | SHA1 | Date | |
---|---|---|---|
4e5fb1d898 | |||
a9b33fb882 | |||
5459b88660 | |||
e457733244 | |||
e5ca368d3f | |||
d2df0f2b06 |
250 changed files with 17216 additions and 270536 deletions
|
@ -24,41 +24,26 @@ jobs:
|
|||
with:
|
||||
cache-on-failure: "true"
|
||||
|
||||
- name: Download rustypipe-botguard
|
||||
run: |
|
||||
TARGET=$(rustc --version --verbose | grep "host:" | sed -e 's/^host: //')
|
||||
cd ~
|
||||
curl -SsL -o rustypipe-botguard.tar.xz "https://codeberg.org/ThetaDev/rustypipe-botguard/releases/download/v0.1.1/rustypipe-botguard-v0.1.1-${TARGET}.tar.xz"
|
||||
cd /usr/local/bin
|
||||
sudo tar -xJf ~/rustypipe-botguard.tar.xz
|
||||
rm ~/rustypipe-botguard.tar.xz
|
||||
rustypipe-botguard --version
|
||||
|
||||
- name: 📎 Clippy
|
||||
run: |
|
||||
cargo clippy --all --tests --features=rss,userdata,indicatif,audiotag -- -D warnings
|
||||
cargo clippy --package=rustypipe --tests -- -D warnings
|
||||
cargo clippy --package=rustypipe-downloader -- -D warnings
|
||||
cargo clippy --package=rustypipe-cli -- -D warnings
|
||||
cargo clippy --package=rustypipe-cli --features=timezone -- -D warnings
|
||||
run: cargo clippy --all --tests --features=rss,indicatif,audiotag -- -D warnings
|
||||
|
||||
- name: 🧪 Test
|
||||
run: cargo nextest run --config-file ~/.config/nextest.toml --profile ci --retries 2 --features rss,userdata --workspace -- --skip 'user_data::'
|
||||
run: |
|
||||
printf "$RUSTYPIPE_CACHE" > rustypipe_cache.json
|
||||
ls
|
||||
head -n 1 rustypipe_cache.json
|
||||
RUST_LOG=debug cargo test --package rustypipe --test youtube --features rss -- user_login --exact --nocapture
|
||||
cargo nextest run --config-file ~/.config/nextest.toml --profile ci --retries 2 --features rss --workspace
|
||||
env:
|
||||
ALL_PROXY: "http://warpproxy:8124"
|
||||
|
||||
- name: Move test report
|
||||
if: always()
|
||||
run: mv target/nextest/ci/junit.xml junit.xml || true
|
||||
RUSTYPIPE_CACHE: "${{ secrets.RUSTYPIPE_CACHE }}"
|
||||
|
||||
- name: 💌 Upload test report
|
||||
if: always()
|
||||
uses: https://code.forgejo.org/forgejo/upload-artifact@v4
|
||||
with:
|
||||
name: test
|
||||
path: |
|
||||
junit.xml
|
||||
rustypipe_reports
|
||||
path: target/nextest/ci/junit.xml
|
||||
|
||||
- name: 🔗 Artifactview PR comment
|
||||
if: ${{ always() && github.event_name == 'pull_request' }}
|
||||
|
|
|
@ -1,69 +0,0 @@
|
|||
name: Release CLI
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "rustypipe-cli/v*.*.*"
|
||||
|
||||
jobs:
|
||||
Release:
|
||||
runs-on: cimaster-latest
|
||||
steps:
|
||||
- name: 📦 Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup cross compilation
|
||||
run: |
|
||||
rustup target add x86_64-pc-windows-msvc x86_64-apple-darwin aarch64-apple-darwin
|
||||
cargo install cargo-xwin
|
||||
|
||||
# https://wapl.es/rust/2019/02/17/rust-cross-compile-linux-to-macos.html/
|
||||
sudo apt-get install -y llvm clang cmake
|
||||
cd ~
|
||||
git clone https://github.com/tpoechtrager/osxcross
|
||||
cd osxcross
|
||||
wget -nc "https://github.com/joseluisq/macosx-sdks/releases/download/12.3/MacOSX12.3.sdk.tar.xz"
|
||||
mv MacOSX12.3.sdk.tar.xz tarballs/
|
||||
UNATTENDED=yes OSX_VERSION_MIN=12.3 ./build.sh
|
||||
OSXCROSS_BIN="$(pwd)/target/bin"
|
||||
|
||||
echo "CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER=$(find "$OSXCROSS_BIN" -name "x86_64-apple-darwin*-clang")" >> $GITHUB_ENV
|
||||
echo "CARGO_TARGET_X86_64_APPLE_DARWIN_RUSTFLAGS=-Car=$(find "$OSXCROSS_BIN" -name "x86_64-apple-darwin*-ar"),-Clink-arg=-undefined,-Clink-arg=dynamic_lookup" >> $GITHUB_ENV
|
||||
echo "CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=$(find "$OSXCROSS_BIN" -name "aarch64-apple-darwin*-clang")" >> $GITHUB_ENV
|
||||
echo "CARGO_TARGET_AARCH64_APPLE_DARWIN_RUSTFLAGS=-Car=$(find "$OSXCROSS_BIN" -name "aarch64-apple-darwin*-ar"),-Clink-arg=-undefined,-Clink-arg=dynamic_lookup" >> $GITHUB_ENV
|
||||
|
||||
- name: ⚒️ Build application
|
||||
run: |
|
||||
export PATH="$PATH:$HOME/osxcross/target/bin"
|
||||
CRATE="rustypipe-cli"
|
||||
PKG_CONFIG_SYSROOT_DIR=/usr/x86_64-linux-gnu cargo build --release --package=$CRATE --target x86_64-unknown-linux-gnu
|
||||
PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu cargo build --release --package=$CRATE --target aarch64-unknown-linux-gnu
|
||||
CC="$CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER" CXX="$CARGO_TARGET_X86_64_APPLE_DARWIN_LINKER++" cargo build --release --package=$CRATE --target x86_64-apple-darwin
|
||||
CC="$CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER" CXX="$CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER++" cargo build --release --package=$CRATE --target aarch64-apple-darwin
|
||||
cargo xwin build --release --package=$CRATE --target x86_64-pc-windows-msvc
|
||||
|
||||
- name: Prepare release
|
||||
run: |
|
||||
CRATE="rustypipe-cli"
|
||||
BIN="rustypipe"
|
||||
echo "CRATE=$CRATE" >> "$GITHUB_ENV"
|
||||
echo "CRATE_VERSION=$(echo '${{ github.ref_name }}' | awk 'BEGIN{RS="/"} NR==2{print}')" >> "$GITHUB_ENV"
|
||||
CL_PATH="cli/CHANGELOG.md"
|
||||
{
|
||||
echo 'CHANGELOG<<END_OF_FILE'
|
||||
awk 'BEGIN{RS="(^|\n)## [^\n]+\n*"} NR==2 { print }' "$CL_PATH"
|
||||
echo END_OF_FILE
|
||||
} >> "$GITHUB_ENV"
|
||||
|
||||
mkdir dist
|
||||
|
||||
for arch in x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu x86_64-apple-darwin aarch64-apple-darwin; do
|
||||
tar -cJf "dist/${BIN}-${CRATE_VERSION}-${arch}.tar.xz" -C target/${arch}/release "${BIN}"
|
||||
done
|
||||
(cd target/x86_64-pc-windows-msvc/release && zip -9 "../../../dist/${BIN}-${CRATE_VERSION}-x86_64-pc-windows-msvc.zip" "${BIN}.exe")
|
||||
|
||||
- name: 🎉 Publish release
|
||||
uses: https://gitea.com/actions/release-action@main
|
||||
with:
|
||||
title: "${{ env.CRATE }} ${{ env.CRATE_VERSION }}"
|
||||
body: "${{ env.CHANGELOG }}"
|
||||
files: dist/*
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
renovate:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: renovate/renovate:39
|
||||
image: renovate/renovate:latest
|
||||
|
||||
steps:
|
||||
- name: Load renovate repo cache
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -4,5 +4,4 @@
|
|||
*.snap.new
|
||||
|
||||
rustypipe_reports
|
||||
rustypipe_cache*.json
|
||||
bg_snapshot.bin
|
||||
rustypipe_cache.json
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: end-of-file-fixer
|
||||
- id: check-json
|
||||
|
@ -10,8 +10,4 @@ repos:
|
|||
hooks:
|
||||
- id: cargo-fmt
|
||||
- id: cargo-clippy
|
||||
name: cargo-clippy rustypipe
|
||||
args: ["--package=rustypipe", "--tests", "--", "-D", "warnings"]
|
||||
- id: cargo-clippy
|
||||
name: cargo-clippy workspace
|
||||
args: ["--all", "--tests", "--features=rss,userdata,indicatif,audiotag", "--", "-D", "warnings"]
|
||||
args: ["--all", "--tests", "--features=rss,indicatif,audiotag", "--", "-D", "warnings"]
|
||||
|
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.cargo.features": ["rss", "indicatif", "audiotag"]
|
||||
}
|
10
.woodpecker.yml
Normal file
10
.woodpecker.yml
Normal file
|
@ -0,0 +1,10 @@
|
|||
steps:
|
||||
test:
|
||||
image: rust:latest
|
||||
environment:
|
||||
- CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
|
||||
commands:
|
||||
- rustup component add rustfmt clippy
|
||||
- cargo fmt --all --check
|
||||
- cargo clippy --all --features=rss -- -D warnings
|
||||
- cargo test --features=rss --workspace
|
163
CHANGELOG.md
163
CHANGELOG.md
|
@ -3,169 +3,6 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
|
||||
|
||||
## [v0.10.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe/v0.9.0..rustypipe/v0.10.0) - 2025-02-09
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add visitor data cache, remove random visitor data - ([b12f4c5](https://codeberg.org/ThetaDev/rustypipe/commit/b12f4c5d821a9189d7ed8410ad860824b6d052ef))
|
||||
- Add support for rustypipe-botguard to get PO tokens - ([b90a252](https://codeberg.org/ThetaDev/rustypipe/commit/b90a252a5e1bf05a5294168b0ec16a73cbb88f42))
|
||||
- Add session po token cache - ([b72b501](https://codeberg.org/ThetaDev/rustypipe/commit/b72b501b6dbcf4333b24cd80e7c8c61b0c21ec91))
|
||||
- Check rustypipe-botguard-api version - ([8385b87](https://codeberg.org/ThetaDev/rustypipe/commit/8385b87c63677f32a240679a78702f53072e517a))
|
||||
- Rewrite request attempt system, retry with different visitor data - ([dfd03ed](https://codeberg.org/ThetaDev/rustypipe/commit/dfd03edfadff2657e9cfbf04e5d313ba409520ac))
|
||||
- Log failed player fetch attempts with player_from_clients - ([8e35358](https://codeberg.org/ThetaDev/rustypipe/commit/8e35358c8941301f6ebf7646a11ab22711082569))
|
||||
- Add timezone query option - ([3a2370b](https://codeberg.org/ThetaDev/rustypipe/commit/3a2370b97ca3d0f40d72d66a23295557317d29fb))
|
||||
- [**breaking**] Add userdata feature for all personal data queries (playback history, subscriptions) - ([65cb424](https://codeberg.org/ThetaDev/rustypipe/commit/65cb4244c6ab547f53d0cb12af802c4189188c86))
|
||||
- Add RustyPipe::version_botguard fn, detect rustypipe-botguard in current dir, add botguard version to report - ([1d755b7](https://codeberg.org/ThetaDev/rustypipe/commit/1d755b76bf4569f7d0bb90a65494ac8e7aae499a))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Parsing history dates - ([af7dc10](https://codeberg.org/ThetaDev/rustypipe/commit/af7dc1016322a87dd8fec0b739939c2b12b6f400))
|
||||
- A/V streams incorrectly recognized as video-only - ([2b891ca](https://codeberg.org/ThetaDev/rustypipe/commit/2b891ca0788f91f16dbb9203191cb3d2092ecc74))
|
||||
- Update iOS client - ([e915416](https://codeberg.org/ThetaDev/rustypipe/commit/e91541629d6c944c1001f5883e3c1264aeeb3969))
|
||||
- A/B test 20: music continuation item renderer - ([9c67f8f](https://codeberg.org/ThetaDev/rustypipe/commit/9c67f8f85bef8214848dc9d17bff6cff252e015e))
|
||||
- Include whole request body in report - ([15245c1](https://codeberg.org/ThetaDev/rustypipe/commit/15245c18b584e42523762b94fcc7284d483660a0))
|
||||
- Extracting nsig fn when outside variable starts with $ - ([eda16e3](https://codeberg.org/ThetaDev/rustypipe/commit/eda16e378730a3b57c4982a626df1622a93c574a))
|
||||
- Retry updating deobf data after a RustyPipe update - ([50ab1f7](https://codeberg.org/ThetaDev/rustypipe/commit/50ab1f7a5d8aeaa3720264b4a4b27805bb0e8121))
|
||||
- Allow player data to be fetched without botguard - ([29c854b](https://codeberg.org/ThetaDev/rustypipe/commit/29c854b20d7a6677415b1744e7ba7ecd4f594ea5))
|
||||
- Output full request body in reports, clean up `get_player_po_token` - ([a0d850f](https://codeberg.org/ThetaDev/rustypipe/commit/a0d850f8e01428a73bbd66397d0dbf797b45958f))
|
||||
- Correct timezone offset for parsed dates, add timezone_local option - ([a5a7be5](https://codeberg.org/ThetaDev/rustypipe/commit/a5a7be5b4e0a0b73d7e1dc802ebd7bd48dafc76d))
|
||||
- Use localzone crate to get local tz - ([5acbf0e](https://codeberg.org/ThetaDev/rustypipe/commit/5acbf0e456b1f10707e0a56125d993a8129eee3a))
|
||||
- Only use cached potokens with min. 10min lifetime - ([0c94267](https://codeberg.org/ThetaDev/rustypipe/commit/0c94267d0371b2b26c7b5c9abfa156d5cde2153e))
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Add Botguard info to README - ([9957add](https://codeberg.org/ThetaDev/rustypipe/commit/9957add2b5d6391b2c1869d2019fd7dd91b8cd41))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rust crate rquickjs to 0.9.0 (#33) - ([2c8ac41](https://codeberg.org/ThetaDev/rustypipe/commit/2c8ac410aa535d83f8bcc7181f81914b13bceb77))
|
||||
|
||||
|
||||
## [v0.9.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe/v0.8.0..rustypipe/v0.9.0) - 2025-01-16
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add functions to fetch a user's history and subscriptions - ([14e3995](https://codeberg.org/ThetaDev/rustypipe/commit/14e399594f97a1228a8c2991a14dd8745af1beb7))
|
||||
- Add history item dates, extend timeago parser - ([320a8c2](https://codeberg.org/ThetaDev/rustypipe/commit/320a8c2c24217ad5697f0424c4f994bbbe31f3aa))
|
||||
- Add session headers when using cookie auth - ([3c95b52](https://codeberg.org/ThetaDev/rustypipe/commit/3c95b52ceaf0df2d67ee0d2f2ac658f666f29836))
|
||||
- Add cookies.txt parser, add cookie auth + history cmds to CLI - ([cf498e4](https://codeberg.org/ThetaDev/rustypipe/commit/cf498e4a8f9318b0197bc3f0cbaf7043c53adb9d))
|
||||
- Add method to get saved_playlists - ([27f64fc](https://codeberg.org/ThetaDev/rustypipe/commit/27f64fc412e833d5bd19ad72913aae19358e98b9))
|
||||
- Extract player DRM data - ([2af4001](https://codeberg.org/ThetaDev/rustypipe/commit/2af4001c75f2ff4f7c891aa59ac22c2c6b7902a2))
|
||||
- Add Dolby audio codecs (ac-3, ec-3) - ([a7f8c78](https://codeberg.org/ThetaDev/rustypipe/commit/a7f8c789b1a34710274c4630e027ef868397aea2))
|
||||
- Add DRM and audio channel number filtering to StreamFilter - ([d5abee2](https://codeberg.org/ThetaDev/rustypipe/commit/d5abee275300ab1bc10fc8d6c35a4e3813fd2bd4))
|
||||
- Set cache file permissions to 600 - ([dee8a99](https://codeberg.org/ThetaDev/rustypipe/commit/dee8a99e7a8d071c987709a01f02ee8fecf2d776))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Dont leak authorization and cookie header in reports - ([75fce91](https://codeberg.org/ThetaDev/rustypipe/commit/75fce91353c02cd498f27d21b08261c23ea03d70))
|
||||
- Require new time crate version which added Month::length - ([ec7a195](https://codeberg.org/ThetaDev/rustypipe/commit/ec7a195c98f39346c4c8db875212c3843580450e))
|
||||
- Parsing numbers (it), dates (kn) - ([63f86b6](https://codeberg.org/ThetaDev/rustypipe/commit/63f86b6e186aa1d2dcaf7e9169ccebb2265e5905))
|
||||
- Accept user-specific playlist ids (LL, WL) - ([97c3f30](https://codeberg.org/ThetaDev/rustypipe/commit/97c3f30d180d3e62b7e19f22d191d7fd7614daca))
|
||||
- Only use auth-enabled clients for fetching player with auth option enabled - ([2b2b4af](https://codeberg.org/ThetaDev/rustypipe/commit/2b2b4af0b26cdd0d4bf2218d3f527abd88658abf))
|
||||
- A/B test 19: Music artist album groups reordered - ([5daad1b](https://codeberg.org/ThetaDev/rustypipe/commit/5daad1b700e8dcf1f3e803db1685f08f27794898))
|
||||
- Switch to rquickjs crate for deobfuscator - ([75c3746](https://codeberg.org/ThetaDev/rustypipe/commit/75c3746890f3428f3314b7b10c9ec816ad275836))
|
||||
- Player_from_clients method not send/sync - ([9c512c3](https://codeberg.org/ThetaDev/rustypipe/commit/9c512c3c4dbec0fc3b973536733d61ba61125a92))
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Update README - ([0432477](https://codeberg.org/ThetaDev/rustypipe/commit/0432477451ecd5f64145d65239c721f4e44826c0))
|
||||
- Fix README - ([11442df](https://codeberg.org/ThetaDev/rustypipe/commit/11442dfd369599396357f5b7a7a4268a7b537f57))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rust crate rstest to 0.24.0 (#20) - ([ab19034](https://codeberg.org/ThetaDev/rustypipe/commit/ab19034ab19baf090e83eada056559676ffdadce))
|
||||
- *(deps)* Update rust crate dirs to v6 (#24) - ([6a60425](https://codeberg.org/ThetaDev/rustypipe/commit/6a604252b1af7a9388db5dc170f737069cc31051))
|
||||
- Update pre-commit hooks - ([7cd9246](https://codeberg.org/ThetaDev/rustypipe/commit/7cd9246260493d7839018cb39a2dfb4dded8b343))
|
||||
|
||||
|
||||
## [v0.8.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe/v0.7.2..rustypipe/v0.8.0) - 2024-12-20
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Log warning when generating report - ([258f18a](https://codeberg.org/ThetaDev/rustypipe/commit/258f18a99d848ae7e6808beddad054037a3b3799))
|
||||
- Add auto-dubbed audio tracks, improved StreamFilter - ([1d1ae17](https://codeberg.org/ThetaDev/rustypipe/commit/1d1ae17ffc16724667d43142aa57abda2e6468e4))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Replace deprecated call to `time::util::days_in_year_month` - ([69ef6ae](https://codeberg.org/ThetaDev/rustypipe/commit/69ef6ae51e9b09a9b9c06057e717bf6f054c9803))
|
||||
- Nsig fn extra variable extraction - ([8014741](https://codeberg.org/ThetaDev/rustypipe/commit/80147413ee3190bb530f8f6b02738bcc787a6444))
|
||||
- Deobf function extraction, allow $ in variable names - ([8cadbc1](https://codeberg.org/ThetaDev/rustypipe/commit/8cadbc1a4c865d085e30249dba0f353472456a32))
|
||||
- Remove leading zero-width-space from comments, ensure space after links - ([162959c](https://codeberg.org/ThetaDev/rustypipe/commit/162959ca4513a03496776fae905b4bf20c79899c))
|
||||
- Update client versions, enable Opus audio with iOS client - ([1b60c97](https://codeberg.org/ThetaDev/rustypipe/commit/1b60c97a183b9d74b92df14b5b113c61aba1be7f))
|
||||
- Extract transcript from comment voice replies - ([30f60c3](https://codeberg.org/ThetaDev/rustypipe/commit/30f60c30f9d87d39585db93c1c9e274f48d688ba))
|
||||
- Error 400 when fetching player with login - ([5ce84c4](https://codeberg.org/ThetaDev/rustypipe/commit/5ce84c44a6844f692258066c83e04df875e0aa91))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Update user agent - ([53e5846](https://codeberg.org/ThetaDev/rustypipe/commit/53e5846286e8db920622152c2a0a57ddc7c41d25))
|
||||
|
||||
|
||||
## [v0.7.2](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe/v0.7.1..rustypipe/v0.7.2) - 2024-12-13
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Replace futures dependency with futures-util - ([5c39bf4](https://codeberg.org/ThetaDev/rustypipe/commit/5c39bf4842b13d37a4277ea5506e15c179892ce5))
|
||||
- Lifetime-related lints - ([c4feff3](https://codeberg.org/ThetaDev/rustypipe/commit/c4feff37a5989097b575c43d89c26427d92d77b9))
|
||||
- Limit retry attempts to fetch client versions and deobf data - ([44ae456](https://codeberg.org/ThetaDev/rustypipe/commit/44ae456d2c654679837da8ec44932c44b1b01195))
|
||||
- Deobfuscation function extraction - ([f5437aa](https://codeberg.org/ThetaDev/rustypipe/commit/f5437aa127b2b7c5a08839643e30ea1ec989d30b))
|
||||
|
||||
|
||||
## [v0.7.1](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe/v0.7.0..rustypipe/v0.7.1) - 2024-11-25
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Disable Android client - ([a846b72](https://codeberg.org/ThetaDev/rustypipe/commit/a846b729e3519e3d5e62bdf028d9b48a7f8ea2ce))
|
||||
- A/B test 18: music playlist facepile avatar model - ([6c8108c](https://codeberg.org/ThetaDev/rustypipe/commit/6c8108c94acf9ca2336381bdca7c97b24a809521))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Add docs badge to README - ([706e881](https://codeberg.org/ThetaDev/rustypipe/commit/706e88134c0e94ce7d880735e9d31b3ff531a4f9))
|
||||
|
||||
|
||||
## [v0.7.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe/v0.6.0..rustypipe/v0.7.0) - 2024-11-10
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Allow searching for YTM users - ([50010b7](https://codeberg.org/ThetaDev/rustypipe/commit/50010b7b0856d3ce05fe7a9d5989e526089bc2ef))
|
||||
- [**breaking**] Replace `TrackItem::is_video` attr with TrackType enum; serde lowercase AlbumType enum for consistency - ([044094a](https://codeberg.org/ThetaDev/rustypipe/commit/044094a4b70f05c46a459fa1597e23f4224b7b0b))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Fetch unlocalized player data to interpret errors correctly; regression introduced with v0.6.0 - ([0919cbd](https://codeberg.org/ThetaDev/rustypipe/commit/0919cbd0dfe28ea00610c67a694e5f319e80635f))
|
||||
- A/B test 17: channel playlists lockupViewModel - ([342119d](https://codeberg.org/ThetaDev/rustypipe/commit/342119dba6f3dc2152eef1fc9841264a9e56b9f0))
|
||||
- [**breaking**] Serde: lowercase Verification enum - ([badb3ae](https://codeberg.org/ThetaDev/rustypipe/commit/badb3aef8249315909160b8ff73df3019f07cf97))
|
||||
- Parsing videos using LockupViewModel (Music video recommendations) - ([870ff79](https://codeberg.org/ThetaDev/rustypipe/commit/870ff79ee07dfab1f4f2be3a401cd5320ed587da))
|
||||
- Parsing lockup playlists with "MIX" instead of view count - ([ac8fbc3](https://codeberg.org/ThetaDev/rustypipe/commit/ac8fbc3e679819189e2791c323975acaf1b43035))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rust crate thiserror to v2 (#16) - ([e1e1687](https://codeberg.org/ThetaDev/rustypipe/commit/e1e1687605603686ac5fd5deeb6aa8fecaf92494))
|
||||
|
||||
|
||||
## [v0.6.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe/v0.5.0..rustypipe/v0.6.0) - 2024-10-28
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- [**breaking**] Remove TvHtml5Embed client as it got disabled - ([9e835c8](https://codeberg.org/ThetaDev/rustypipe/commit/9e835c8f38a3dd28c65561b2f9bb7a0f530c24f1))
|
||||
- [**breaking**] Generate random visitorData, remove `RustyPipeQuery::get_context` and `YTContext<'a>` from public API - ([7c4f44d](https://codeberg.org/ThetaDev/rustypipe/commit/7c4f44d09c4d813efff9e7d1059ddacd226b9e9d))
|
||||
- Add OAuth user login to access age-restricted videos - ([1cc3f9a](https://codeberg.org/ThetaDev/rustypipe/commit/1cc3f9ad74908d33e247ba6243103bfc22540164))
|
||||
- Add user_auth_logout method - ([9e2fe61](https://codeberg.org/ThetaDev/rustypipe/commit/9e2fe61267846ce216e0c498d8fa9ee672e03cbf))
|
||||
- Revoke OAuth token when logging out - ([62f8a92](https://codeberg.org/ThetaDev/rustypipe/commit/62f8a9210c23e1f02c711a2294af8766ca6b70e2))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Skip serializing empty cache entries - ([be18d89](https://codeberg.org/ThetaDev/rustypipe/commit/be18d89ea65e35ddcf0f31bea3360e5db209fb9f))
|
||||
- Fetch artist albums continuation - ([b589061](https://codeberg.org/ThetaDev/rustypipe/commit/b589061a40245637b4fe619a26892291d87d25e6))
|
||||
- Update channel order tokens - ([79a6281](https://codeberg.org/ThetaDev/rustypipe/commit/79a62816ff62d94e5c706f45b1ce5971e5e58a81))
|
||||
- Handle auth errors - ([512223f](https://codeberg.org/ThetaDev/rustypipe/commit/512223fd83fb1ba2ba7ad96ed050a70bb7ec294d))
|
||||
- Use same visitor data for fetching artist album continuations - ([7b0499f](https://codeberg.org/ThetaDev/rustypipe/commit/7b0499f6b7cbf6ac4b83695adadfebb3f30349c7))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rust crate fancy-regex to 0.14.0 (#14) - ([94194e0](https://codeberg.org/ThetaDev/rustypipe/commit/94194e019c46ca49c343086e80e8eb75c52f4bc6))
|
||||
- *(deps)* Update rust crate quick-xml to 0.37.0 (#15) - ([0662b5c](https://codeberg.org/ThetaDev/rustypipe/commit/0662b5ccfccc922b28629f11ea52c3eb35f9efd2))
|
||||
|
||||
|
||||
## [v0.5.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe/v0.4.0..rustypipe/v0.5.0) - 2024-10-13
|
||||
|
||||
### 🚀 Features
|
||||
|
|
44
Cargo.toml
44
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "rustypipe"
|
||||
version = "0.10.0"
|
||||
version = "0.5.0"
|
||||
rust-version = "1.67.1"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
@ -24,11 +24,13 @@ keywords = ["youtube", "video", "music"]
|
|||
categories = ["api-bindings", "multimedia"]
|
||||
|
||||
[workspace.dependencies]
|
||||
rquickjs = "0.9.0"
|
||||
quick-js-dtp = { version = "0.4.1", default-features = false, features = [
|
||||
"patch-dateparser",
|
||||
] }
|
||||
once_cell = "1.12.0"
|
||||
regex = "1.6.0"
|
||||
fancy-regex = "0.14.0"
|
||||
thiserror = "2.0.0"
|
||||
fancy-regex = "0.13.0"
|
||||
thiserror = "1.0.0"
|
||||
url = "2.2.0"
|
||||
reqwest = { version = "0.12.0", default-features = false }
|
||||
tokio = "1.20.4"
|
||||
|
@ -39,23 +41,20 @@ serde_with = { version = "3.0.0", default-features = false, features = [
|
|||
"macros",
|
||||
] }
|
||||
serde_plain = "1.0.0"
|
||||
sha1 = "0.10.0"
|
||||
rand = "0.8.0"
|
||||
time = { version = "0.3.37", features = [
|
||||
time = { version = "0.3.10", features = [
|
||||
"macros",
|
||||
"serde-human-readable",
|
||||
"serde-well-known",
|
||||
"local-offset",
|
||||
] }
|
||||
futures-util = "0.3.31"
|
||||
futures = "0.3.21"
|
||||
ress = "0.11.0"
|
||||
phf = "0.11.0"
|
||||
phf_codegen = "0.11.0"
|
||||
data-encoding = "2.0.0"
|
||||
base64 = "0.22.0"
|
||||
urlencoding = "2.1.0"
|
||||
quick-xml = { version = "0.37.0", features = ["serialize"] }
|
||||
quick-xml = { version = "0.36.0", features = ["serialize"] }
|
||||
tracing = { version = "0.1.0", features = ["log"] }
|
||||
localzone = "0.3.1"
|
||||
|
||||
# CLI
|
||||
indicatif = "0.17.0"
|
||||
|
@ -63,19 +62,19 @@ anyhow = "1.0"
|
|||
clap = { version = "4.0.0", features = ["derive"] }
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
serde_yaml = "0.9.0"
|
||||
dirs = "6.0.0"
|
||||
dirs = "5.0.0"
|
||||
filenamify = "0.1.0"
|
||||
|
||||
# Testing
|
||||
rstest = "0.24.0"
|
||||
rstest = "0.23.0"
|
||||
tokio-test = "0.4.2"
|
||||
insta = { version = "1.17.1", features = ["ron", "redactions"] }
|
||||
path_macro = "1.0.0"
|
||||
tracing-test = "0.2.5"
|
||||
|
||||
# Included crates
|
||||
rustypipe = { path = ".", version = "0.10.0", default-features = false }
|
||||
rustypipe-downloader = { path = "./downloader", version = "0.3.0", default-features = false, features = [
|
||||
rustypipe = { path = ".", version = "0.5.0", default-features = false }
|
||||
rustypipe-downloader = { path = "./downloader", version = "0.2.1", default-features = false, features = [
|
||||
"indicatif",
|
||||
"audiotag",
|
||||
] }
|
||||
|
@ -83,8 +82,7 @@ rustypipe-downloader = { path = "./downloader", version = "0.3.0", default-featu
|
|||
[features]
|
||||
default = ["default-tls"]
|
||||
|
||||
rss = ["dep:quick-xml"]
|
||||
userdata = []
|
||||
rss = ["quick-xml"]
|
||||
|
||||
# Reqwest TLS options
|
||||
default-tls = ["reqwest/default-tls"]
|
||||
|
@ -95,27 +93,25 @@ rustls-tls-webpki-roots = ["reqwest/rustls-tls-webpki-roots"]
|
|||
rustls-tls-native-roots = ["reqwest/rustls-tls-native-roots"]
|
||||
|
||||
[dependencies]
|
||||
rquickjs.workspace = true
|
||||
quick-js-dtp.workspace = true
|
||||
once_cell.workspace = true
|
||||
regex.workspace = true
|
||||
fancy-regex.workspace = true
|
||||
thiserror.workspace = true
|
||||
url.workspace = true
|
||||
reqwest = { workspace = true, features = ["json", "gzip", "brotli"] }
|
||||
tokio = { workspace = true, features = ["macros", "time", "process"] }
|
||||
tokio = { workspace = true, features = ["macros", "time"] }
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_with.workspace = true
|
||||
serde_plain.workspace = true
|
||||
sha1.workspace = true
|
||||
rand.workspace = true
|
||||
time.workspace = true
|
||||
ress.workspace = true
|
||||
phf.workspace = true
|
||||
data-encoding.workspace = true
|
||||
base64.workspace = true
|
||||
urlencoding.workspace = true
|
||||
tracing.workspace = true
|
||||
localzone.workspace = true
|
||||
quick-xml = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -127,6 +123,6 @@ tracing-test.workspace = true
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
# To build locally:
|
||||
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features rss,userdata --no-deps --open
|
||||
features = ["rss", "userdata"]
|
||||
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features rss --no-deps --open
|
||||
features = ["rss"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
## Development
|
||||
|
||||
**Requirements:**
|
||||
|
||||
- Current version of stable Rust
|
||||
- [`just`](https://github.com/casey/just) task runner
|
||||
- [`nextest`](https://nexte.st) test runner
|
||||
- [`pre-commit`](https://pre-commit.com/)
|
||||
- yq (YAML processor)
|
||||
|
||||
### Tasks
|
||||
|
||||
**Testing**
|
||||
|
||||
- `just test` Run unit+integration tests
|
||||
- `just unittest` Run unit tests
|
||||
- `just testyt` Run YouTube integration tests
|
||||
- `just testintl` Run YouTube integration tests for all supported languages (this takes
|
||||
a long time and is therefore not run in CI)
|
||||
- `YT_LANG=de just testyt` Run YouTube integration tests for a specific language
|
||||
|
||||
**Tools**
|
||||
|
||||
- `just testfiles` Download missing testfiles for unit tests
|
||||
- `just report2yaml` Convert RustyPipe reports into a more readable yaml format
|
||||
(requires `yq`)
|
17
Justfile
17
Justfile
|
@ -1,19 +1,15 @@
|
|||
test:
|
||||
# cargo test --features=rss,userdata
|
||||
cargo nextest run --workspace --features=rss,userdata --no-fail-fast --retries 1 -- --skip 'user_data::'
|
||||
# cargo test --features=rss
|
||||
cargo nextest run --workspace --features=rss --no-fail-fast --failure-output final --retries 1
|
||||
|
||||
unittest:
|
||||
cargo nextest run --features=rss,userdata --no-fail-fast --lib
|
||||
cargo nextest run --features=rss --no-fail-fast --failure-output final --lib
|
||||
|
||||
testyt:
|
||||
cargo nextest run --features=rss,userdata --no-fail-fast --retries 1 --test youtube -- --skip 'user_data::'
|
||||
|
||||
testyt-cookie:
|
||||
cargo nextest run --features=rss,userdata --no-fail-fast --retries 1 --test youtube
|
||||
cargo nextest run --features=rss --no-fail-fast --failure-output final --retries 1 --test youtube
|
||||
|
||||
testyt-localized:
|
||||
YT_LANG=th cargo nextest run --features=rss,userdata --no-fail-fast --retries 1 --test youtube -- \
|
||||
--skip 'user_data::' --skip 'search_suggestion' --skip 'isrc_search_languages'
|
||||
YT_LANG=th cargo nextest run --features=rss --no-fail-fast --failure-output final --retries 1 --test youtube
|
||||
|
||||
testintl:
|
||||
#!/usr/bin/env bash
|
||||
|
@ -32,8 +28,7 @@ testintl:
|
|||
for YT_LANG in "${LANGUAGES[@]}"; do
|
||||
echo "---TESTS FOR $YT_LANG ---"
|
||||
|
||||
if YT_LANG="$YT_LANG" cargo nextest run --no-fail-fast --retries 1 --test-threads 4 --test youtube -- \
|
||||
--skip 'user_data::' --skip 'search_suggestion' --skip 'isrc_search_languages' --skip 'resolve_'; then
|
||||
if YT_LANG="$YT_LANG" cargo nextest run --no-fail-fast --failure-output final --retries 1 --test-threads 4 --test youtube -E 'not test(/^resolve/)'; then
|
||||
echo "--- $YT_LANG COMPLETED ---"
|
||||
else
|
||||
echo "--- $YT_LANG FAILED ---"
|
||||
|
|
140
README.md
140
README.md
|
@ -1,8 +1,7 @@
|
|||
# 
|
||||
|
||||
[](https://crates.io/crates/rustypipe)
|
||||
[](https://opensource.org/licenses/GPL-3.0)
|
||||
[](https://docs.rs/rustypipe)
|
||||
[](http://opensource.org/licenses/GPL-3.0)
|
||||
[](https://codeberg.org/ThetaDev/rustypipe/actions/?workflow=ci.yaml)
|
||||
|
||||
RustyPipe is a fully featured Rust client for the public YouTube / YouTube Music API
|
||||
|
@ -21,8 +20,6 @@ RustyPipe is a fully featured Rust client for the public YouTube / YouTube Music
|
|||
- **Search suggestions**
|
||||
- **Trending**
|
||||
- **URL resolver**
|
||||
- **Subscriptions**
|
||||
- **Playback history**
|
||||
|
||||
### YouTube Music
|
||||
|
||||
|
@ -36,30 +33,9 @@ RustyPipe is a fully featured Rust client for the public YouTube / YouTube Music
|
|||
- **Moods/Genres**
|
||||
- **Charts**
|
||||
- **New** (albums, music videos)
|
||||
- **Saved items**
|
||||
- **Playback history**
|
||||
|
||||
## Getting started
|
||||
|
||||
The RustyPipe library works as follows: at first you have to instantiate a RustyPipe
|
||||
client. You can either create it with default options or use the `RustyPipe::builder()`
|
||||
to customize it.
|
||||
|
||||
For fetching data you have to start with a new RustyPipe query object (`rp.query()`).
|
||||
The query object holds options for an individual query (e.g. content language or
|
||||
country). You can adjust these options with setter methods. Finally call your query
|
||||
method to fetch the data you need.
|
||||
|
||||
All query methods are async, you need the tokio runtime to execute them.
|
||||
|
||||
```rust ignore
|
||||
let rp = RustyPipe::new();
|
||||
let rp = RustyPipe::builder().storage_dir("/app/data").build().unwrap();
|
||||
let channel = rp.query().lang(Language::De).channel_videos("UCl2mFZoRqjw_ELax4Yisf6w").await.unwrap();
|
||||
```
|
||||
|
||||
Here are a few examples to get you started:
|
||||
|
||||
### Cargo.toml
|
||||
|
||||
```toml
|
||||
|
@ -181,105 +157,29 @@ Subscribers: 1780000
|
|||
...
|
||||
```
|
||||
|
||||
## Crate features
|
||||
## Development
|
||||
|
||||
Some features of RustyPipe are gated behind features to avoid compiling unneeded
|
||||
dependencies.
|
||||
**Requirements:**
|
||||
|
||||
- `rss` Fetch a channel's RSS feed, which is faster than fetching the channel page
|
||||
- `userdata` Add functions to fetch YouTube user data (watch history, subscriptions,
|
||||
music library)
|
||||
- Current version of stable Rust
|
||||
- [`just`](https://github.com/casey/just) task runner
|
||||
- [`nextest`](https://nexte.st) test runner
|
||||
- [`pre-commit`](https://pre-commit.com/)
|
||||
- yq (YAML processor)
|
||||
|
||||
You can also choose the TLS library used for making web requests using the same features
|
||||
as the reqwest crate (`default-tls`, `native-tls`, `native-tls-alpn`,
|
||||
`native-tls-vendored`, `rustls-tls-webpki-roots`, `rustls-tls-native-roots`).
|
||||
### Tasks
|
||||
|
||||
## Cache storage
|
||||
**Testing**
|
||||
|
||||
The RustyPipe cache holds the current version numbers for all clients, the JavaScript
|
||||
code used to deobfuscate video URLs and the authentication token/cookies. Never share
|
||||
the contents of the cache if you are using authentication.
|
||||
- `just test` Run unit+integration tests
|
||||
- `just unittest` Run unit tests
|
||||
- `just testyt` Run YouTube integration tests
|
||||
- `just testintl` Run YouTube integration tests for all supported languages (this takes
|
||||
a long time and is therefore not run in CI)
|
||||
- `YT_LANG=de just testyt` Run YouTube integration tests for a specific language
|
||||
|
||||
By default the cache is written to a JSON file named `rustypipe_cache.json` in the
|
||||
current working directory. This path can be changed with the `storage_dir` option of the
|
||||
RustyPipeBuilder. The RustyPipe CLI stores its cache in the userdata folder. The full
|
||||
path on Linux is `~/.local/share/rustypipe/rustypipe_cache.json`.
|
||||
**Tools**
|
||||
|
||||
You can integrate your own cache storage backend (e.g. database storage) by implementing
|
||||
the `CacheStorage` trait.
|
||||
|
||||
## Reports
|
||||
|
||||
RustyPipe has a builtin error reporting system. If a YouTube response cannot be
|
||||
deserialized or parsed, the original response data along with some request metadata is
|
||||
written to a JSON file in the folder `rustypipe_reports`, located in RustyPipe's storage
|
||||
directory (current folder by default, `~/.local/share/rustypipe` for the CLI).
|
||||
|
||||
When submitting a bug report to the RustyPipe project, you can share this report to help
|
||||
resolve the issue.
|
||||
|
||||
RustyPipe reports come in 3 severity levels:
|
||||
|
||||
- DBG (no error occurred, report creation was enabled by the `RustyPipeQuery::report`
|
||||
query option)
|
||||
- WRN (parts of the response could not be deserialized/parsed, response data may be
|
||||
incomplete)
|
||||
- ERR (entire response could not be deserialized/parsed, RustyPipe returned an error)
|
||||
|
||||
## PO tokens
|
||||
|
||||
Since August 2024 YouTube requires PO tokens to access streams from web-based clients
|
||||
(Desktop, Mobile). Otherwise streams will return a 403 error.
|
||||
|
||||
Generating PO tokens requires a simulated browser environment, which would be too large
|
||||
to include in RustyPipe directly.
|
||||
|
||||
Therefore, the PO token generation is handled by a seperate CLI application
|
||||
([rustypipe-botguard](https://codeberg.org/ThetaDev/rustypipe-botguard)) which is called
|
||||
by the RustyPipe crate. RustyPipe automatically detects the rustypipe-botguard binary if
|
||||
it is located in PATH or the current working directory. If your rustypipe-botguard
|
||||
binary is located at a different path, you can specify it with the `.botguard_bin(path)`
|
||||
option.
|
||||
|
||||
## Authentication
|
||||
|
||||
RustyPipe supports authenticating with your YouTube account to access
|
||||
age-restricted/private videos and user information. There are 2 supported authentication
|
||||
methods: OAuth and cookies.
|
||||
|
||||
To execute a query with authentication, use the `.authenticated()` query option. This
|
||||
option is enabled by default for queries that always require authentication like
|
||||
fetching user data. RustyPipe may automatically use authentication in case a video is
|
||||
age-restricted or your IP address is banned by YouTube. If you never want to use
|
||||
authentication, set the `.unauthenticated()` query option.
|
||||
|
||||
### OAuth
|
||||
|
||||
OAuth is the authentication method used by the YouTube TV client. It is more
|
||||
user-friendly than extracting cookies, however it only works with the TV client. This
|
||||
means that you can only fetch videos and not access any user data.
|
||||
|
||||
To login using OAuth, you first have to get a new device code using the
|
||||
`rp.user_auth_get_code()` function. You can then enter the code on
|
||||
<https://google.com/device> and log in with your Google account. After generating the
|
||||
code, you can call the `rp.user_auth_wait_for_login()` function which waits until the
|
||||
user has logged in and stores the authentication token in the cache.
|
||||
|
||||
### Cookies
|
||||
|
||||
Authenticating with cookies allows you to use the functionality of the YouTube/YouTube
|
||||
Music Desktop client. You can fetch your subscribed channels, playlists and your music
|
||||
collection. You can also fetch videos using the Desktop client, including private
|
||||
videos, as long as you have access to them.
|
||||
|
||||
To authenticate with cookies you have to log into YouTube in a fresh browser session
|
||||
(open Incognito/Private mode). Then extract the cookies from the developer tools or by
|
||||
using browser plugins like "Get cookies.txt LOCALLY"
|
||||
([Firefox](https://addons.mozilla.org/de/firefox/addon/get-cookies-txt-locally/))
|
||||
([Chromium](https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc)).
|
||||
Close the browser window after extracting the cookies to prevent YouTube from rotating
|
||||
the cookies.
|
||||
|
||||
You can then add the cookies to your RustyPipe client using the `user_auth_set_cookie`
|
||||
or `user_auth_set_cookie_txt` function. The cookies are stored in the cache file. To log
|
||||
out, use the function `user_auth_remove_cookie`.
|
||||
- `just testfiles` Download missing testfiles for unit tests
|
||||
- `just report2yaml` Convert RustyPipe reports into a more readable yaml format
|
||||
(requires `yq`)
|
||||
|
|
|
@ -3,95 +3,6 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
|
||||
|
||||
## [v0.7.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-cli/v0.6.0..rustypipe-cli/v0.7.0) - 2025-02-09
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add support for rustypipe-botguard to get PO tokens - ([b90a252](https://codeberg.org/ThetaDev/rustypipe/commit/b90a252a5e1bf05a5294168b0ec16a73cbb88f42))
|
||||
- [**breaking**] Remove manual PO token options from downloader/cli, add new rustypipe-botguard options - ([cddb32f](https://codeberg.org/ThetaDev/rustypipe/commit/cddb32f190276265258c6ab45b3d43a8891c4b39))
|
||||
- Add session po token cache - ([b72b501](https://codeberg.org/ThetaDev/rustypipe/commit/b72b501b6dbcf4333b24cd80e7c8c61b0c21ec91))
|
||||
- Add timezone query option - ([3a2370b](https://codeberg.org/ThetaDev/rustypipe/commit/3a2370b97ca3d0f40d72d66a23295557317d29fb))
|
||||
- Add --timezone-local CLI option - ([4f2bb47](https://codeberg.org/ThetaDev/rustypipe/commit/4f2bb47ab42ae0c68a64f3b3c2831fa7850b6f56))
|
||||
- Add verbose flag - ([629b590](https://codeberg.org/ThetaDev/rustypipe/commit/629b5905da653c6fe0f3c6b5814dd2f49030e7ed))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Parsing mixed-case language codes like zh-CN - ([9c73ed4](https://codeberg.org/ThetaDev/rustypipe/commit/9c73ed4b3008cb093c0fa7fd94fd9f1ba8cd3627))
|
||||
|
||||
### 🚜 Refactor
|
||||
|
||||
- [**breaking**] Add client_type field to DownloadError, rename cli option po-token-cache to pot-cache - ([594e675](https://codeberg.org/ThetaDev/rustypipe/commit/594e675b39efc5fbcdbd5e920a4d2cdee64f718e))
|
||||
- Rename rustypipe-cli binary to rustypipe - ([c1a872e](https://codeberg.org/ThetaDev/rustypipe/commit/c1a872e1c14ea0956053bd7c65f6875b1cb3bc55))
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Add Botguard info to README - ([9957add](https://codeberg.org/ThetaDev/rustypipe/commit/9957add2b5d6391b2c1869d2019fd7dd91b8cd41))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rustypipe to 0.10.0
|
||||
- *(deps)* Update rust crate rquickjs to 0.9.0 (#33) - ([2c8ac41](https://codeberg.org/ThetaDev/rustypipe/commit/2c8ac410aa535d83f8bcc7181f81914b13bceb77))
|
||||
|
||||
|
||||
## [v0.6.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-cli/v0.5.0..rustypipe-cli/v0.6.0) - 2025-01-16
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Add functions to fetch a user's history and subscriptions - ([14e3995](https://codeberg.org/ThetaDev/rustypipe/commit/14e399594f97a1228a8c2991a14dd8745af1beb7))
|
||||
- Add history item dates, extend timeago parser - ([320a8c2](https://codeberg.org/ThetaDev/rustypipe/commit/320a8c2c24217ad5697f0424c4f994bbbe31f3aa))
|
||||
- Add cookies.txt parser, add cookie auth + history cmds to CLI - ([cf498e4](https://codeberg.org/ThetaDev/rustypipe/commit/cf498e4a8f9318b0197bc3f0cbaf7043c53adb9d))
|
||||
- Add CLI commands to fetch user library and YTM releases/charts - ([a1b43ad](https://codeberg.org/ThetaDev/rustypipe/commit/a1b43ad70a66cfcbaba8ef302ac8699f243e56e7))
|
||||
- Export subscriptions as OPML / NewPipe JSON - ([c90d966](https://codeberg.org/ThetaDev/rustypipe/commit/c90d966b17eab24e957d980695888a459707055c))
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Update README - ([0432477](https://codeberg.org/ThetaDev/rustypipe/commit/0432477451ecd5f64145d65239c721f4e44826c0))
|
||||
- Fix README - ([11442df](https://codeberg.org/ThetaDev/rustypipe/commit/11442dfd369599396357f5b7a7a4268a7b537f57))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rust crate rstest to 0.24.0 (#20) - ([ab19034](https://codeberg.org/ThetaDev/rustypipe/commit/ab19034ab19baf090e83eada056559676ffdadce))
|
||||
- *(deps)* Update rust crate dirs to v6 (#24) - ([6a60425](https://codeberg.org/ThetaDev/rustypipe/commit/6a604252b1af7a9388db5dc170f737069cc31051))
|
||||
|
||||
|
||||
## [v0.5.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-cli/v0.4.0..rustypipe-cli/v0.5.0) - 2024-12-20
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Get comment replies, rich text formatting - ([dceba44](https://codeberg.org/ThetaDev/rustypipe/commit/dceba442fe1a1d5d8d2a6d9422ff699593131f6d))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Replace futures dependency with futures-util - ([5c39bf4](https://codeberg.org/ThetaDev/rustypipe/commit/5c39bf4842b13d37a4277ea5506e15c179892ce5))
|
||||
- Error 400 when fetching player with login - ([5ce84c4](https://codeberg.org/ThetaDev/rustypipe/commit/5ce84c44a6844f692258066c83e04df875e0aa91))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Add docs badge to README - ([706e881](https://codeberg.org/ThetaDev/rustypipe/commit/706e88134c0e94ce7d880735e9d31b3ff531a4f9))
|
||||
- *(deps)* Update rustypipe to 0.8.0
|
||||
|
||||
|
||||
## [v0.4.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-cli/v0.3.0..rustypipe-cli/v0.4.0) - 2024-11-10
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Allow searching for YTM users - ([50010b7](https://codeberg.org/ThetaDev/rustypipe/commit/50010b7b0856d3ce05fe7a9d5989e526089bc2ef))
|
||||
- [**breaking**] Replace `TrackItem::is_video` attr with TrackType enum; serde lowercase AlbumType enum for consistency - ([044094a](https://codeberg.org/ThetaDev/rustypipe/commit/044094a4b70f05c46a459fa1597e23f4224b7b0b))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rust crate thiserror to v2 (#16) - ([e1e1687](https://codeberg.org/ThetaDev/rustypipe/commit/e1e1687605603686ac5fd5deeb6aa8fecaf92494))
|
||||
|
||||
|
||||
## [v0.3.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-cli/v0.2.2..rustypipe-cli/v0.3.0) - 2024-10-28
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- [**breaking**] Remove TvHtml5Embed client as it got disabled - ([9e835c8](https://codeberg.org/ThetaDev/rustypipe/commit/9e835c8f38a3dd28c65561b2f9bb7a0f530c24f1))
|
||||
- Add OAuth user login to access age-restricted videos - ([1cc3f9a](https://codeberg.org/ThetaDev/rustypipe/commit/1cc3f9ad74908d33e247ba6243103bfc22540164))
|
||||
- Revoke OAuth token when logging out - ([62f8a92](https://codeberg.org/ThetaDev/rustypipe/commit/62f8a9210c23e1f02c711a2294af8766ca6b70e2))
|
||||
|
||||
|
||||
## [v0.2.2](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-cli/v0.2.1..rustypipe-cli/v0.2.2) - 2024-10-13
|
||||
|
||||
### 🚀 Features
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "rustypipe-cli"
|
||||
version = "0.7.0"
|
||||
version = "0.2.2"
|
||||
rust-version = "1.70.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
@ -12,7 +12,6 @@ description = "CLI for RustyPipe - download videos and extract data from YouTube
|
|||
|
||||
[features]
|
||||
default = ["native-tls"]
|
||||
timezone = ["dep:time", "dep:time-tz"]
|
||||
|
||||
# Reqwest TLS options
|
||||
native-tls = [
|
||||
|
@ -42,16 +41,13 @@ rustls-tls-native-roots = [
|
|||
]
|
||||
|
||||
[dependencies]
|
||||
rustypipe = { workspace = true, features = ["rss", "userdata"] }
|
||||
rustypipe = { workspace = true, features = ["rss"] }
|
||||
rustypipe-downloader.workspace = true
|
||||
reqwest.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||
futures-util.workspace = true
|
||||
futures.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
quick-xml.workspace = true
|
||||
time = { workspace = true, optional = true }
|
||||
time-tz = { version = "2.0.0", optional = true }
|
||||
|
||||
indicatif.workspace = true
|
||||
anyhow.workspace = true
|
||||
|
@ -64,7 +60,3 @@ dirs.workspace = true
|
|||
anstream = "0.6.15"
|
||||
owo-colors = "4.0.0"
|
||||
const_format = "0.2.33"
|
||||
|
||||
[[bin]]
|
||||
name = "rustypipe"
|
||||
path = "src/main.rs"
|
||||
|
|
104
cli/README.md
104
cli/README.md
|
@ -1,26 +1,14 @@
|
|||
#  CLI
|
||||
|
||||
[](https://crates.io/crates/rustypipe-cli)
|
||||
[](https://opensource.org/licenses/GPL-3.0)
|
||||
[](http://opensource.org/licenses/GPL-3.0)
|
||||
[](https://codeberg.org/ThetaDev/rustypipe/actions/?workflow=ci.yaml)
|
||||
|
||||
The RustyPipe CLI is a powerful YouTube client for the command line. It allows you to
|
||||
access most of the features of the RustyPipe crate: getting data from YouTube and
|
||||
downloading videos.
|
||||
|
||||
## Installation
|
||||
|
||||
You can download a compiled version of RustyPipe here:
|
||||
<https://codeberg.org/ThetaDev/rustypipe/releases>
|
||||
|
||||
Alternatively, you can compile it yourself by installing [Rust](https://rustup.rs/) and
|
||||
running `cargo install rustypipe-cli`.
|
||||
|
||||
To be able to access streams from web-based clients (Desktop, Mobile) you need to
|
||||
download [rustypipe-botguard](https://codeberg.org/ThetaDev/rustypipe-botguard/releases)
|
||||
and place the binary either in the PATH or the current working directory.
|
||||
|
||||
For downloading videos you also need to have ffmpeg installed.
|
||||
The following subcommands are included:
|
||||
|
||||
## `get`: Fetch information
|
||||
|
||||
|
@ -30,13 +18,13 @@ the associated metadata. It can fetch channels, playlists, albums and videos.
|
|||
**Usage:** `rustypipe get UC2TXq_t06Hjdr2g_KdKpHQg`
|
||||
|
||||
- `-l`, `--limit` Limit the number of list items to fetch
|
||||
- `-t`, `--tab` Channel tab (options: **videos**, shorts, live, playlists, info)
|
||||
- ``-t, --tab` Channel tab (options: **videos**, shorts, live, playlists, info)
|
||||
- `-m, --music` Use the YouTube Music API
|
||||
- `--rss`Fetch the RSS feed of a channel
|
||||
- `--comments` Get comments (options: top, latest)
|
||||
- `--lyrics` Get the lyrics for YTM tracks
|
||||
- `--player` Get the player data instead of the video details when fetching videos
|
||||
- `-c`, `--client-type` YT clients used to fetch player data (options: desktop, tv,
|
||||
- `-c, --client-type` YT clients used to fetch player data (options: desktop, tv,
|
||||
tv-embed, android, ios; if multiple clients are specified, they are attempted in
|
||||
order)
|
||||
|
||||
|
@ -59,7 +47,7 @@ when searching YTM or individual channels.
|
|||
- `--date` Filter results by upload date (options: hour, day, week, month, year)
|
||||
- `--order` Sort search results (options: rating, date, views)
|
||||
- `--channel` Channel ID for searching channel videos
|
||||
- `-m`, `--music` Search YouTube Music in the given category (options: all, tracks,
|
||||
- `-m, --music` Search YouTube Music in the given category (options: all, tracks,
|
||||
videos, artists, albums, playlists-ytm, playlists-community)
|
||||
|
||||
## `dl`: Download videos
|
||||
|
@ -78,94 +66,26 @@ videos can be downloaded in parallel for improved performance.
|
|||
- `-r`, `--resolution` Video resolution (e.g. 720, 1080). Set to 0 for audio-only
|
||||
- `-a`, `--audio` Download only the audio track and write track metadata + album cover
|
||||
- `-p`, `--parallel` Number of videos downloaded in parallel (default: 8)
|
||||
- `-m`, `--music` Use YouTube Music for downloading playlists
|
||||
- `-m, --music` Use YouTube Music for downloading playlists
|
||||
- `-l`, `--limit` Limit the number of videos to download (default: 1000)
|
||||
- `-c`, `--client-type` YT clients used to fetch player data (options: desktop, tv,
|
||||
tv-embed, android, ios; if multiple clients are specified, they are attempted in
|
||||
order)
|
||||
- `--pot` token to circumvent bot detection
|
||||
|
||||
## `vdata`: Get visitor data
|
||||
|
||||
You can use the vdata command to get a new visitor data ID. This feature may come in
|
||||
You can use the vdata command to get a new visitor data cookie. This feature may come in
|
||||
handy for testing and reproducing A/B tests.
|
||||
|
||||
## `releases` Get YouTube Music new releases
|
||||
|
||||
Get a list of new albums or music videos on YouTube Music
|
||||
|
||||
**Usage:** `rustypipe releases` or `rustypipe releases --videos`
|
||||
|
||||
## `charts`: Get YouTube Music charts
|
||||
|
||||
Get a list of the most popular tracks and artists for a given country
|
||||
|
||||
**Usage:** `rustypipe charts DE`
|
||||
|
||||
## `history`: Get YouTube playback history
|
||||
|
||||
Get a list of recently played videos or tracks
|
||||
|
||||
### Options
|
||||
|
||||
- `-l`, `--limit` Limit the number of list items to fetch
|
||||
- `--search` Search the playback history (unavailable on YouTube Music)
|
||||
- `-m`, `--music` Get the YouTube Music playback history
|
||||
|
||||
## `subscriptions`: Get subscribed channels
|
||||
|
||||
You can use the RustyPipe CLI to get a list of the channels you subscribed to. With the
|
||||
`--format` flag you can export then in different formats, including OPML and NewPipe
|
||||
JSON.
|
||||
|
||||
With the `--feed` option you can output a list of the latest videos from your
|
||||
subscription feed instead.
|
||||
|
||||
### Options
|
||||
|
||||
- `-l`, `--limit` Limit the number of list items to fetch
|
||||
- `-m`, `--music` Get a list of subscribed YouTube Music artists
|
||||
- `--feed` Output YouTube Music subscription feed
|
||||
|
||||
## `playlists`, `albums`, `tracks`: Get your YouTube library
|
||||
|
||||
Fetch a list of all the items saved in your YouTube/YouTube Music profile.
|
||||
|
||||
### Options
|
||||
|
||||
- `-l`, `--limit` Limit the number of list items to fetch
|
||||
- `-m`, `--music` (only for playlists): Get your YouTube Music playlists
|
||||
|
||||
## Global options
|
||||
|
||||
- **Proxy:** RustyPipe respects the environment variables `HTTP_PROXY`, `HTTPS_PROXY`
|
||||
and `ALL_PROXY`
|
||||
- **Logging:** Enable debug logging with the `-v` (verbose) flag. If you want more
|
||||
fine-grained control, use the `RUST_LOG` environment variable.
|
||||
- **Visitor data:** A custom visitor data ID can be used with the `--vdata` flag
|
||||
- **Authentication:** Use the commands `rustypipe login` and `rustypipe login --cookie`
|
||||
to log into your Google account using either OAuth or YouTube cookies. With the
|
||||
`--auth` flag you can use authentication for any request.
|
||||
- `--lang` Change the YouTube content language
|
||||
- `--country` Change the YouTube content country
|
||||
- `--tz` Use a specific
|
||||
[timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (e.g.
|
||||
Europe/Berlin, Australia/Sydney)
|
||||
|
||||
**Note:** this requires building rustypipe-cli with the `timezone` feature
|
||||
|
||||
- `--local-tz` Use the local timezone instead of UTC
|
||||
- `--report` Generate a report on every request and store it in a `rustypipe_reports`
|
||||
folder in the current directory
|
||||
- `--cache-file` Change the RustyPipe cache file location (Default:
|
||||
`~/.local/share/rustypipe/rustypipe_cache.json`)
|
||||
- `--report-dir` Change the RustyPipe report directory location (Default:
|
||||
`~/.local/share/rustypipe/rustypipe_reports`)
|
||||
- `--botguard-bin` Use a
|
||||
[rustypipe-botguard](https://codeberg.org/ThetaDev/rustypipe-botguard) binary from the
|
||||
given path for generating PO tokens
|
||||
- `--no-botguard` Disable Botguard, only download videos using clients that dont require
|
||||
it
|
||||
- `--pot-cache` Enable caching for session-bound PO tokens
|
||||
- **Logging:** You can change the log level with the `RUST_LOG` environment variable, it
|
||||
is set to `info` by default
|
||||
- **Visitor data:** A custom visitor data cookie can be used with the `--vdata` flag
|
||||
- `--report`
|
||||
|
||||
### Output format
|
||||
|
||||
|
|
752
cli/src/main.rs
752
cli/src/main.rs
File diff suppressed because it is too large
Load diff
|
@ -9,13 +9,12 @@ repository.workspace = true
|
|||
publish = false
|
||||
|
||||
[dependencies]
|
||||
rustypipe = { path = "../", features = ["userdata"] }
|
||||
rustypipe = { path = "../" }
|
||||
reqwest.workspace = true
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
futures-util.workspace = true
|
||||
futures.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_plain.workspace = true
|
||||
serde_with.workspace = true
|
||||
once_cell.workspace = true
|
||||
regex.workspace = true
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use futures_util::{stream, StreamExt};
|
||||
use futures::{stream, StreamExt};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use num_enum::TryFromPrimitive;
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -36,16 +36,13 @@ pub enum ABTest {
|
|||
CommentsFrameworkUpdate = 14,
|
||||
ChannelShortsLockup = 15,
|
||||
PlaylistPageHeader = 16,
|
||||
ChannelPlaylistsLockup = 17,
|
||||
MusicPlaylistFacepile = 18,
|
||||
MusicAlbumGroupsReordered = 19,
|
||||
MusicContinuationItemRenderer = 20,
|
||||
}
|
||||
|
||||
/// List of active A/B tests that are run when none is manually specified
|
||||
const TESTS_TO_RUN: &[ABTest] = &[
|
||||
ABTest::MusicAlbumGroupsReordered,
|
||||
ABTest::MusicContinuationItemRenderer,
|
||||
const TESTS_TO_RUN: [ABTest; 3] = [
|
||||
ABTest::ChannelPageHeader,
|
||||
ABTest::MusicPlaylistTwoColumn,
|
||||
ABTest::CommentsFrameworkUpdate,
|
||||
];
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -94,7 +91,7 @@ pub async fn run_test(
|
|||
let rp = rp.clone();
|
||||
let pb = pb.clone();
|
||||
async move {
|
||||
let visitor_data = rp.query().get_visitor_data(true).await.unwrap();
|
||||
let visitor_data = rp.query().get_visitor_data().await.unwrap();
|
||||
let query = rp.query().visitor_data(&visitor_data);
|
||||
let is_present = match ab {
|
||||
ABTest::AttributedTextDescription => attributed_text_description(&query).await,
|
||||
|
@ -115,12 +112,6 @@ pub async fn run_test(
|
|||
ABTest::CommentsFrameworkUpdate => comments_framework_update(&query).await,
|
||||
ABTest::ChannelShortsLockup => channel_shorts_lockup(&query).await,
|
||||
ABTest::PlaylistPageHeader => playlist_page_header_renderer(&query).await,
|
||||
ABTest::ChannelPlaylistsLockup => channel_playlists_lockup(&query).await,
|
||||
ABTest::MusicPlaylistFacepile => music_playlist_facepile(&query).await,
|
||||
ABTest::MusicAlbumGroupsReordered => music_album_groups_reordered(&query).await,
|
||||
ABTest::MusicContinuationItemRenderer => {
|
||||
music_continuation_item_renderer(&query).await
|
||||
}
|
||||
}
|
||||
.unwrap();
|
||||
pb.inc(1);
|
||||
|
@ -146,10 +137,10 @@ pub async fn run_all_tests(n: usize, concurrency: usize) -> Vec<ABTestRes> {
|
|||
let mut results = Vec::new();
|
||||
|
||||
for ab in TESTS_TO_RUN {
|
||||
let (occurrences, vd_present, vd_absent) = run_test(*ab, n, concurrency).await;
|
||||
let (occurrences, vd_present, vd_absent) = run_test(ab, n, concurrency).await;
|
||||
results.push(ABTestRes {
|
||||
id: *ab as u16,
|
||||
name: *ab,
|
||||
id: ab as u16,
|
||||
name: ab,
|
||||
tests: n,
|
||||
occurrences,
|
||||
vd_present,
|
||||
|
@ -165,7 +156,7 @@ pub async fn attributed_text_description(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
content_check_ok: false,
|
||||
racy_check_ok: false,
|
||||
};
|
||||
let response_txt = rp.raw(ClientType::Desktop, "next", &q).await?;
|
||||
let response_txt = rp.raw(ClientType::Desktop, "next", &q).await.unwrap();
|
||||
|
||||
if !response_txt.contains("\"Black Mamba\"") {
|
||||
bail!("invalid response data");
|
||||
|
@ -175,7 +166,7 @@ pub async fn attributed_text_description(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
}
|
||||
|
||||
pub async fn three_tab_channel_layout(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let channel = rp.channel_videos("UCR-DXc1voovS8nhAvccRZhg").await?;
|
||||
let channel = rp.channel_videos("UCR-DXc1voovS8nhAvccRZhg").await.unwrap();
|
||||
Ok(channel.has_live || channel.has_shorts)
|
||||
}
|
||||
|
||||
|
@ -242,7 +233,8 @@ pub async fn discography_page(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
params: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(res.contains(&format!("\"MPAD{id}\"")))
|
||||
}
|
||||
|
||||
|
@ -304,7 +296,8 @@ pub async fn channel_about_modal(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
params: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(!res.contains("\"EgVhYm91dPIGBAoCEgA%3D\""))
|
||||
}
|
||||
|
||||
|
@ -341,7 +334,8 @@ pub async fn music_playlist_two_column(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
params: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(res.contains("\"musicResponsiveHeaderRenderer\""))
|
||||
}
|
||||
|
||||
|
@ -350,7 +344,8 @@ pub async fn comments_framework_update(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
"Eg0SC3dMZHBSN2d1S3k4GAYyJSIRIgt3TGRwUjdndUt5ODAAeAJCEGNvbW1lbnRzLXNlY3Rpb24%3D";
|
||||
let res = rp
|
||||
.raw(ClientType::Desktop, "next", &QCont { continuation })
|
||||
.await?;
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(res.contains("\"frameworkUpdates\""))
|
||||
}
|
||||
|
||||
|
@ -365,7 +360,8 @@ pub async fn channel_shorts_lockup(rp: &RustyPipeQuery) -> Result<bool> {
|
|||
params: Some("EgZzaG9ydHPyBgUKA5oBAA%3D%3D"),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(res.contains("\"shortsLockupViewModel\""))
|
||||
}
|
||||
|
||||
|
@ -380,66 +376,7 @@ pub async fn playlist_page_header_renderer(rp: &RustyPipeQuery) -> Result<bool>
|
|||
params: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(res.contains("\"pageHeaderRenderer\""))
|
||||
}
|
||||
|
||||
pub async fn channel_playlists_lockup(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let id = "UC2DjFE7Xf11URZqWBigcVOQ";
|
||||
let res = rp
|
||||
.raw(
|
||||
ClientType::Desktop,
|
||||
"browse",
|
||||
&QBrowse {
|
||||
browse_id: id,
|
||||
params: Some("EglwbGF5bGlzdHMgAQ%3D%3D"),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(res.contains("\"lockupViewModel\""))
|
||||
}
|
||||
|
||||
pub async fn music_playlist_facepile(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let id = "VLPL1J-6JOckZtE_P9Xx8D3b2O6w0idhuKBe";
|
||||
let res = rp
|
||||
.raw(
|
||||
ClientType::DesktopMusic,
|
||||
"browse",
|
||||
&QBrowse {
|
||||
browse_id: id,
|
||||
params: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(res.contains("\"facepile\""))
|
||||
}
|
||||
|
||||
pub async fn music_album_groups_reordered(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let id = "UCOR4_bSVIXPsGa4BbCSt60Q";
|
||||
let res = rp
|
||||
.raw(
|
||||
ClientType::DesktopMusic,
|
||||
"browse",
|
||||
&QBrowse {
|
||||
browse_id: id,
|
||||
params: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(res.contains("\"Singles & EPs\""))
|
||||
}
|
||||
|
||||
pub async fn music_continuation_item_renderer(rp: &RustyPipeQuery) -> Result<bool> {
|
||||
let id = "VLPLbZIPy20-1pN7mqjckepWF78ndb6ci_qi";
|
||||
let res = rp
|
||||
.raw(
|
||||
ClientType::DesktopMusic,
|
||||
"browse",
|
||||
&QBrowse {
|
||||
browse_id: id,
|
||||
params: None,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(res.contains("\"continuationItemRenderer\""))
|
||||
}
|
||||
|
|
|
@ -1,41 +1,28 @@
|
|||
use std::{collections::BTreeMap, fs::File, io::BufReader};
|
||||
|
||||
use futures_util::stream::{self, StreamExt};
|
||||
use futures::stream::{self, StreamExt};
|
||||
use path_macro::path;
|
||||
use rustypipe::{
|
||||
client::{ClientType, RustyPipe, RustyPipeQuery},
|
||||
model::AlbumType,
|
||||
param::{Language, LANGUAGES},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::rust::deserialize_ignore_any;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
model::{ContentsRenderer, QBrowse, SectionList, Tab, TextRuns},
|
||||
model::{QBrowse, TextRuns},
|
||||
util::{self, DICT_DIR},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum AlbumTypeX {
|
||||
Album,
|
||||
Ep,
|
||||
Single,
|
||||
Audiobook,
|
||||
Show,
|
||||
AlbumRow,
|
||||
SingleRow,
|
||||
}
|
||||
|
||||
pub async fn collect_album_types(concurrency: usize) {
|
||||
let json_path = path!(*DICT_DIR / "album_type_samples.json");
|
||||
|
||||
let album_types = [
|
||||
(AlbumTypeX::Album, "MPREb_nlBWQROfvjo"),
|
||||
(AlbumTypeX::Single, "MPREb_bHfHGoy7vuv"),
|
||||
(AlbumTypeX::Ep, "MPREb_u1I69lSAe5v"),
|
||||
(AlbumTypeX::Audiobook, "MPREb_gaoNzsQHedo"),
|
||||
(AlbumTypeX::Show, "MPREb_cwzk8EUwypZ"),
|
||||
(AlbumType::Album, "MPREb_nlBWQROfvjo"),
|
||||
(AlbumType::Single, "MPREb_bHfHGoy7vuv"),
|
||||
(AlbumType::Ep, "MPREb_u1I69lSAe5v"),
|
||||
(AlbumType::Audiobook, "MPREb_gaoNzsQHedo"),
|
||||
(AlbumType::Show, "MPREb_cwzk8EUwypZ"),
|
||||
];
|
||||
|
||||
let rp = RustyPipe::new();
|
||||
|
@ -45,7 +32,7 @@ pub async fn collect_album_types(concurrency: usize) {
|
|||
let rp = rp.clone();
|
||||
async move {
|
||||
let query = rp.query().lang(lang);
|
||||
let mut data: BTreeMap<AlbumTypeX, String> = BTreeMap::new();
|
||||
let mut data: BTreeMap<AlbumType, String> = BTreeMap::new();
|
||||
|
||||
for (album_type, id) in album_types {
|
||||
let atype_txt = get_album_type(&query, id).await;
|
||||
|
@ -53,22 +40,6 @@ pub async fn collect_album_types(concurrency: usize) {
|
|||
data.insert(album_type, atype_txt);
|
||||
}
|
||||
|
||||
let (albums_txt, singles_txt) = get_album_groups(&query).await;
|
||||
println!(
|
||||
"collected {}-{:?} ({})",
|
||||
lang,
|
||||
AlbumTypeX::AlbumRow,
|
||||
&albums_txt
|
||||
);
|
||||
println!(
|
||||
"collected {}-{:?} ({})",
|
||||
lang,
|
||||
AlbumTypeX::SingleRow,
|
||||
&singles_txt
|
||||
);
|
||||
data.insert(AlbumTypeX::AlbumRow, albums_txt);
|
||||
data.insert(AlbumTypeX::SingleRow, singles_txt);
|
||||
|
||||
(lang, data)
|
||||
}
|
||||
})
|
||||
|
@ -84,7 +55,7 @@ pub fn write_samples_to_dict() {
|
|||
let json_path = path!(*DICT_DIR / "album_type_samples.json");
|
||||
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
let collected: BTreeMap<Language, BTreeMap<String, String>> =
|
||||
let collected: BTreeMap<Language, BTreeMap<AlbumType, String>> =
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||
let mut dict = util::read_dict();
|
||||
let langs = dict.keys().copied().collect::<Vec<_>>();
|
||||
|
@ -96,12 +67,10 @@ pub fn write_samples_to_dict() {
|
|||
e_langs.push(lang);
|
||||
|
||||
for lang in &e_langs {
|
||||
collected.get(lang).unwrap().iter().for_each(|(t_str, v)| {
|
||||
let t =
|
||||
serde_plain::from_str::<AlbumType>(t_str.split('_').next().unwrap()).unwrap();
|
||||
collected.get(lang).unwrap().iter().for_each(|(t, v)| {
|
||||
dict_entry
|
||||
.album_types
|
||||
.insert(v.to_lowercase().trim().to_owned(), t);
|
||||
.insert(v.to_lowercase().trim().to_owned(), *t);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -111,19 +80,13 @@ pub fn write_samples_to_dict() {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct AlbumData {
|
||||
contents: AlbumContents,
|
||||
header: Header,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AlbumContents {
|
||||
two_column_browse_results_renderer: ContentsRenderer<Tab<SectionList<AlbumHeader>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AlbumHeader {
|
||||
music_responsive_header_renderer: HeaderRenderer,
|
||||
struct Header {
|
||||
music_detail_header_renderer: HeaderRenderer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -143,20 +106,8 @@ async fn get_album_type(query: &RustyPipeQuery, id: &str) -> String {
|
|||
let album = serde_json::from_str::<AlbumData>(&response_txt).unwrap();
|
||||
|
||||
album
|
||||
.contents
|
||||
.two_column_browse_results_renderer
|
||||
.contents
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.tab_renderer
|
||||
.content
|
||||
.section_list_renderer
|
||||
.contents
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.music_responsive_header_renderer
|
||||
.header
|
||||
.music_detail_header_renderer
|
||||
.subtitle
|
||||
.runs
|
||||
.into_iter()
|
||||
|
@ -164,84 +115,3 @@ async fn get_album_type(query: &RustyPipeQuery, id: &str) -> String {
|
|||
.unwrap()
|
||||
.text
|
||||
}
|
||||
|
||||
async fn get_album_groups(query: &RustyPipeQuery) -> (String, String) {
|
||||
let body = QBrowse {
|
||||
browse_id: "UCOR4_bSVIXPsGa4BbCSt60Q",
|
||||
params: None,
|
||||
};
|
||||
let response_txt = query
|
||||
.clone()
|
||||
.visitor_data("CgtwbzJZcS1XZWc1QSjM2JG8BjIKCgJERRIEEgAgCw%3D%3D")
|
||||
.raw(ClientType::DesktopMusic, "browse", &body)
|
||||
.await
|
||||
.unwrap();
|
||||
let artist = serde_json::from_str::<ArtistData>(&response_txt).unwrap();
|
||||
|
||||
let sections = artist
|
||||
.contents
|
||||
.single_column_browse_results_renderer
|
||||
.contents
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|c| c.tab_renderer.content.section_list_renderer.contents)
|
||||
.unwrap();
|
||||
let titles = sections
|
||||
.into_iter()
|
||||
.filter_map(|s| {
|
||||
if let ItemSection::MusicCarouselShelfRenderer(r) = s {
|
||||
r.header
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|h| {
|
||||
h.music_carousel_shelf_basic_header_renderer
|
||||
.title
|
||||
.runs
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.text
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert!(titles.len() >= 2, "too few sections");
|
||||
|
||||
let mut titles_it = titles.into_iter();
|
||||
(titles_it.next().unwrap(), titles_it.next().unwrap())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ArtistData {
|
||||
contents: ArtistDataContents,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ArtistDataContents {
|
||||
single_column_browse_results_renderer: ContentsRenderer<Tab<SectionList<ItemSection>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum ItemSection {
|
||||
MusicCarouselShelfRenderer(MusicCarouselShelf),
|
||||
#[serde(other, deserialize_with = "deserialize_ignore_any")]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MusicCarouselShelf {
|
||||
header: Option<MusicCarouselShelfHeader>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct MusicCarouselShelfHeader {
|
||||
music_carousel_shelf_basic_header_renderer: MusicCarouselShelfHeaderRenderer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct MusicCarouselShelfHeaderRenderer {
|
||||
title: TextRuns,
|
||||
}
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
use std::{collections::BTreeMap, fs::File, io::BufReader};
|
||||
|
||||
use path_macro::path;
|
||||
use rustypipe::{
|
||||
client::RustyPipe,
|
||||
param::{Language, LANGUAGES},
|
||||
};
|
||||
|
||||
use crate::util::{self, DICT_DIR};
|
||||
|
||||
type CollectedDates = BTreeMap<Language, BTreeMap<String, String>>;
|
||||
|
||||
const THIS_WEEK: &str = "this_week";
|
||||
const LAST_WEEK: &str = "last_week";
|
||||
|
||||
pub async fn collect_dates_music() {
|
||||
let json_path = path!(*DICT_DIR / "history_date_samples.json");
|
||||
let rp = RustyPipe::builder()
|
||||
.storage_dir(path!(env!("CARGO_MANIFEST_DIR") / ".."))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut res: CollectedDates = {
|
||||
let json_file = File::open(&json_path).unwrap();
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap()
|
||||
};
|
||||
|
||||
for lang in LANGUAGES {
|
||||
println!("{lang}");
|
||||
let history = rp.query().lang(lang).music_history().await.unwrap();
|
||||
if history.items.len() < 3 {
|
||||
panic!("{lang} empty history")
|
||||
}
|
||||
|
||||
// The indexes have to be adapted before running
|
||||
let entry = res.entry(lang).or_default();
|
||||
entry.insert(
|
||||
THIS_WEEK.to_owned(),
|
||||
history.items[0].playback_date_txt.clone().unwrap(),
|
||||
);
|
||||
entry.insert(
|
||||
LAST_WEEK.to_owned(),
|
||||
history.items[18].playback_date_txt.clone().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
let file = File::create(&json_path).unwrap();
|
||||
serde_json::to_writer_pretty(file, &res).unwrap();
|
||||
}
|
||||
|
||||
pub async fn collect_dates() {
|
||||
let json_path = path!(*DICT_DIR / "history_date_samples.json");
|
||||
let rp = RustyPipe::builder()
|
||||
.storage_dir(path!(env!("CARGO_MANIFEST_DIR") / ".."))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let mut res: CollectedDates = {
|
||||
let json_file = File::open(&json_path).unwrap();
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap()
|
||||
};
|
||||
|
||||
for lang in LANGUAGES {
|
||||
println!("{lang}");
|
||||
let history = rp.query().lang(lang).history().await.unwrap();
|
||||
if history.items.len() < 3 {
|
||||
panic!("{lang} empty history")
|
||||
}
|
||||
|
||||
let entry = res.entry(lang).or_default();
|
||||
entry.insert(
|
||||
"tuesday".to_owned(),
|
||||
history.items[0].playback_date_txt.clone().unwrap(),
|
||||
);
|
||||
entry.insert(
|
||||
"0000-01-06".to_owned(),
|
||||
history.items[1].playback_date_txt.clone().unwrap(),
|
||||
);
|
||||
entry.insert(
|
||||
"2024-12-28".to_owned(),
|
||||
history.items[15].playback_date_txt.clone().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
let file = File::create(&json_path).unwrap();
|
||||
serde_json::to_writer_pretty(file, &res).unwrap();
|
||||
}
|
||||
|
||||
pub fn write_samples_to_dict() {
|
||||
let json_path = path!(*DICT_DIR / "history_date_samples.json");
|
||||
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
let collected_dates: CollectedDates =
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||
let mut dict = util::read_dict();
|
||||
let langs = dict.keys().copied().collect::<Vec<_>>();
|
||||
|
||||
for lang in langs {
|
||||
let dict_entry = dict.entry(lang).or_default();
|
||||
let cd = &collected_dates[&lang];
|
||||
dict_entry
|
||||
.timeago_nd_tokens
|
||||
.insert(util::filter_datestr(&cd[THIS_WEEK]), "0Wl".to_owned());
|
||||
dict_entry
|
||||
.timeago_nd_tokens
|
||||
.insert(util::filter_datestr(&cd[LAST_WEEK]), "1Wl".to_owned());
|
||||
}
|
||||
|
||||
util::write_dict(dict);
|
||||
}
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use futures_util::{stream, StreamExt};
|
||||
use futures::{stream, StreamExt};
|
||||
use once_cell::sync::Lazy;
|
||||
use path_macro::path;
|
||||
use regex::Regex;
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
io::BufReader,
|
||||
};
|
||||
|
||||
use futures_util::{stream, StreamExt};
|
||||
use futures::{stream, StreamExt};
|
||||
use ordered_hash_map::OrderedHashMap;
|
||||
use path_macro::path;
|
||||
use rustypipe::{
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
fs::File,
|
||||
};
|
||||
|
||||
use futures_util::{stream, StreamExt};
|
||||
use futures::{stream, StreamExt};
|
||||
use path_macro::path;
|
||||
use rustypipe::{
|
||||
client::{RustyPipe, RustyPipeQuery},
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures_util::{stream, StreamExt};
|
||||
use futures::{stream, StreamExt};
|
||||
use path_macro::path;
|
||||
use rustypipe::{
|
||||
client::{ClientType, RustyPipe, RustyPipeQuery},
|
||||
|
|
|
@ -62,17 +62,6 @@ pub async fn download_testfiles() {
|
|||
music_charts().await;
|
||||
music_genres().await;
|
||||
music_genre().await;
|
||||
|
||||
// User data
|
||||
history().await;
|
||||
subscriptions().await;
|
||||
subscription_feed().await;
|
||||
|
||||
music_history().await;
|
||||
music_saved_artists().await;
|
||||
music_saved_albums().await;
|
||||
music_saved_tracks().await;
|
||||
music_saved_playlists().await;
|
||||
}
|
||||
|
||||
const CLIENT_TYPES: [ClientType; 5] = [
|
||||
|
@ -466,36 +455,6 @@ async fn trending() {
|
|||
rp.query().trending().await.unwrap();
|
||||
}
|
||||
|
||||
async fn history() {
|
||||
let json_path = path!(*TESTFILES_DIR / "userdata" / "history.json");
|
||||
if json_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rp = rp_testfile(&json_path);
|
||||
rp.query().history().await.unwrap();
|
||||
}
|
||||
|
||||
async fn subscriptions() {
|
||||
let json_path = path!(*TESTFILES_DIR / "userdata" / "subscriptions.json");
|
||||
if json_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rp = rp_testfile(&json_path);
|
||||
rp.query().subscriptions().await.unwrap();
|
||||
}
|
||||
|
||||
async fn subscription_feed() {
|
||||
let json_path = path!(*TESTFILES_DIR / "userdata" / "subscription_feed.json");
|
||||
if json_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rp = rp_testfile(&json_path);
|
||||
rp.query().subscription_feed().await.unwrap();
|
||||
}
|
||||
|
||||
async fn music_playlist() {
|
||||
for (name, id) in [
|
||||
("short", "RDCLAK5uy_kFQXdnqMaQCVx2wpUM4ZfbsGCDibZtkJk"),
|
||||
|
@ -817,53 +776,3 @@ async fn music_genre() {
|
|||
rp.query().music_genre(id).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn music_history() {
|
||||
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "music_history.json");
|
||||
if json_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rp = rp_testfile(&json_path);
|
||||
rp.query().music_history().await.unwrap();
|
||||
}
|
||||
|
||||
async fn music_saved_artists() {
|
||||
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "saved_artists.json");
|
||||
if json_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rp = rp_testfile(&json_path);
|
||||
rp.query().music_saved_artists().await.unwrap();
|
||||
}
|
||||
|
||||
async fn music_saved_albums() {
|
||||
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "saved_albums.json");
|
||||
if json_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rp = rp_testfile(&json_path);
|
||||
rp.query().music_saved_albums().await.unwrap();
|
||||
}
|
||||
|
||||
async fn music_saved_tracks() {
|
||||
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "saved_tracks.json");
|
||||
if json_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rp = rp_testfile(&json_path);
|
||||
rp.query().music_saved_tracks().await.unwrap();
|
||||
}
|
||||
|
||||
async fn music_saved_playlists() {
|
||||
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "saved_playlists.json");
|
||||
if json_path.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let rp = rp_testfile(&json_path);
|
||||
rp.query().music_saved_playlists().await.unwrap();
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
};
|
||||
|
||||
fn parse_tu(tu: &str) -> (u8, Option<TimeUnit>) {
|
||||
static TU_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\d*)(\w*)$").unwrap());
|
||||
static TU_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"^(\d*)(\w?)$").unwrap());
|
||||
match TU_PATTERN.captures(tu) {
|
||||
Some(cap) => (
|
||||
cap.get(1).unwrap().as_str().parse().unwrap_or(1),
|
||||
|
@ -22,8 +22,6 @@ fn parse_tu(tu: &str) -> (u8, Option<TimeUnit>) {
|
|||
"W" => Some(TimeUnit::Week),
|
||||
"M" => Some(TimeUnit::Month),
|
||||
"Y" => Some(TimeUnit::Year),
|
||||
"Wl" => Some(TimeUnit::LastWeek),
|
||||
"Wd" => Some(TimeUnit::LastWeekday),
|
||||
"" => None,
|
||||
_ => panic!("invalid time unit: {tu}"),
|
||||
},
|
||||
|
@ -45,7 +43,7 @@ pub fn generate_dictionary() {
|
|||
use crate::{
|
||||
model::AlbumType,
|
||||
param::Language,
|
||||
util::timeago::{TaToken, TimeUnit},
|
||||
util::timeago::{DateCmp, TaToken, TimeUnit},
|
||||
};
|
||||
|
||||
/// Dictionary entry containing language-specific parsing information
|
||||
|
@ -57,13 +55,14 @@ pub(crate) struct Entry {
|
|||
/// Identifiers: `Y`(ear), `M`(month), `W`(eek), `D`(ay),
|
||||
/// `h`(our), `m`(inute), `s`(econd)
|
||||
pub timeago_tokens: phf::Map<&'static str, TaToken>,
|
||||
/// True if the month has to be parsed before the day
|
||||
/// Order in which to parse numeric date components. Formatted as
|
||||
/// a string of date identifiers (Y, M, D).
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// - 03.01.2020 => DMY => false
|
||||
/// - 01/03/2020 => MDY => true
|
||||
pub month_before_day: bool,
|
||||
/// - 03.01.2020 => `"DMY"`
|
||||
/// - Jan 3, 2020 => `"DY"`
|
||||
pub date_order: &'static [DateCmp],
|
||||
/// Tokens for parsing month names.
|
||||
///
|
||||
/// Format: Parsed token -> Month number (starting from 1)
|
||||
|
@ -138,6 +137,13 @@ pub(crate) fn entry(lang: Language) -> Entry {
|
|||
};
|
||||
});
|
||||
|
||||
// Date order
|
||||
let mut date_order = "&[".to_owned();
|
||||
entry.date_order.chars().for_each(|c| {
|
||||
write!(date_order, "DateCmp::{c}, ").unwrap();
|
||||
});
|
||||
date_order = date_order.trim_end_matches([' ', ',']).to_owned() + "]";
|
||||
|
||||
// Number tokens
|
||||
let mut number_tokens = phf_codegen::Map::<&str>::new();
|
||||
entry.number_tokens.iter().for_each(|(txt, mag)| {
|
||||
|
@ -178,8 +184,8 @@ pub(crate) fn entry(lang: Language) -> Entry {
|
|||
.to_string()
|
||||
.replace('\n', "\n ");
|
||||
|
||||
write!(code_timeago_tokens, "{} => Entry {{\n timeago_tokens: {},\n month_before_day: {:?},\n months: {},\n timeago_nd_tokens: {},\n comma_decimal: {:?},\n number_tokens: {},\n number_nd_tokens: {},\n album_types: {},\n chan_prefix: {:?},\n chan_suffix: {:?},\n }},\n ",
|
||||
selector, code_ta_tokens, entry.month_before_day, code_months, code_ta_nd_tokens, entry.comma_decimal, code_number_tokens, code_number_nd_tokens, code_album_types, entry.chan_prefix, entry.chan_suffix).unwrap();
|
||||
write!(code_timeago_tokens, "{} => Entry {{\n timeago_tokens: {},\n date_order: {},\n months: {},\n timeago_nd_tokens: {},\n comma_decimal: {:?},\n number_tokens: {},\n number_nd_tokens: {},\n album_types: {},\n chan_prefix: {:?},\n chan_suffix: {:?},\n }},\n ",
|
||||
selector, code_ta_tokens, date_order, code_months, code_ta_nd_tokens, entry.comma_decimal, code_number_tokens, code_number_nd_tokens, code_album_types, entry.chan_prefix, entry.chan_suffix).unwrap();
|
||||
}
|
||||
|
||||
code_timeago_tokens = code_timeago_tokens.trim_end().to_owned() + "\n }\n}\n";
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
mod abtest;
|
||||
mod collect_album_types;
|
||||
mod collect_chan_prefixes;
|
||||
mod collect_history_dates;
|
||||
mod collect_large_numbers;
|
||||
mod collect_playlist_dates;
|
||||
mod collect_video_dates;
|
||||
|
@ -31,11 +30,8 @@ enum Commands {
|
|||
CollectAlbumTypes,
|
||||
CollectVideoDurations,
|
||||
CollectVideoDates,
|
||||
CollectHistoryDates,
|
||||
CollectMusicHistoryDates,
|
||||
CollectChanPrefixes,
|
||||
ParsePlaylistDates,
|
||||
ParseHistoryDates,
|
||||
ParseLargeNumbers,
|
||||
ParseAlbumTypes,
|
||||
ParseVideoDurations,
|
||||
|
@ -72,17 +68,10 @@ async fn main() {
|
|||
Commands::CollectVideoDates => {
|
||||
collect_video_dates::collect_video_dates(cli.concurrency).await;
|
||||
}
|
||||
Commands::CollectHistoryDates => {
|
||||
collect_history_dates::collect_dates().await;
|
||||
}
|
||||
Commands::CollectMusicHistoryDates => {
|
||||
collect_history_dates::collect_dates_music().await;
|
||||
}
|
||||
Commands::CollectChanPrefixes => {
|
||||
collect_chan_prefixes::collect_chan_prefixes().await;
|
||||
}
|
||||
Commands::ParsePlaylistDates => collect_playlist_dates::write_samples_to_dict(),
|
||||
Commands::ParseHistoryDates => collect_history_dates::write_samples_to_dict(),
|
||||
Commands::ParseLargeNumbers => collect_large_numbers::write_samples_to_dict(),
|
||||
Commands::ParseAlbumTypes => collect_album_types::write_samples_to_dict(),
|
||||
Commands::ParseVideoDurations => collect_video_durations::parse_video_durations(),
|
||||
|
|
|
@ -13,13 +13,6 @@ pub struct DictEntry {
|
|||
/// Should the language be parsed by character instead of by word?
|
||||
/// (e.g. Chinese/Japanese)
|
||||
pub by_char: bool,
|
||||
/// True if the month has to be parsed before the day
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// - 03.01.2020 => DMY => false
|
||||
/// - 01/03/2020 => MDY => true
|
||||
pub month_before_day: bool,
|
||||
/// Tokens for parsing timeago strings.
|
||||
///
|
||||
/// Format: Parsed token -> \[Quantity\] Identifier
|
||||
|
@ -95,8 +88,6 @@ pub enum TimeUnit {
|
|||
Week,
|
||||
Month,
|
||||
Year,
|
||||
LastWeek,
|
||||
LastWeekday,
|
||||
}
|
||||
|
||||
impl TimeUnit {
|
||||
|
@ -109,8 +100,6 @@ impl TimeUnit {
|
|||
TimeUnit::Week => "W",
|
||||
TimeUnit::Month => "M",
|
||||
TimeUnit::Year => "Y",
|
||||
TimeUnit::LastWeek => "Wl",
|
||||
TimeUnit::LastWeekday => "Wd",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +141,7 @@ pub struct Text {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Channel {
|
||||
pub contents: TwoColumnBrowseResults,
|
||||
pub contents: Contents,
|
||||
pub header: ChannelHeader,
|
||||
}
|
||||
|
||||
|
@ -170,7 +159,7 @@ pub struct HeaderRenderer {
|
|||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TwoColumnBrowseResults {
|
||||
pub struct Contents {
|
||||
pub two_column_browse_results_renderer: TabsRenderer,
|
||||
}
|
||||
|
||||
|
@ -179,37 +168,24 @@ pub struct TwoColumnBrowseResults {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TabsRenderer {
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub tabs: Vec<Tab<RichGrid>>,
|
||||
pub tabs: Vec<TabRendererWrap>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ContentsRenderer<T> {
|
||||
#[serde(alias = "tabs")]
|
||||
pub contents: Vec<T>,
|
||||
pub struct TabRendererWrap {
|
||||
pub tab_renderer: TabRenderer,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Tab<T> {
|
||||
pub tab_renderer: TabRenderer<T>,
|
||||
pub struct TabRenderer {
|
||||
pub content: RichGridRendererWrap,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TabRenderer<T> {
|
||||
pub content: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct SectionList<T> {
|
||||
pub section_list_renderer: ContentsRenderer<T>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RichGrid {
|
||||
pub struct RichGridRendererWrap {
|
||||
pub rich_grid_renderer: RichGridRenderer,
|
||||
}
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@ pub fn filter_datestr(string: &str) -> String {
|
|||
.to_lowercase()
|
||||
.chars()
|
||||
.filter_map(|c| {
|
||||
if matches!(c, '\u{200b}' | '.' | ',') || c.is_ascii_digit() {
|
||||
if c == '\u{200b}' || c.is_ascii_digit() {
|
||||
None
|
||||
} else if c == '-' {
|
||||
Some(' ')
|
||||
|
|
|
@ -3,93 +3,6 @@
|
|||
All notable changes to this project will be documented in this file.
|
||||
|
||||
|
||||
## [v0.3.0](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-downloader/v0.2.7..rustypipe-downloader/v0.3.0) - 2025-02-09
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- [**breaking**] Remove manual PO token options from downloader in favor of rustypipe-botguard - ([cddb32f](https://codeberg.org/ThetaDev/rustypipe/commit/cddb32f190276265258c6ab45b3d43a8891c4b39))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Ensure downloader futures are send - ([812ff4c](https://codeberg.org/ThetaDev/rustypipe/commit/812ff4c5bafffc5708a6d5066f1ebadb6d9fc958))
|
||||
- Download audio with dolby codec - ([9234005](https://codeberg.org/ThetaDev/rustypipe/commit/92340056f868007beccb64e9e26eb39abc40f7aa))
|
||||
|
||||
### 🚜 Refactor
|
||||
|
||||
- [**breaking**] Add client_type field to DownloadError, rename cli option po-token-cache to pot-cache - ([594e675](https://codeberg.org/ThetaDev/rustypipe/commit/594e675b39efc5fbcdbd5e920a4d2cdee64f718e))
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Add Botguard info to README - ([9957add](https://codeberg.org/ThetaDev/rustypipe/commit/9957add2b5d6391b2c1869d2019fd7dd91b8cd41))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rustypipe to 0.10.0
|
||||
|
||||
|
||||
## [v0.2.7](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-downloader/v0.2.6..rustypipe-downloader/v0.2.7) - 2025-01-16
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- Extract player DRM data - ([2af4001](https://codeberg.org/ThetaDev/rustypipe/commit/2af4001c75f2ff4f7c891aa59ac22c2c6b7902a2))
|
||||
- Prefer maxresdefault.jpg thumbnail if available - ([a8e97f4](https://codeberg.org/ThetaDev/rustypipe/commit/a8e97f411a1e769e52d8cbde11f0a4ca1535f7ef))
|
||||
- Add DRM and audio channel number filtering to StreamFilter - ([d5abee2](https://codeberg.org/ThetaDev/rustypipe/commit/d5abee275300ab1bc10fc8d6c35a4e3813fd2bd4))
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Remove Unix file metadata usage (Windows compatibility) - ([5c6d992](https://codeberg.org/ThetaDev/rustypipe/commit/5c6d992939f55a203ac1784f1e9175ac1d498ce8))
|
||||
|
||||
### 📚 Documentation
|
||||
|
||||
- Update README - ([0432477](https://codeberg.org/ThetaDev/rustypipe/commit/0432477451ecd5f64145d65239c721f4e44826c0))
|
||||
- Fix README - ([11442df](https://codeberg.org/ThetaDev/rustypipe/commit/11442dfd369599396357f5b7a7a4268a7b537f57))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rustypipe to 0.9.0
|
||||
- *(deps)* Update rust crate rstest to 0.24.0 (#20) - ([ab19034](https://codeberg.org/ThetaDev/rustypipe/commit/ab19034ab19baf090e83eada056559676ffdadce))
|
||||
- *(deps)* Update rust crate lofty to 0.22.0 - ([addeb82](https://codeberg.org/ThetaDev/rustypipe/commit/addeb821101aa968b95455604bc13bd24f50328f))
|
||||
- *(deps)* Update rust crate dirs to v6 (#24) - ([6a60425](https://codeberg.org/ThetaDev/rustypipe/commit/6a604252b1af7a9388db5dc170f737069cc31051))
|
||||
|
||||
|
||||
## [v0.2.6](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-downloader/v0.2.5..rustypipe-downloader/v0.2.6) - 2024-12-20
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rustypipe to 0.8.0
|
||||
|
||||
|
||||
## [v0.2.5](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-downloader/v0.2.4..rustypipe-downloader/v0.2.5) - 2024-12-13
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Replace futures dependency with futures-util - ([5c39bf4](https://codeberg.org/ThetaDev/rustypipe/commit/5c39bf4842b13d37a4277ea5506e15c179892ce5))
|
||||
- Remove empty tempfile after unsuccessful download - ([5262bec](https://codeberg.org/ThetaDev/rustypipe/commit/5262becca1e9e3e8262833764ef18c23bc401172))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- Add docs badge to README - ([706e881](https://codeberg.org/ThetaDev/rustypipe/commit/706e88134c0e94ce7d880735e9d31b3ff531a4f9))
|
||||
|
||||
|
||||
## [v0.2.4](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-downloader/v0.2.3..rustypipe-downloader/v0.2.4) - 2024-11-10
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rust crate thiserror to v2 (#16) - ([e1e1687](https://codeberg.org/ThetaDev/rustypipe/commit/e1e1687605603686ac5fd5deeb6aa8fecaf92494))
|
||||
- *(deps)* Update rustypipe to 0.7.0
|
||||
|
||||
|
||||
## [v0.2.3](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-downloader/v0.2.2..rustypipe-downloader/v0.2.3) - 2024-10-28
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Remove unnecessary image.rs dependencies - ([1b08166](https://codeberg.org/ThetaDev/rustypipe/commit/1b08166399cccb8394d2fdd82d54162c1a9e01be))
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update rustypipe to 0.6.0
|
||||
|
||||
|
||||
## [v0.2.2](https://codeberg.org/ThetaDev/rustypipe/compare/rustypipe-downloader/v0.2.1..rustypipe-downloader/v0.2.2) - 2024-10-13
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "rustypipe-downloader"
|
||||
version = "0.3.0"
|
||||
version = "0.2.2"
|
||||
rust-version = "1.67.1"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
|
@ -37,7 +37,7 @@ rustypipe.workspace = true
|
|||
once_cell.workspace = true
|
||||
regex.workspace = true
|
||||
thiserror.workspace = true
|
||||
futures-util.workspace = true
|
||||
futures.workspace = true
|
||||
reqwest = { workspace = true, features = ["stream"] }
|
||||
rand.workspace = true
|
||||
tokio = { workspace = true, features = ["macros", "fs", "process"] }
|
||||
|
@ -45,13 +45,9 @@ indicatif = { workspace = true, optional = true }
|
|||
filenamify.workspace = true
|
||||
tracing.workspace = true
|
||||
time.workspace = true
|
||||
lofty = { version = "0.22.0", optional = true }
|
||||
image = { version = "0.25.0", optional = true, default-features = false, features = [
|
||||
"rayon",
|
||||
"jpeg",
|
||||
"webp",
|
||||
] }
|
||||
smartcrop2 = { version = "0.3.1", optional = true }
|
||||
lofty = { version = "0.21.0", optional = true }
|
||||
image = { version = "0.25.0", optional = true }
|
||||
smartcrop2 = { version = "0.3.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
path_macro.workspace = true
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
#  Downloader
|
||||
|
||||
[](https://crates.io/crates/rustypipe-downloader)
|
||||
[](https://opensource.org/licenses/GPL-3.0)
|
||||
[](https://docs.rs/rustypipe-downloader)
|
||||
[](http://opensource.org/licenses/GPL-3.0)
|
||||
[](https://codeberg.org/ThetaDev/rustypipe/actions/?workflow=ci.yaml)
|
||||
|
||||
The downloader is a companion crate for RustyPipe that allows for easy and fast
|
||||
|
|
|
@ -13,13 +13,8 @@ pub enum DownloadError {
|
|||
#[error("http error: {0}")]
|
||||
Http(#[from] reqwest::Error),
|
||||
/// 403 error trying to download video
|
||||
#[error("YouTube returned 403 error; visitor_data={}", .visitor_data.as_deref().unwrap_or_default())]
|
||||
Forbidden {
|
||||
/// Client type used to fetch the failed stream
|
||||
client_type: ClientType,
|
||||
/// Visitor data used to fetch the failed stream
|
||||
visitor_data: Option<String>,
|
||||
},
|
||||
#[error("YouTube returned 403 error")]
|
||||
Forbidden(ClientType),
|
||||
/// File IO error
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
|
@ -30,8 +25,8 @@ pub enum DownloadError {
|
|||
#[error("Progressive download error: {0}")]
|
||||
Progressive(Cow<'static, str>),
|
||||
/// Video could not be downloaded because of invalid player data
|
||||
#[error("source error: {0}")]
|
||||
Source(Cow<'static, str>),
|
||||
#[error("input error: {0}")]
|
||||
Input(Cow<'static, str>),
|
||||
/// Download target already exists
|
||||
#[error("file {0} already exists")]
|
||||
Exists(PathBuf),
|
||||
|
|
|
@ -15,13 +15,13 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
use futures_util::stream::{self, StreamExt, TryStreamExt};
|
||||
use futures::stream::{self, StreamExt};
|
||||
use once_cell::sync::Lazy;
|
||||
use rand::Rng;
|
||||
use regex::Regex;
|
||||
use reqwest::{header, Client, StatusCode, Url};
|
||||
use rustypipe::{
|
||||
client::{ClientType, RustyPipe},
|
||||
client::{ClientType, RustyPipe, DEFAULT_PLAYER_CLIENT_ORDER},
|
||||
model::{
|
||||
traits::{FileFormat, YtEntity},
|
||||
AudioCodec, TrackItem, VideoCodec, VideoPlayer,
|
||||
|
@ -77,6 +77,7 @@ pub struct DownloaderBuilder {
|
|||
#[cfg(feature = "audiotag")]
|
||||
crop_cover: bool,
|
||||
client_types: Option<Vec<ClientType>>,
|
||||
pot: Option<String>,
|
||||
}
|
||||
|
||||
struct DownloaderInner {
|
||||
|
@ -108,6 +109,8 @@ struct DownloaderInner {
|
|||
crop_cover: bool,
|
||||
/// Client types for fetching videos
|
||||
client_types: Option<Vec<ClientType>>,
|
||||
/// Pot token to circumvent bot detection
|
||||
pot: Option<String>,
|
||||
}
|
||||
|
||||
/// Download query
|
||||
|
@ -127,6 +130,8 @@ pub struct DownloadQuery {
|
|||
video_format: Option<DownloadVideoFormat>,
|
||||
/// Client types for fetching videos
|
||||
client_types: Option<Vec<ClientType>>,
|
||||
/// Pot token to circumvent bot detection
|
||||
pot: Option<String>,
|
||||
}
|
||||
|
||||
/// Video to be downloaded
|
||||
|
@ -293,6 +298,7 @@ impl Default for DownloaderBuilder {
|
|||
#[cfg(feature = "audiotag")]
|
||||
crop_cover: false,
|
||||
client_types: None,
|
||||
pot: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -411,6 +417,21 @@ impl DownloaderBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the `pot` token to circumvent bot detection
|
||||
///
|
||||
/// YouTube has implemented the token to prevent other clients from downloading YouTube videos.
|
||||
/// The token is generated using YouTube's botguard. Therefore you need a full browser environment
|
||||
/// to obtain one.
|
||||
///
|
||||
/// The Invidious project has created a script to extract this token: <https://github.com/iv-org/youtube-trusted-session-generator>
|
||||
///
|
||||
/// The `pot` token is only used for the [`ClientType::Desktop`] and [`ClientType::DesktopMusic`] clients.
|
||||
#[must_use]
|
||||
pub fn pot<S: Into<String>>(mut self, pot: S) -> Self {
|
||||
self.pot = Some(pot.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a new, configured [`Downloader`] instance
|
||||
pub fn build(self) -> Downloader {
|
||||
self.build_with_client(
|
||||
|
@ -445,6 +466,7 @@ impl DownloaderBuilder {
|
|||
#[cfg(feature = "audiotag")]
|
||||
crop_cover: self.crop_cover,
|
||||
client_types: self.client_types,
|
||||
pot: self.pot,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -479,6 +501,7 @@ impl Downloader {
|
|||
filter: None,
|
||||
video_format: None,
|
||||
client_types: None,
|
||||
pot: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -506,7 +529,7 @@ impl Downloader {
|
|||
self.query(DownloadVideo::from_entity(video))
|
||||
}
|
||||
|
||||
/// Download a video from a [`TrackItem`] (YouTube Music album/playlist item)
|
||||
/// Download a video from a [`TrackItem`] (YouTube music album/playlist item)
|
||||
///
|
||||
/// Providing an entity has the advantage that the download path can be determined before the video
|
||||
/// is fetched, so already downloaded videos get skipped right away.
|
||||
|
@ -629,6 +652,21 @@ impl DownloadQuery {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the `pot` token to circumvent bot detection
|
||||
///
|
||||
/// YouTube has implemented the token to prevent other clients from downloading YouTube videos.
|
||||
/// The token is generated using YouTube's botguard. Therefore you need a full browser environment
|
||||
/// to obtain one.
|
||||
///
|
||||
/// The Invidious project has created a script to extract this token: <https://github.com/iv-org/youtube-trusted-session-generator>
|
||||
///
|
||||
/// The `pot` token is only used for the [`ClientType::Desktop`] and [`ClientType::DesktopMusic`] clients.
|
||||
#[must_use]
|
||||
pub fn pot<S: Into<String>>(mut self, pot: S) -> Self {
|
||||
self.pot = Some(pot.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Download the video
|
||||
///
|
||||
/// If no download path is set, the video is downloaded to the current directory
|
||||
|
@ -660,15 +698,9 @@ impl DownloadQuery {
|
|||
.await
|
||||
{
|
||||
Ok(res) => return Ok(res),
|
||||
Err(DownloadError::Forbidden {
|
||||
client_type,
|
||||
visitor_data,
|
||||
}) => {
|
||||
failed_client = Some(client_type);
|
||||
DownloadError::Forbidden {
|
||||
client_type,
|
||||
visitor_data,
|
||||
}
|
||||
Err(DownloadError::Forbidden(c)) => {
|
||||
failed_client = Some(c);
|
||||
DownloadError::Forbidden(c)
|
||||
}
|
||||
Err(DownloadError::Http(e)) => {
|
||||
if !e.is_timeout() {
|
||||
|
@ -738,7 +770,7 @@ impl DownloadQuery {
|
|||
.as_ref()
|
||||
.or(self.dl.i.client_types.as_ref())
|
||||
.map(Vec::as_slice)
|
||||
.unwrap_or(q.player_client_order()),
|
||||
.unwrap_or(DEFAULT_PLAYER_CLIENT_ORDER),
|
||||
);
|
||||
|
||||
// If the last download failed, try another client if possible
|
||||
|
@ -755,15 +787,20 @@ impl DownloadQuery {
|
|||
|
||||
let player_data = q.player_from_clients(&self.video.id, &client_types).await?;
|
||||
let user_agent = q.user_agent(player_data.client_type);
|
||||
let pot = if matches!(
|
||||
player_data.client_type,
|
||||
ClientType::Desktop | ClientType::DesktopMusic
|
||||
) {
|
||||
self.pot.as_deref().or(self.dl.i.pot.as_deref())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Select streams to download
|
||||
let (video, audio) = player_data.select_video_audio_stream(filter);
|
||||
|
||||
if video.is_none() && audio.is_none() {
|
||||
if player_data.drm.is_some() {
|
||||
return Err(DownloadError::Source("video is DRM-protected".into()));
|
||||
}
|
||||
return Err(DownloadError::Source("no stream found".into()));
|
||||
return Err(DownloadError::Input("no stream found".into()));
|
||||
}
|
||||
|
||||
let extension = match video {
|
||||
|
@ -772,9 +809,7 @@ impl DownloadQuery {
|
|||
Some(audio) => match audio.codec {
|
||||
AudioCodec::Mp4a => "m4a",
|
||||
AudioCodec::Opus => "opus",
|
||||
AudioCodec::Ac3 => "ac3",
|
||||
AudioCodec::Ec3 => "eac3",
|
||||
_ => return Err(DownloadError::Source("unknown audio codec".into())),
|
||||
_ => return Err(DownloadError::Input("unknown audio codec".into())),
|
||||
},
|
||||
None => unreachable!(),
|
||||
},
|
||||
|
@ -833,10 +868,11 @@ impl DownloadQuery {
|
|||
if let Some(pb) = pb {
|
||||
pb.set_message(format!("Downloading {name}{attempt_suffix}"))
|
||||
}
|
||||
let downloads = download_streams(
|
||||
downloads,
|
||||
download_streams(
|
||||
&downloads,
|
||||
&self.dl.i.http,
|
||||
&user_agent,
|
||||
pot,
|
||||
#[cfg(feature = "indicatif")]
|
||||
pb.clone(),
|
||||
)
|
||||
|
@ -844,14 +880,7 @@ impl DownloadQuery {
|
|||
.map_err(|e| {
|
||||
if let DownloadError::Http(e) = &e {
|
||||
if e.status() == Some(StatusCode::FORBIDDEN) {
|
||||
// 403 errors may occur due to bad visitor data IDs
|
||||
if let Some(vd) = &player_data.visitor_data {
|
||||
q.remove_visitor_data(vd);
|
||||
}
|
||||
return DownloadError::Forbidden {
|
||||
client_type: player_data.client_type,
|
||||
visitor_data: player_data.visitor_data.clone(),
|
||||
};
|
||||
return DownloadError::Forbidden(player_data.client_type);
|
||||
}
|
||||
}
|
||||
e
|
||||
|
@ -871,7 +900,7 @@ impl DownloadQuery {
|
|||
|
||||
// Tag audio file
|
||||
#[cfg(feature = "audiotag")]
|
||||
if self.dl.i.audio_tag && video.is_none() && matches!(extension, "m4a" | "opus") {
|
||||
if self.dl.i.audio_tag && video.is_none() {
|
||||
let (details, track) = match details {
|
||||
Some(d) => (d, self.dl.i.rp.query().music_details(&self.video.id).await?),
|
||||
None => {
|
||||
|
@ -898,9 +927,13 @@ impl DownloadQuery {
|
|||
}
|
||||
|
||||
// Delete original files
|
||||
for d in &downloads {
|
||||
fs::remove_file(&d.file).await?;
|
||||
}
|
||||
stream::iter(&downloads)
|
||||
.map(|d| fs::remove_file(d.file.clone()))
|
||||
.buffer_unordered(downloads.len())
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<core::result::Result<(), _>>()?;
|
||||
|
||||
#[cfg(feature = "indicatif")]
|
||||
if let Some(pb) = pb {
|
||||
|
@ -986,37 +1019,14 @@ impl DownloadQuery {
|
|||
};
|
||||
|
||||
if let Some(thumbnail) = thumbnail {
|
||||
// Attempt to get the higher resolution, uncropped maxresdefault.jpg thumbnail if available
|
||||
let mut resp = None;
|
||||
if thumbnail.height != thumbnail.width {
|
||||
if let Ok(x) = self
|
||||
.dl
|
||||
.i
|
||||
.http
|
||||
.get(format!(
|
||||
"https://i.ytimg.com/vi/{}/maxresdefault.jpg",
|
||||
track.id
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()
|
||||
{
|
||||
resp = Some(x);
|
||||
}
|
||||
}
|
||||
|
||||
let resp = match resp {
|
||||
Some(resp) => resp,
|
||||
None => self
|
||||
let resp = self
|
||||
.dl
|
||||
.i
|
||||
.http
|
||||
.get(thumbnail.url)
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?,
|
||||
};
|
||||
|
||||
.error_for_status()?;
|
||||
let img_type = resp
|
||||
.headers()
|
||||
.get(header::CONTENT_TYPE)
|
||||
|
@ -1115,6 +1125,7 @@ async fn download_single_file(
|
|||
output: &Path,
|
||||
http: &Client,
|
||||
user_agent: &str,
|
||||
pot: Option<&str>,
|
||||
#[cfg(feature = "indicatif")] pb: Option<ProgressBar>,
|
||||
) -> Result<()> {
|
||||
// Check if file is already downloaded
|
||||
|
@ -1203,7 +1214,7 @@ async fn download_single_file(
|
|||
.open(&output_path_tmp)
|
||||
.await?;
|
||||
|
||||
let res = if is_gvideo && size.is_some() {
|
||||
if is_gvideo && size.is_some() {
|
||||
download_chunks_by_param(
|
||||
http,
|
||||
&mut file,
|
||||
|
@ -1211,10 +1222,11 @@ async fn download_single_file(
|
|||
size.unwrap(),
|
||||
offset,
|
||||
user_agent,
|
||||
pot,
|
||||
#[cfg(feature = "indicatif")]
|
||||
pb,
|
||||
)
|
||||
.await
|
||||
.await?;
|
||||
} else {
|
||||
download_chunks_by_header(
|
||||
http,
|
||||
|
@ -1226,19 +1238,7 @@ async fn download_single_file(
|
|||
#[cfg(feature = "indicatif")]
|
||||
pb,
|
||||
)
|
||||
.await
|
||||
};
|
||||
|
||||
drop(file);
|
||||
if let Err(e) = res {
|
||||
// Remove temporary file if nothing was downloaded (e.g. 403 error)
|
||||
if std::fs::metadata(&output_path_tmp)
|
||||
.map(|md| md.len() == 0)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
_ = std::fs::remove_file(&output_path_tmp);
|
||||
}
|
||||
return Err(e);
|
||||
.await?;
|
||||
}
|
||||
|
||||
fs::rename(&output_path_tmp, &output_path).await?;
|
||||
|
@ -1338,6 +1338,7 @@ async fn download_chunks_by_param(
|
|||
size: u64,
|
||||
offset: u64,
|
||||
user_agent: &str,
|
||||
pot: Option<&str>,
|
||||
#[cfg(feature = "indicatif")] pb: Option<ProgressBar>,
|
||||
) -> Result<()> {
|
||||
let mut offset = offset;
|
||||
|
@ -1350,9 +1351,12 @@ async fn download_chunks_by_param(
|
|||
let range = get_download_range(offset, Some(size));
|
||||
tracing::debug!("Fetching range {}-{}", range.start, range.end);
|
||||
|
||||
let urlp =
|
||||
let mut urlp =
|
||||
Url::parse_with_params(url, [("range", &format!("{}-{}", range.start, range.end))])
|
||||
.map_err(|e| DownloadError::Progressive(format!("url parsing: {e}").into()))?;
|
||||
if let Some(pot) = pot {
|
||||
urlp.query_pairs_mut().append_pair("pot", pot);
|
||||
}
|
||||
|
||||
let res = http
|
||||
.get(urlp)
|
||||
|
@ -1370,6 +1374,7 @@ async fn download_chunks_by_param(
|
|||
));
|
||||
}
|
||||
|
||||
tracing::debug!("Retrieving chunks...");
|
||||
let mut stream = res.bytes_stream();
|
||||
while let Some(item) = stream.next().await {
|
||||
// Retrieve chunk.
|
||||
|
@ -1399,30 +1404,33 @@ struct StreamDownload {
|
|||
}
|
||||
|
||||
async fn download_streams(
|
||||
downloads: Vec<StreamDownload>,
|
||||
downloads: &Vec<StreamDownload>,
|
||||
http: &Client,
|
||||
user_agent: &str,
|
||||
pot: Option<&str>,
|
||||
#[cfg(feature = "indicatif")] pb: Option<ProgressBar>,
|
||||
) -> Result<Vec<StreamDownload>> {
|
||||
stream::iter(downloads.iter().map(Ok))
|
||||
.try_for_each_concurrent(2, |d| {
|
||||
#[cfg(feature = "indicatif")]
|
||||
let pb = pb.clone();
|
||||
async move {
|
||||
) -> Result<()> {
|
||||
let n = downloads.len();
|
||||
|
||||
stream::iter(downloads)
|
||||
.map(|d| {
|
||||
download_single_file(
|
||||
&d.url,
|
||||
&d.file,
|
||||
http,
|
||||
user_agent,
|
||||
pot,
|
||||
#[cfg(feature = "indicatif")]
|
||||
pb,
|
||||
pb.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.await?;
|
||||
.buffer_unordered(n)
|
||||
.collect::<Vec<_>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(downloads)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn convert_streams(
|
||||
|
|
|
@ -47,13 +47,11 @@ async fn download_music(rp: RustyPipe) {
|
|||
let td = TempDir::default();
|
||||
let td_path = td.to_path_buf();
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut dl = Downloader::builder().rustypipe(&rp);
|
||||
#[cfg(feature = "audiotag")]
|
||||
{
|
||||
dl = dl.audio_tag().crop_cover();
|
||||
}
|
||||
let dl = dl.build();
|
||||
let dl = Downloader::builder()
|
||||
.audio_tag()
|
||||
.crop_cover()
|
||||
.rustypipe(&rp)
|
||||
.build();
|
||||
|
||||
let res = dl
|
||||
.id("bVtv3st8bgc")
|
||||
|
@ -77,7 +75,7 @@ async fn download_music(rp: RustyPipe) {
|
|||
assert_audio_meta(
|
||||
&res.dest,
|
||||
"Lord of the Riffs",
|
||||
"Alexander Nakarada",
|
||||
"Alexander Nakarada - CreatorChords",
|
||||
"Lord of the Riffs",
|
||||
"2022-02-05",
|
||||
);
|
||||
|
@ -113,15 +111,3 @@ fn assert_audio_meta(p: &Path, title: &str, artist: &str, album: &str, date: &st
|
|||
assert_eq!(tags["ALBUM"].as_str(), Some(album));
|
||||
assert_eq!(tags["DATE"].as_str(), Some(date));
|
||||
}
|
||||
|
||||
/// This is just a static check to make sure all RustyPipe futures can be sent
|
||||
/// between threads safely.
|
||||
/// Otherwise this may cause issues when integrating RustyPipe into async projects.
|
||||
#[allow(unused)]
|
||||
async fn all_send_and_sync() {
|
||||
fn send_and_sync<T: Send + Sync>(t: T) {}
|
||||
|
||||
let dl = Downloader::default();
|
||||
let dlq = dl.id("");
|
||||
send_and_sync(dlq.download());
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@
|
|||
When YouTube introduces a new feature, it does so gradually. When a user creates a new
|
||||
session, YouTube decided randomly which new features should be enabled.
|
||||
|
||||
YouTube sessions are identified by the visitor data ID. This cookie is sent with
|
||||
YouTube sessions are identified by the visitor data cookie. This cookie is sent with
|
||||
every API request using the `context.client.visitor_data` JSON parameter. It is also
|
||||
returned in the `responseContext.visitorData` response parameter and stored as the
|
||||
`__SECURE-YEC` cookie.
|
||||
|
||||
By sending the same visitor data ID, A/B tests can be reproduced, which is important
|
||||
By sending the same visitor data cookie, A/B tests can be reproduced, which is important
|
||||
for testing alternative YouTube clients.
|
||||
|
||||
This page lists all A/B tests that were encountered while maintaining the RustyPipe
|
||||
|
@ -381,7 +381,7 @@ YouTube also changed the way the full discography page is fetched, surprisingly
|
|||
it easier for alternative clients. The discography page now has its own content ID in
|
||||
the format of `MPAD<channel id>` (Music Page Artist Discography). This page can be
|
||||
fetched with a regular browse request without requiring parameters to be parsed or a
|
||||
visitor data ID to be set, as it was the case with the old system.
|
||||
visitor data cookie to be set, as it was the case with the old system.
|
||||
|
||||
**OLD**
|
||||
|
||||
|
@ -793,7 +793,7 @@ YouTube changed the data model for the channel shorts tab
|
|||
- **Encountered on:** 11.10.2024
|
||||
- **Impact:** 🟢 Low
|
||||
- **Endpoint:** browse
|
||||
- **Status:** Stabilized
|
||||
- **Status:** Common (99%)
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -899,156 +899,3 @@ YouTube changed the data model for the channel shorts tab
|
|||
}
|
||||
}
|
||||
```
|
||||
|
||||
## [17] Channel playlists: lockupViewModel
|
||||
|
||||
- **Encountered on:** 09.11.2024
|
||||
- **Impact:** 🟢 Low
|
||||
- **Endpoint:** browse
|
||||
- **Status:** Stabilized
|
||||
|
||||
YouTube changed the data model for the channel playlists / podcasts / albums tab
|
||||
|
||||
```json
|
||||
{
|
||||
"lockupViewModel": {
|
||||
"contentImage": {
|
||||
"collectionThumbnailViewModel": {
|
||||
"primaryThumbnail": {
|
||||
"thumbnailViewModel": {
|
||||
"image": {
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://i.ytimg.com/vi/XYdmX8w8xwI/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCqmf6TGfDinNXhgU29ZxOkv2u9sQ",
|
||||
"width": 480,
|
||||
"height": 270
|
||||
}
|
||||
]
|
||||
},
|
||||
"overlays": [
|
||||
{
|
||||
"thumbnailOverlayBadgeViewModel": {
|
||||
"thumbnailBadges": [
|
||||
{
|
||||
"thumbnailBadgeViewModel": {
|
||||
"icon": {
|
||||
"sources": [
|
||||
{
|
||||
"clientResource": {
|
||||
"imageName": "PLAYLISTS"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"text": "5 videos",
|
||||
"badgeStyle": "THUMBNAIL_OVERLAY_BADGE_STYLE_DEFAULT",
|
||||
"backgroundColor": {
|
||||
"lightTheme": 2370867,
|
||||
"darkTheme": 2370867
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"position": "THUMBNAIL_OVERLAY_BADGE_POSITION_BOTTOM_END"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"lockupMetadataViewModel": {
|
||||
"title": {
|
||||
"content": "Jellybean Components Series"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contentId": "PLvOlSehNtuHv268f0mW5m1t_hq_RVGRSA",
|
||||
"contentType": "LOCKUP_CONTENT_TYPE_PLAYLIST"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## [18] Music playlists facepile avatar
|
||||
|
||||
- **Encountered on:** 25.11.2024
|
||||
- **Impact:** 🟢 Low
|
||||
- **Endpoint:** browse (YTM)
|
||||
- **Status:** Stabilized
|
||||
|
||||
YouTube changed the data model for the channel playlist owner avatar into a `facepile`
|
||||
object. It now also contains the channel avatar.
|
||||
|
||||
The model is also used for playlists owned by YouTube Music (with the avatar and
|
||||
commandContext missing).
|
||||
|
||||
```json
|
||||
{
|
||||
"facepile": {
|
||||
"avatarStackViewModel": {
|
||||
"avatars": [
|
||||
{
|
||||
"avatarViewModel": {
|
||||
"image": {
|
||||
"sources": [
|
||||
{
|
||||
"url": "https://yt3.ggpht.com/ytc/AIdro_n9ALaLETwQH6_2WlXitIaIKV-IqBDWWquvyI2jucNAZaQ=s48-c-k-c0x00000000-no-cc-rj-rp"
|
||||
}
|
||||
]
|
||||
},
|
||||
"avatarImageSize": "AVATAR_SIZE_XS"
|
||||
}
|
||||
}
|
||||
],
|
||||
"text": {
|
||||
"content": "Chaosflo44"
|
||||
},
|
||||
"rendererContext": {
|
||||
"commandContext": {
|
||||
"onTap": {
|
||||
"innertubeCommand": {
|
||||
"browseEndpoint": {
|
||||
"browseId": "UCQM0bS4_04-Y4JuYrgmnpZQ",
|
||||
"browseEndpointContextSupportedConfigs": {
|
||||
"browseEndpointContextMusicConfig": {
|
||||
"pageType": "MUSIC_PAGE_TYPE_USER_CHANNEL"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## [19] Music artist album groups reordered
|
||||
|
||||
- **Encountered on:** 13.01.2025
|
||||
- **Impact:** 🟢 Low
|
||||
- **Endpoint:** browse (YTM)
|
||||
- **Status:** Common (10%)
|
||||
|
||||
YouTube Music used to group artist albums into 2 rows: "Albums" and "Singles".
|
||||
|
||||
These groups were changed into "Albums" and "Singles & EPs". Now the "Album" label is
|
||||
omitted for albums in their group, while singles and EPs have a label with their type.
|
||||
|
||||
## [20] Music continuation item renderer
|
||||
|
||||
- **Encountered on:** 25.01.2025
|
||||
- **Impact:** 🟢 Low
|
||||
- **Endpoint:** browse (YTM)
|
||||
- **Status:** Common (4%)
|
||||
|
||||
YouTube Music now uses a `continuationItemRenderer` for music playlists instead of
|
||||
putting the continuations in a separate attribute of the MusicShelf.
|
||||
|
||||
The continuation response now uses a `onResponseReceivedActions` field for its music
|
||||
items.
|
||||
|
||||
YouTube Music now also sends a random 16-character string as a `clientScreenNonce` in
|
||||
the request context. This is not mandatory though.
|
||||
|
|
|
@ -16,7 +16,7 @@ The pot token is base64-formatted and usually starts with a M
|
|||
|
||||
`MnToZ2brHmyo0ehfKtK_EWUq60dPYDXksNX_UsaniM_Uj6zbtiIZujCHY02hr7opxB_n3XHetJQCBV9cnNHovuhvDqrjfxsKR-sjn-eIxqv3qOZKphvyDpQzlYBnT2AXK41R-ti6iPonrvlvKIASNmYX2lhsEg==`
|
||||
|
||||
The token is generated from YouTubes Botguard script. The token is bound to the visitor data ID
|
||||
The token is generated from YouTubes Botguard script. The token is bound to the visitor data cookie
|
||||
used to fetch the player data.
|
||||
|
||||
This feature has been A/B-tested for a few weeks. During that time, refetching the player in case
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:best-practices", ":preserveSemverRanges"],
|
||||
"extends": [
|
||||
"config:best-practices",
|
||||
":semanticCommitTypeAll(chore)"
|
||||
],
|
||||
"semanticCommits": "enabled",
|
||||
"automerge": true,
|
||||
"automergeStrategy": "squash",
|
||||
|
|
21
src/cache.rs
21
src/cache.rs
|
@ -16,8 +16,7 @@
|
|||
//! the cache as a JSON file.
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::Write,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
|
@ -69,21 +68,7 @@ impl Default for FileStorage {
|
|||
|
||||
impl CacheStorage for FileStorage {
|
||||
fn write(&self, data: &str) {
|
||||
fn _write(path: &Path, data: &str) -> Result<(), std::io::Error> {
|
||||
let mut f = File::create(path)?;
|
||||
// Set cache file permissions to 0600 on Unix-based systems
|
||||
#[cfg(target_family = "unix")]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let metadata = f.metadata()?;
|
||||
let mut permissions = metadata.permissions();
|
||||
permissions.set_mode(0o600);
|
||||
std::fs::set_permissions(path, permissions)?;
|
||||
}
|
||||
f.write_all(data.as_bytes())
|
||||
}
|
||||
|
||||
_write(&self.path, data).unwrap_or_else(|e| {
|
||||
fs::write(&self.path, data).unwrap_or_else(|e| {
|
||||
error!(
|
||||
"Could not write cache to file `{}`. Error: {}",
|
||||
self.path.to_string_lossy(),
|
||||
|
@ -97,7 +82,7 @@ impl CacheStorage for FileStorage {
|
|||
return None;
|
||||
}
|
||||
|
||||
match std::fs::read_to_string(&self.path) {
|
||||
match fs::read_to_string(&self.path) {
|
||||
Ok(data) => Some(data),
|
||||
Err(e) => {
|
||||
error!(
|
||||
|
|
|
@ -9,16 +9,14 @@ use crate::{
|
|||
error::{Error, ExtractionError},
|
||||
model::{
|
||||
paginator::{ContinuationEndpoint, Paginator},
|
||||
Channel, ChannelInfo, PlaylistItem, Verification, VideoItem,
|
||||
Channel, ChannelInfo, PlaylistItem, VideoItem,
|
||||
},
|
||||
param::{ChannelOrder, ChannelVideoTab, Language},
|
||||
serializer::{text::TextComponent, MapResult},
|
||||
util::{self, timeago, ProtoBuilder},
|
||||
};
|
||||
|
||||
use super::{
|
||||
response, ClientType, MapRespCtx, MapRespOptions, MapResponse, QContinuation, RustyPipeQuery,
|
||||
};
|
||||
use super::{response, ClientType, MapRespCtx, MapResponse, QContinuation, RustyPipeQuery};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -180,16 +178,12 @@ impl RustyPipeQuery {
|
|||
continuation: &channel_info_ctoken(channel_id, &random_target()),
|
||||
};
|
||||
|
||||
self.execute_request_ctx::<response::ChannelAbout, _, _>(
|
||||
self.execute_request::<response::ChannelAbout, _, _>(
|
||||
ClientType::Desktop,
|
||||
"channel_info",
|
||||
channel_id,
|
||||
"browse",
|
||||
&request_body,
|
||||
MapRespOptions {
|
||||
unlocalized: true,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
@ -230,7 +224,6 @@ impl MapResponse<Channel<Paginator<VideoItem>>> for response::Channel {
|
|||
mapper.ctoken,
|
||||
visitor_data,
|
||||
ContinuationEndpoint::Browse,
|
||||
false,
|
||||
);
|
||||
|
||||
Ok(MapResult {
|
||||
|
@ -280,7 +273,7 @@ impl MapResponse<Channel<Paginator<PlaylistItem>>> for response::Channel {
|
|||
|
||||
impl MapResponse<ChannelInfo> for response::ChannelAbout {
|
||||
fn map_response(self, ctx: &MapRespCtx<'_>) -> Result<MapResult<ChannelInfo>, ExtractionError> {
|
||||
// Channel info is always fetched in English. There is no localized data
|
||||
// Channel info is always fetched in English. There is no localized data there
|
||||
// and it allows parsing the country name.
|
||||
let lang = Language::En;
|
||||
|
||||
|
@ -335,7 +328,7 @@ impl MapResponse<ChannelInfo> for response::ChannelAbout {
|
|||
.video_count_text
|
||||
.and_then(|txt| util::parse_numeric_or_warn(&txt, &mut warnings)),
|
||||
create_date: about.joined_date_text.and_then(|txt| {
|
||||
timeago::parse_textual_date_or_warn(lang, ctx.utc_offset, &txt, &mut warnings)
|
||||
timeago::parse_textual_date_or_warn(lang, &txt, &mut warnings)
|
||||
.map(OffsetDateTime::date)
|
||||
}),
|
||||
view_count: about
|
||||
|
@ -490,7 +483,7 @@ fn map_channel(
|
|||
.avatar_view_model
|
||||
.image
|
||||
.into(),
|
||||
verification: hdata.title.map(Verification::from).unwrap_or_default(),
|
||||
verification: hdata.title.into(),
|
||||
description: metadata.description,
|
||||
tags: microformat.microformat_data_renderer.tags,
|
||||
banner: hdata.banner.image_banner_view_model.image.into(),
|
||||
|
@ -777,10 +770,8 @@ mod tests {
|
|||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::base("base")]
|
||||
#[case::lockup("20241109_lockup")]
|
||||
fn map_channel_playlists(#[case] name: &str) {
|
||||
let json_path = path!(*TESTFILES / "channel" / format!("channel_playlists_{name}.json"));
|
||||
fn map_channel_playlists() {
|
||||
let json_path = path!(*TESTFILES / "channel" / "channel_playlists.json");
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
||||
let channel: response::Channel =
|
||||
|
@ -794,7 +785,7 @@ mod tests {
|
|||
"deserialization/mapping warnings: {:?}",
|
||||
map_res.warnings
|
||||
);
|
||||
insta::assert_ron_snapshot!(format!("map_channel_playlists_{name}"), map_res.c);
|
||||
insta::assert_ron_snapshot!("map_channel_playlists", map_res.c);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::fmt::Debug;
|
|||
use crate::{
|
||||
error::{Error, ExtractionError},
|
||||
model::ChannelRss,
|
||||
report::Report,
|
||||
report::{Report, RustyPipeInfo},
|
||||
util,
|
||||
};
|
||||
|
||||
|
@ -45,7 +45,7 @@ impl RustyPipeQuery {
|
|||
Err(e) => {
|
||||
if let Some(reporter) = &self.client.inner.reporter {
|
||||
let report = Report {
|
||||
info: self.rp_info(),
|
||||
info: RustyPipeInfo::new(Some(self.opts.lang)),
|
||||
level: crate::report::Level::ERR,
|
||||
operation: "channel_rss",
|
||||
error: Some(e.to_string()),
|
||||
|
|
1236
src/client/mod.rs
1236
src/client/mod.rs
File diff suppressed because it is too large
Load diff
|
@ -5,14 +5,10 @@ use regex::Regex;
|
|||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
client::{
|
||||
response::{music_item::map_album_type, url_endpoint::NavigationEndpoint},
|
||||
MapRespOptions, QContinuation,
|
||||
},
|
||||
client::{response::url_endpoint::NavigationEndpoint, MapRespCtxSource, QContinuation},
|
||||
error::{Error, ExtractionError},
|
||||
model::{
|
||||
paginator::Paginator, traits::FromYtItem, AlbumItem, AlbumType, ArtistId, MusicArtist,
|
||||
MusicItem,
|
||||
paginator::Paginator, traits::FromYtItem, AlbumItem, ArtistId, MusicArtist, MusicItem,
|
||||
},
|
||||
param::{AlbumFilter, AlbumOrder},
|
||||
serializer::MapResult,
|
||||
|
@ -113,9 +109,8 @@ impl RustyPipeQuery {
|
|||
artist_id,
|
||||
"browse",
|
||||
&request_body,
|
||||
MapRespOptions {
|
||||
MapRespCtxSource {
|
||||
artist: Some(first_page.artist.clone()),
|
||||
visitor_data: first_page.visitor_data.as_deref(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
|
@ -179,7 +174,8 @@ fn map_artist_page(
|
|||
.contents
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|c| c.tab_renderer.content.section_list_renderer.contents)
|
||||
.and_then(|tab| tab.tab_renderer.content)
|
||||
.map(|c| c.section_list_renderer.contents)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut mapper = MusicListMapper::with_artist(
|
||||
|
@ -210,12 +206,11 @@ fn map_artist_page(
|
|||
}
|
||||
}
|
||||
}
|
||||
mapper.album_type = AlbumType::Single;
|
||||
|
||||
mapper.map_response(shelf.contents);
|
||||
}
|
||||
response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf) => {
|
||||
let mut extendable_albums = false;
|
||||
mapper.album_type = AlbumType::Single;
|
||||
if let Some(h) = shelf.header {
|
||||
if let Some(button) = h
|
||||
.music_carousel_shelf_basic_header_renderer
|
||||
|
@ -254,12 +249,6 @@ fn map_artist_page(
|
|||
}
|
||||
}
|
||||
}
|
||||
mapper.album_type = map_album_type(
|
||||
h.music_carousel_shelf_basic_header_renderer
|
||||
.title
|
||||
.first_str(),
|
||||
ctx.lang,
|
||||
);
|
||||
}
|
||||
|
||||
if !skip_extendables || !extendable_albums {
|
||||
|
@ -330,7 +319,6 @@ struct FirstAlbumPage {
|
|||
albums: Vec<AlbumItem>,
|
||||
ctoken: Option<String>,
|
||||
artist: ArtistId,
|
||||
visitor_data: Option<String>,
|
||||
}
|
||||
|
||||
impl MapResponse<FirstAlbumPage> for response::MusicArtistAlbums {
|
||||
|
@ -384,7 +372,6 @@ impl MapResponse<FirstAlbumPage> for response::MusicArtistAlbums {
|
|||
albums: mapped.c.albums,
|
||||
ctoken,
|
||||
artist: artist_id,
|
||||
visitor_data: ctx.visitor_data.map(str::to_owned),
|
||||
},
|
||||
warnings: mapped.warnings,
|
||||
})
|
||||
|
@ -425,7 +412,6 @@ mod tests {
|
|||
#[case::only_singles("only_singles", "UCfwCE5VhPMGxNPFxtVv7lRw")]
|
||||
#[case::no_artist("no_artist", "UCh8gHdtzO2tXd593_bjErWg")]
|
||||
#[case::only_more_singles("only_more_singles", "UC0aXrjVxG5pZr99v77wZdPQ")]
|
||||
#[case::grouped_albums("20250113_grouped_albums", "UCOR4_bSVIXPsGa4BbCSt60Q")]
|
||||
fn map_music_artist(#[case] name: &str, #[case] id: &str) {
|
||||
let json_path = path!(*TESTFILES / "music_artist" / format!("artist_{name}.json"));
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
|
|
@ -91,7 +91,7 @@ impl MapResponse<MusicCharts> for response::MusicCharts {
|
|||
.and_then(|btn| btn.button_renderer.navigation_endpoint.music_page())
|
||||
.map(|mp| (mp.typ, mp.id))
|
||||
}) {
|
||||
Some((MusicPageType::Playlist { .. }, id)) => {
|
||||
Some((MusicPageType::Playlist, id)) => {
|
||||
// Top music videos (first shelf with associated playlist)
|
||||
if top_playlist_id.is_none() {
|
||||
mapper_top.map_response(shelf.contents);
|
||||
|
@ -113,12 +113,12 @@ impl MapResponse<MusicCharts> for response::MusicCharts {
|
|||
});
|
||||
|
||||
let mapped_top = mapper_top.conv_items::<TrackItem>();
|
||||
let mapped_trending = mapper_trending.conv_items::<TrackItem>();
|
||||
let mapped_other = mapper_other.group_items();
|
||||
let mut mapped_trending = mapper_trending.conv_items::<TrackItem>();
|
||||
let mut mapped_other = mapper_other.group_items();
|
||||
|
||||
let mut warnings = mapped_top.warnings;
|
||||
warnings.extend(mapped_trending.warnings);
|
||||
warnings.extend(mapped_other.warnings);
|
||||
warnings.append(&mut mapped_trending.warnings);
|
||||
warnings.append(&mut mapped_other.warnings);
|
||||
|
||||
Ok(MapResult {
|
||||
c: MusicCharts {
|
||||
|
|
|
@ -37,7 +37,7 @@ struct QRadio<'a> {
|
|||
}
|
||||
|
||||
impl RustyPipeQuery {
|
||||
/// Get the metadata of a YouTube Music track
|
||||
/// Get the metadata of a YouTube music track
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn music_details<S: AsRef<str> + Debug>(
|
||||
&self,
|
||||
|
@ -61,7 +61,7 @@ impl RustyPipeQuery {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Get the lyrics of a YouTube Music track
|
||||
/// Get the lyrics of a YouTube music track
|
||||
///
|
||||
/// The `lyrics_id` has to be obtained using [`RustyPipeQuery::music_details`].
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
|
@ -269,14 +269,7 @@ impl MapResponse<Paginator<TrackItem>> for response::MusicDetails {
|
|||
.map(|c| c.next_continuation_data.continuation);
|
||||
|
||||
Ok(MapResult {
|
||||
c: Paginator::new_ext(
|
||||
None,
|
||||
tracks,
|
||||
ctoken,
|
||||
None,
|
||||
ContinuationEndpoint::MusicNext,
|
||||
false,
|
||||
),
|
||||
c: Paginator::new_ext(None, tracks, ctoken, None, ContinuationEndpoint::MusicNext),
|
||||
warnings,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,14 +6,12 @@ use crate::{
|
|||
model::{
|
||||
paginator::{ContinuationEndpoint, Paginator},
|
||||
richtext::RichText,
|
||||
AlbumId, ChannelId, MusicAlbum, MusicPlaylist, TrackItem, TrackType,
|
||||
AlbumId, ChannelId, MusicAlbum, MusicPlaylist, TrackItem,
|
||||
},
|
||||
serializer::{text::TextComponents, MapResult},
|
||||
util::{self, TryRemove, DOT_SEPARATOR},
|
||||
};
|
||||
|
||||
use self::response::url_endpoint::MusicPageType;
|
||||
|
||||
use super::{
|
||||
response::{
|
||||
self,
|
||||
|
@ -87,7 +85,7 @@ impl RustyPipeQuery {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, track)| {
|
||||
if track.track_type.is_video() {
|
||||
if track.is_video {
|
||||
Some((i, track.name.clone()))
|
||||
} else {
|
||||
None
|
||||
|
@ -104,7 +102,7 @@ impl RustyPipeQuery {
|
|||
|
||||
for (i, title) in to_replace {
|
||||
let found_track = playlist.tracks.items.iter().find_map(|track| {
|
||||
if track.name == title && track.track_type.is_track() {
|
||||
if track.name == title && !track.is_video {
|
||||
Some((track.id.clone(), track.duration))
|
||||
} else {
|
||||
None
|
||||
|
@ -115,7 +113,7 @@ impl RustyPipeQuery {
|
|||
if let Some(duration) = duration {
|
||||
album.tracks[i].duration = Some(duration);
|
||||
}
|
||||
album.tracks[i].track_type = TrackType::Track;
|
||||
album.tracks[i].is_video = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,15 +180,13 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
|||
|
||||
let mut mapper = MusicListMapper::new(ctx.lang);
|
||||
mapper.map_response(shelf.contents);
|
||||
let map_res = mapper.conv_items();
|
||||
|
||||
let ctoken = mapper.ctoken.clone().or_else(|| {
|
||||
shelf
|
||||
let ctoken = shelf
|
||||
.continuations
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|cont| cont.next_continuation_data.continuation)
|
||||
});
|
||||
let map_res = mapper.conv_items();
|
||||
.map(|cont| cont.next_continuation_data.continuation);
|
||||
|
||||
let track_count = if ctoken.is_some() {
|
||||
header.as_ref().and_then(|h| {
|
||||
|
@ -217,28 +213,6 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
|||
Some(header) => {
|
||||
let h = header.music_detail_header_renderer;
|
||||
|
||||
let (from_ytm, channel) = match h.facepile {
|
||||
Some(facepile) => {
|
||||
let from_ytm = facepile.avatar_stack_view_model.text.starts_with("YouTube");
|
||||
let channel = facepile
|
||||
.avatar_stack_view_model
|
||||
.renderer_context
|
||||
.command_context
|
||||
.and_then(|c| {
|
||||
c.on_tap
|
||||
.innertube_command
|
||||
.music_page()
|
||||
.filter(|p| p.typ == MusicPageType::User)
|
||||
.map(|p| p.id)
|
||||
})
|
||||
.map(|id| ChannelId {
|
||||
id,
|
||||
name: facepile.avatar_stack_view_model.text,
|
||||
});
|
||||
|
||||
(from_ytm && channel.is_none(), channel)
|
||||
}
|
||||
None => {
|
||||
let st = match h.strapline_text_one {
|
||||
Some(s) => s,
|
||||
None => h.subtitle,
|
||||
|
@ -246,9 +220,6 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
|||
|
||||
let from_ytm = st.0.iter().any(util::is_ytm);
|
||||
let channel = st.0.into_iter().find_map(|c| ChannelId::try_from(c).ok());
|
||||
(from_ytm, channel)
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
from_ytm,
|
||||
|
@ -300,7 +271,6 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
|||
ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
ctx.authenticated,
|
||||
),
|
||||
related_playlists: Paginator::new_ext(
|
||||
None,
|
||||
|
@ -308,7 +278,6 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
|||
related_ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
ctx.authenticated,
|
||||
),
|
||||
},
|
||||
warnings: map_res.warnings,
|
||||
|
@ -515,7 +484,6 @@ mod tests {
|
|||
#[case::nomusic("nomusic", "PL1J-6JOckZtE_P9Xx8D3b2O6w0idhuKBe")]
|
||||
#[case::two_columns("20240228_twoColumns", "RDCLAK5uy_kb7EBi6y3GrtJri4_ZH56Ms786DFEimbM")]
|
||||
#[case::n_album("20240228_album", "OLAK5uy_kdSWBZ-9AZDkYkuy0QCc3p0KO9DEHVNH0")]
|
||||
#[case::facepile("20241125_facepile", "PL1J-6JOckZtE_P9Xx8D3b2O6w0idhuKBe")]
|
||||
fn map_music_playlist(#[case] name: &str, #[case] id: &str) {
|
||||
let json_path = path!(*TESTFILES / "music_playlist" / format!("playlist_{name}.json"));
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
paginator::{ContinuationEndpoint, Paginator},
|
||||
traits::FromYtItem,
|
||||
AlbumItem, ArtistItem, MusicItem, MusicPlaylistItem, MusicSearchResult,
|
||||
MusicSearchSuggestion, TrackItem, UserItem,
|
||||
MusicSearchSuggestion, TrackItem,
|
||||
},
|
||||
param::search_filter::MusicSearchFilter,
|
||||
serializer::MapResult,
|
||||
|
@ -57,7 +57,7 @@ impl RustyPipeQuery {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Search YouTube Music and return items of all types
|
||||
/// Search YouTube music and return items of all types
|
||||
pub async fn music_search_main<S: AsRef<str>>(
|
||||
&self,
|
||||
query: S,
|
||||
|
@ -121,15 +121,6 @@ impl RustyPipeQuery {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Search YouTube Music users
|
||||
pub async fn music_search_users<S: AsRef<str>>(
|
||||
&self,
|
||||
query: S,
|
||||
) -> Result<MusicSearchResult<UserItem>, Error> {
|
||||
self.music_search(query, Some(MusicSearchFilter::Users))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get YouTube Music search suggestions
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn music_search_suggestion<S: AsRef<str> + Debug>(
|
||||
|
@ -189,7 +180,6 @@ impl<T: FromYtItem> MapResponse<MusicSearchResult<T>> for response::MusicSearch
|
|||
response::music_search::ItemSection::None => {}
|
||||
});
|
||||
|
||||
let ctoken = ctoken.or(mapper.ctoken.clone());
|
||||
let map_res = mapper.conv_items();
|
||||
|
||||
Ok(MapResult {
|
||||
|
@ -200,7 +190,6 @@ impl<T: FromYtItem> MapResponse<MusicSearchResult<T>> for response::MusicSearch
|
|||
ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::MusicSearch,
|
||||
false,
|
||||
),
|
||||
corrected_query,
|
||||
},
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
use std::fmt::Debug;
|
||||
|
||||
use crate::{
|
||||
client::{
|
||||
response::{self, music_item::MusicListMapper},
|
||||
ClientType, MapResponse, QBrowseParams, RustyPipeQuery,
|
||||
},
|
||||
error::{Error, ExtractionError},
|
||||
model::{
|
||||
paginator::{ContinuationEndpoint, Paginator},
|
||||
AlbumItem, ArtistItem, HistoryItem, MusicPlaylist, MusicPlaylistItem, TrackItem,
|
||||
},
|
||||
serializer::MapResult,
|
||||
};
|
||||
|
||||
use super::{MapRespCtx, MapRespOptions, QContinuation};
|
||||
|
||||
impl RustyPipeQuery {
|
||||
/// Get a list of tracks from YouTube Music which the current user recently played
|
||||
///
|
||||
/// Requires authentication cookies.
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn music_history(&self) -> Result<Paginator<HistoryItem<TrackItem>>, Error> {
|
||||
let request_body = QBrowseParams {
|
||||
browse_id: "FEmusic_history",
|
||||
params: "oggECgIIAQ%3D%3D",
|
||||
};
|
||||
|
||||
self.clone()
|
||||
.authenticated()
|
||||
.execute_request::<response::MusicHistory, _, _>(
|
||||
ClientType::DesktopMusic,
|
||||
"music_history",
|
||||
"",
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get more YouTube Music history items from the given continuation token
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn music_history_continuation<S: AsRef<str> + Debug>(
|
||||
&self,
|
||||
ctoken: S,
|
||||
visitor_data: Option<&str>,
|
||||
) -> Result<Paginator<HistoryItem<TrackItem>>, Error> {
|
||||
let ctoken = ctoken.as_ref();
|
||||
let request_body = QContinuation {
|
||||
continuation: ctoken,
|
||||
};
|
||||
|
||||
self.clone()
|
||||
.authenticated()
|
||||
.execute_request_ctx::<response::MusicContinuation, _, _>(
|
||||
ClientType::Desktop,
|
||||
"history_continuation",
|
||||
ctoken,
|
||||
"browse",
|
||||
&request_body,
|
||||
MapRespOptions {
|
||||
visitor_data,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a list of YouTube Music artists which the current user subscribed to
|
||||
///
|
||||
/// Requires authentication cookies.
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn music_saved_artists(&self) -> Result<Paginator<ArtistItem>, Error> {
|
||||
self.clone()
|
||||
.authenticated()
|
||||
.continuation(
|
||||
"4qmFsgIyEh5GRW11c2ljX2xpYnJhcnlfY29ycHVzX2FydGlzdHMaEGdnTUdLZ1FJQUJBQm9BWUI%3D",
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a list of YouTube Music albums which the current user has added to their collection
|
||||
///
|
||||
/// Requires authentication cookies.
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn music_saved_albums(&self) -> Result<Paginator<AlbumItem>, Error> {
|
||||
self.clone()
|
||||
.authenticated()
|
||||
.continuation(
|
||||
"4qmFsgIoEhRGRW11c2ljX2xpa2VkX2FsYnVtcxoQZ2dNR0tnUUlBQkFCb0FZQg%3D%3D",
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a list of YouTube Music tracks which the current user has added to their collection
|
||||
///
|
||||
/// Contains both liked tracks and tracks from saved albums.
|
||||
///
|
||||
/// Requires authentication cookies.
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn music_saved_tracks(&self) -> Result<Paginator<TrackItem>, Error> {
|
||||
self.clone()
|
||||
.authenticated()
|
||||
.continuation(
|
||||
"4qmFsgIoEhRGRW11c2ljX2xpa2VkX3ZpZGVvcxoQZ2dNR0tnUUlBQkFCb0FZQg%3D%3D",
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get a list of YouTube Music playlists which the current user has added to their collection
|
||||
///
|
||||
/// Requires authentication cookies.
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn music_saved_playlists(&self) -> Result<Paginator<MusicPlaylistItem>, Error> {
|
||||
self.clone()
|
||||
.authenticated()
|
||||
.continuation(
|
||||
"4qmFsgIrEhdGRW11c2ljX2xpa2VkX3BsYXlsaXN0cxoQZ2dNR0tnUUlBQkFCb0FZQg%3D%3D",
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get all liked YouTube Music tracks of the logged-in user
|
||||
///
|
||||
/// The difference to [`RustyPipeQuery::music_saved_tracks`] is that this function only returns
|
||||
/// tracks that were explicitly liked by the user.
|
||||
///
|
||||
/// Requires authentication cookies.
|
||||
pub async fn music_liked_tracks(&self) -> Result<MusicPlaylist, Error> {
|
||||
self.clone()
|
||||
.authenticated()
|
||||
.music_playlist("LM")
|
||||
.await
|
||||
.map_err(crate::util::map_internal_playlist_err)
|
||||
}
|
||||
}
|
||||
|
||||
impl MapResponse<Paginator<HistoryItem<TrackItem>>> for response::MusicHistory {
|
||||
fn map_response(
|
||||
self,
|
||||
ctx: &MapRespCtx<'_>,
|
||||
) -> Result<MapResult<Paginator<HistoryItem<TrackItem>>>, ExtractionError> {
|
||||
let contents = match self.contents {
|
||||
response::music_playlist::Contents::SingleColumnBrowseResultsRenderer(c) => {
|
||||
c.contents
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or(ExtractionError::InvalidData("no content".into()))?
|
||||
.tab_renderer
|
||||
.content
|
||||
.section_list_renderer
|
||||
}
|
||||
response::music_playlist::Contents::TwoColumnBrowseResultsRenderer {
|
||||
secondary_contents,
|
||||
..
|
||||
} => secondary_contents.section_list_renderer,
|
||||
};
|
||||
|
||||
let mut map_res = MapResult::default();
|
||||
|
||||
for shelf in contents.contents {
|
||||
let shelf = if let response::music_item::ItemSection::MusicShelfRenderer(s) = shelf {
|
||||
s
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
let mut mapper = MusicListMapper::new(ctx.lang);
|
||||
mapper.map_response(shelf.contents);
|
||||
mapper.conv_history_items(shelf.title, ctx.utc_offset, &mut map_res);
|
||||
}
|
||||
|
||||
let ctoken = contents
|
||||
.continuations
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|c| c.next_continuation_data.continuation);
|
||||
|
||||
Ok(MapResult {
|
||||
c: Paginator::new_ext(
|
||||
None,
|
||||
map_res.c,
|
||||
ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
true,
|
||||
),
|
||||
warnings: map_res.warnings,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs::File, io::BufReader};
|
||||
|
||||
use path_macro::path;
|
||||
|
||||
use crate::util::tests::TESTFILES;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn map_history() {
|
||||
let json_path = path!(*TESTFILES / "music_userdata" / "music_history.json");
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
||||
let history: response::MusicHistory =
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||
let map_res = history.map_response(&MapRespCtx::test("")).unwrap();
|
||||
|
||||
assert!(
|
||||
map_res.warnings.is_empty(),
|
||||
"deserialization/mapping warnings: {:?}",
|
||||
map_res.warnings
|
||||
);
|
||||
insta::assert_ron_snapshot!(map_res.c, {
|
||||
".items[].playback_date" => "[date]",
|
||||
});
|
||||
}
|
||||
}
|
|
@ -8,15 +8,9 @@ use crate::model::{
|
|||
};
|
||||
use crate::serializer::MapResult;
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
use crate::model::{HistoryItem, TrackItem, VideoItem};
|
||||
|
||||
use super::response::{
|
||||
music_item::{map_queue_item, MusicListMapper, PlaylistPanelVideo},
|
||||
YouTubeListItem,
|
||||
};
|
||||
use super::response::music_item::{map_queue_item, MusicListMapper, PlaylistPanelVideo};
|
||||
use super::{
|
||||
response, ClientType, MapRespCtx, MapRespOptions, MapResponse, QContinuation, RustyPipeQuery,
|
||||
response, ClientType, MapRespCtx, MapRespCtxSource, MapResponse, QContinuation, RustyPipeQuery,
|
||||
};
|
||||
|
||||
impl RustyPipeQuery {
|
||||
|
@ -41,7 +35,7 @@ impl RustyPipeQuery {
|
|||
ctoken,
|
||||
endpoint.as_str(),
|
||||
&request_body,
|
||||
MapRespOptions {
|
||||
MapRespCtxSource {
|
||||
visitor_data,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -61,7 +55,7 @@ impl RustyPipeQuery {
|
|||
ctoken,
|
||||
endpoint.as_str(),
|
||||
&request_body,
|
||||
MapRespOptions {
|
||||
MapRespCtxSource {
|
||||
visitor_data,
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -83,7 +77,6 @@ fn map_yt_paginator<T: FromYtItem>(
|
|||
ctoken: p.ctoken,
|
||||
visitor_data: p.visitor_data,
|
||||
endpoint,
|
||||
authenticated: p.authenticated,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,12 +90,15 @@ fn map_ytm_paginator<T: FromYtItem>(
|
|||
ctoken: p.ctoken,
|
||||
visitor_data: p.visitor_data,
|
||||
endpoint,
|
||||
authenticated: p.authenticated,
|
||||
}
|
||||
}
|
||||
|
||||
fn continuation_items(response: response::Continuation) -> MapResult<Vec<YouTubeListItem>> {
|
||||
response
|
||||
impl MapResponse<Paginator<YouTubeItem>> for response::Continuation {
|
||||
fn map_response(
|
||||
self,
|
||||
ctx: &MapRespCtx<'_>,
|
||||
) -> Result<MapResult<Paginator<YouTubeItem>>, ExtractionError> {
|
||||
let items = self
|
||||
.on_response_received_actions
|
||||
.and_then(|actions| {
|
||||
actions
|
||||
|
@ -115,33 +111,16 @@ fn continuation_items(response: response::Continuation) -> MapResult<Vec<YouTube
|
|||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
response
|
||||
.continuation_contents
|
||||
self.continuation_contents
|
||||
.map(|contents| contents.rich_grid_continuation.contents)
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
impl MapResponse<Paginator<YouTubeItem>> for response::Continuation {
|
||||
fn map_response(
|
||||
self,
|
||||
ctx: &MapRespCtx<'_>,
|
||||
) -> Result<MapResult<Paginator<YouTubeItem>>, ExtractionError> {
|
||||
let estimated_results = self.estimated_results;
|
||||
let items = continuation_items(self);
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut mapper = response::YouTubeListMapper::<YouTubeItem>::new(ctx.lang);
|
||||
mapper.map_response(items);
|
||||
|
||||
Ok(MapResult {
|
||||
c: Paginator::new_ext(
|
||||
estimated_results,
|
||||
mapper.items,
|
||||
mapper.ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::Browse,
|
||||
ctx.authenticated,
|
||||
),
|
||||
c: Paginator::new(self.estimated_results, mapper.items, mapper.ctoken),
|
||||
warnings: mapper.warnings,
|
||||
})
|
||||
}
|
||||
|
@ -202,122 +181,14 @@ impl MapResponse<Paginator<MusicItem>> for response::MusicContinuation {
|
|||
None => {}
|
||||
}
|
||||
|
||||
for a in self.on_response_received_actions {
|
||||
mapper.map_response(a.append_continuation_items_action.continuation_items);
|
||||
}
|
||||
|
||||
let ctoken = mapper.ctoken.clone().or_else(|| {
|
||||
continuations
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|cont| cont.next_continuation_data.continuation)
|
||||
});
|
||||
let map_res = mapper.items();
|
||||
|
||||
Ok(MapResult {
|
||||
c: Paginator::new_ext(
|
||||
None,
|
||||
map_res.c,
|
||||
ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
ctx.authenticated,
|
||||
),
|
||||
warnings: map_res.warnings,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
impl MapResponse<Paginator<HistoryItem<VideoItem>>> for response::Continuation {
|
||||
fn map_response(
|
||||
self,
|
||||
ctx: &MapRespCtx<'_>,
|
||||
) -> Result<MapResult<Paginator<HistoryItem<VideoItem>>>, ExtractionError> {
|
||||
let mut map_res = MapResult::default();
|
||||
let mut ctoken = None;
|
||||
|
||||
let items = continuation_items(self);
|
||||
for item in items.c {
|
||||
match item {
|
||||
response::YouTubeListItem::ItemSectionRenderer { header, contents } => {
|
||||
let mut mapper = response::YouTubeListMapper::<VideoItem>::new(ctx.lang);
|
||||
mapper.map_response(contents);
|
||||
mapper.conv_history_items(
|
||||
header.map(|h| h.item_section_header_renderer.title),
|
||||
ctx.utc_offset,
|
||||
&mut map_res,
|
||||
);
|
||||
}
|
||||
response::YouTubeListItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
} => {
|
||||
if ctoken.is_none() {
|
||||
ctoken = Some(continuation_endpoint.continuation_command.token);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MapResult {
|
||||
c: Paginator::new_ext(
|
||||
None,
|
||||
map_res.c,
|
||||
ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::Browse,
|
||||
ctx.authenticated,
|
||||
),
|
||||
warnings: map_res.warnings,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
impl MapResponse<Paginator<HistoryItem<TrackItem>>> for response::MusicContinuation {
|
||||
fn map_response(
|
||||
self,
|
||||
ctx: &MapRespCtx<'_>,
|
||||
) -> Result<MapResult<Paginator<HistoryItem<TrackItem>>>, ExtractionError> {
|
||||
let mut map_res = MapResult::default();
|
||||
let mut continuations = Vec::new();
|
||||
|
||||
let mut map_shelf = |shelf: response::music_item::MusicShelf| {
|
||||
let mut mapper = MusicListMapper::new(ctx.lang);
|
||||
mapper.map_response(shelf.contents);
|
||||
mapper.conv_history_items(shelf.title, ctx.utc_offset, &mut map_res);
|
||||
continuations.extend(shelf.continuations);
|
||||
};
|
||||
|
||||
match self.continuation_contents {
|
||||
Some(response::music_item::ContinuationContents::MusicShelfContinuation(shelf)) => {
|
||||
map_shelf(shelf);
|
||||
}
|
||||
Some(response::music_item::ContinuationContents::SectionListContinuation(contents)) => {
|
||||
for c in contents.contents {
|
||||
if let response::music_item::ItemSection::MusicShelfRenderer(shelf) = c {
|
||||
map_shelf(shelf);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let ctoken = continuations
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|cont| cont.next_continuation_data.continuation);
|
||||
|
||||
Ok(MapResult {
|
||||
c: Paginator::new_ext(
|
||||
None,
|
||||
map_res.c,
|
||||
ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::MusicBrowse,
|
||||
ctx.authenticated,
|
||||
),
|
||||
c: Paginator::new(None, map_res.c, ctoken),
|
||||
warnings: map_res.warnings,
|
||||
})
|
||||
}
|
||||
|
@ -327,18 +198,12 @@ impl<T: FromYtItem> Paginator<T> {
|
|||
/// Get the next page from the paginator (or `None` if the paginator is exhausted)
|
||||
pub async fn next<Q: AsRef<RustyPipeQuery>>(&self, query: Q) -> Result<Option<Self>, Error> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => {
|
||||
let q = if self.authenticated {
|
||||
&query.as_ref().clone().authenticated()
|
||||
} else {
|
||||
query.as_ref()
|
||||
};
|
||||
|
||||
Some(
|
||||
q.continuation(ctoken, self.endpoint, self.visitor_data.as_deref())
|
||||
Some(ctoken) => Some(
|
||||
query
|
||||
.as_ref()
|
||||
.continuation(ctoken, self.endpoint, self.visitor_data.as_deref())
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
@ -427,40 +292,6 @@ impl Paginator<Comment> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||
impl Paginator<HistoryItem<VideoItem>> {
|
||||
/// Get the next page from the paginator (or `None` if the paginator is exhausted)
|
||||
pub async fn next<Q: AsRef<RustyPipeQuery>>(&self, query: Q) -> Result<Option<Self>, Error> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some(
|
||||
query
|
||||
.as_ref()
|
||||
.history_continuation(ctoken, self.visitor_data.as_deref())
|
||||
.await?,
|
||||
),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||
impl Paginator<HistoryItem<TrackItem>> {
|
||||
/// Get the next page from the paginator (or `None` if the paginator is exhausted)
|
||||
pub async fn next<Q: AsRef<RustyPipeQuery>>(&self, query: Q) -> Result<Option<Self>, Error> {
|
||||
Ok(match &self.ctoken {
|
||||
Some(ctoken) => Some(
|
||||
query
|
||||
.as_ref()
|
||||
.music_history_continuation(ctoken, self.visitor_data.as_deref())
|
||||
.await?,
|
||||
),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! paginator {
|
||||
($entity_type:ty) => {
|
||||
impl Paginator<$entity_type> {
|
||||
|
@ -542,12 +373,6 @@ macro_rules! paginator {
|
|||
}
|
||||
|
||||
paginator!(Comment);
|
||||
#[cfg(feature = "userdata")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||
paginator!(HistoryItem<VideoItem>);
|
||||
#[cfg(feature = "userdata")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||
paginator!(HistoryItem<TrackItem>);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
@ -558,10 +383,7 @@ mod tests {
|
|||
|
||||
use super::*;
|
||||
use crate::{
|
||||
model::{
|
||||
AlbumItem, ArtistItem, ChannelItem, MusicPlaylistItem, PlaylistItem, TrackItem,
|
||||
VideoItem,
|
||||
},
|
||||
model::{MusicPlaylistItem, PlaylistItem, TrackItem, VideoItem},
|
||||
util::tests::TESTFILES,
|
||||
};
|
||||
|
||||
|
@ -632,32 +454,10 @@ mod tests {
|
|||
insta::assert_ron_snapshot!(format!("map_{name}"), paginator);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::subscriptions("subscriptions", path!("userdata" / "subscriptions.json"))]
|
||||
fn map_continuation_channels(#[case] name: &str, #[case] path: PathBuf) {
|
||||
let json_path = path!(*TESTFILES / path);
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
||||
let items: response::Continuation =
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||
let map_res: MapResult<Paginator<YouTubeItem>> =
|
||||
items.map_response(&MapRespCtx::test("")).unwrap();
|
||||
let paginator: Paginator<ChannelItem> =
|
||||
map_yt_paginator(map_res.c, ContinuationEndpoint::Browse);
|
||||
|
||||
assert!(
|
||||
map_res.warnings.is_empty(),
|
||||
"deserialization/mapping warnings: {:?}",
|
||||
map_res.warnings
|
||||
);
|
||||
insta::assert_ron_snapshot!(format!("map_{name}"), paginator);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::playlist_tracks("playlist_tracks", path!("music_playlist" / "playlist_cont.json"))]
|
||||
#[case::search_tracks("search_tracks", path!("music_search" / "tracks_cont.json"))]
|
||||
#[case::radio_tracks("radio_tracks", path!("music_details" / "radio_cont.json"))]
|
||||
#[case::saved_tracks("saved_tracks", path!("music_userdata" / "saved_tracks.json"))]
|
||||
fn map_continuation_tracks(#[case] name: &str, #[case] path: PathBuf) {
|
||||
let json_path = path!(*TESTFILES / path);
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
@ -677,51 +477,8 @@ mod tests {
|
|||
insta::assert_ron_snapshot!(format!("map_{name}"), paginator);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::saved_artists("saved_artists", path!("music_userdata" / "saved_artists.json"))]
|
||||
fn map_continuation_artists(#[case] name: &str, #[case] path: PathBuf) {
|
||||
let json_path = path!(*TESTFILES / path);
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
||||
let items: response::MusicContinuation =
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||
let map_res: MapResult<Paginator<MusicItem>> =
|
||||
items.map_response(&MapRespCtx::test("")).unwrap();
|
||||
let paginator: Paginator<ArtistItem> =
|
||||
map_ytm_paginator(map_res.c, ContinuationEndpoint::MusicBrowse);
|
||||
|
||||
assert!(
|
||||
map_res.warnings.is_empty(),
|
||||
"deserialization/mapping warnings: {:?}",
|
||||
map_res.warnings
|
||||
);
|
||||
insta::assert_ron_snapshot!(format!("map_{name}"), paginator);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::saved_albums("saved_albums", path!("music_userdata" / "saved_albums.json"))]
|
||||
fn map_continuation_albums(#[case] name: &str, #[case] path: PathBuf) {
|
||||
let json_path = path!(*TESTFILES / path);
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
||||
let items: response::MusicContinuation =
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||
let map_res: MapResult<Paginator<MusicItem>> =
|
||||
items.map_response(&MapRespCtx::test("")).unwrap();
|
||||
let paginator: Paginator<AlbumItem> =
|
||||
map_ytm_paginator(map_res.c, ContinuationEndpoint::MusicBrowse);
|
||||
|
||||
assert!(
|
||||
map_res.warnings.is_empty(),
|
||||
"deserialization/mapping warnings: {:?}",
|
||||
map_res.warnings
|
||||
);
|
||||
insta::assert_ron_snapshot!(format!("map_{name}"), paginator);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case::playlist_related("playlist_related", path!("music_playlist" / "playlist_related.json"))]
|
||||
#[case::saved_playlists("saved_playlists", path!("music_userdata" / "saved_playlists.json"))]
|
||||
fn map_continuation_music_playlists(#[case] name: &str, #[case] path: PathBuf) {
|
||||
let json_path = path!(*TESTFILES / path);
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
|
|
|
@ -7,16 +7,14 @@ use std::{
|
|||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
deobfuscate::{DeobfData, Deobfuscator},
|
||||
error::{internal::DeobfError, AuthError, Error, ExtractionError, UnavailabilityReason},
|
||||
deobfuscate::Deobfuscator,
|
||||
error::{internal::DeobfError, Error, ExtractionError, UnavailabilityReason},
|
||||
model::{
|
||||
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, DrmLicense,
|
||||
DrmSystem, Frameset, Subtitle, VideoCodec, VideoFormat, VideoPlayer, VideoPlayerDetails,
|
||||
VideoPlayerDrm, VideoStream,
|
||||
traits::QualityOrd, AudioCodec, AudioFormat, AudioStream, AudioTrack, Frameset, Subtitle,
|
||||
VideoCodec, VideoFormat, VideoPlayer, VideoPlayerDetails, VideoStream,
|
||||
},
|
||||
util,
|
||||
};
|
||||
|
@ -26,7 +24,8 @@ use super::{
|
|||
self,
|
||||
player::{self, Format},
|
||||
},
|
||||
ClientType, MapRespCtx, MapRespOptions, MapResponse, MapResult, PoToken, RustyPipeQuery,
|
||||
ClientType, MapRespCtx, MapRespCtxSource, MapResponse, MapResult, RustyPipeQuery,
|
||||
DEFAULT_PLAYER_CLIENT_ORDER,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
@ -35,15 +34,17 @@ struct QPlayer<'a> {
|
|||
/// Website playback context
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
playback_context: Option<QPlaybackContext<'a>>,
|
||||
/// Content playback nonce (mobile only, 16 random chars)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
cpn: Option<String>,
|
||||
/// YouTube video ID
|
||||
video_id: &'a str,
|
||||
/// Set to true to allow extraction of streams with sensitive content
|
||||
content_check_ok: bool,
|
||||
/// Probably refers to allowing sensitive content, too
|
||||
racy_check_ok: bool,
|
||||
/// Botguard data
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
service_integrity_dimensions: Option<ServiceIntegrity>,
|
||||
params: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
@ -61,35 +62,10 @@ struct QContentPlaybackContext<'a> {
|
|||
referer: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct QDrmLicense<'a> {
|
||||
drm_system: &'a str,
|
||||
video_id: &'a str,
|
||||
cpn: &'a str,
|
||||
session_id: &'a str,
|
||||
license_request: &'a str,
|
||||
drm_params: &'a str,
|
||||
drm_video_feature: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ServiceIntegrity {
|
||||
po_token: String,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PlayerPoToken {
|
||||
visitor_data: Option<String>,
|
||||
session_po_token: Option<PoToken>,
|
||||
content_po_token: Option<ServiceIntegrity>,
|
||||
}
|
||||
|
||||
impl RustyPipeQuery {
|
||||
/// Get YouTube player data (video/audio streams + basic metadata)
|
||||
pub async fn player<S: AsRef<str> + Debug>(&self, video_id: S) -> Result<VideoPlayer, Error> {
|
||||
self.player_from_clients(video_id, self.player_client_order())
|
||||
self.player_from_clients(video_id, DEFAULT_PLAYER_CLIENT_ORDER)
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -97,36 +73,29 @@ impl RustyPipeQuery {
|
|||
///
|
||||
/// The clients are used in the given order. If a client cannot fetch the requested video,
|
||||
/// an attempt is made with the next one.
|
||||
///
|
||||
/// If an age-restricted video is detected, it will automatically use the [`ClientType::TvHtml5Embed`]
|
||||
/// since it is the only one that can circumvent age restrictions.
|
||||
pub async fn player_from_clients<S: AsRef<str> + Debug>(
|
||||
&self,
|
||||
video_id: S,
|
||||
clients: &[ClientType],
|
||||
) -> Result<VideoPlayer, Error> {
|
||||
let video_id = video_id.as_ref();
|
||||
let mut last_e = None;
|
||||
let mut clients_iter = clients.iter().peekable();
|
||||
|
||||
while let Some(client) = clients_iter.next() {
|
||||
if self.opts.auth == Some(true) && !self.auth_enabled(*client) {
|
||||
// If no client has auth enabled, return NoLogin error instead of "no clients"
|
||||
if last_e.is_none() {
|
||||
last_e = Some(Error::Auth(AuthError::NoLogin));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let mut last_e = Error::Other("no clients".into());
|
||||
|
||||
for client in clients {
|
||||
let res = self.player_from_client(video_id, *client).await;
|
||||
match res {
|
||||
Ok(res) => return Ok(res),
|
||||
Err(Error::Extraction(e)) => {
|
||||
if e.use_login() {
|
||||
if let Some(c) = self.auth_enabled_client(clients) {
|
||||
if e.use_login() && self.auth_enabled() {
|
||||
tracing::info!("{e}; fetching player with login");
|
||||
|
||||
match self
|
||||
.clone()
|
||||
.authenticated()
|
||||
.player_from_client(video_id, c)
|
||||
.player_from_client(video_id, *client)
|
||||
.await
|
||||
{
|
||||
Ok(res) => return Ok(res),
|
||||
|
@ -137,51 +106,15 @@ impl RustyPipeQuery {
|
|||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
} else {
|
||||
return Err(Error::Extraction(e));
|
||||
}
|
||||
last_e = Error::Extraction(e);
|
||||
} else if !e.switch_client() {
|
||||
return Err(Error::Extraction(e));
|
||||
}
|
||||
if let Some(next_client) = clients_iter.peek() {
|
||||
tracing::warn!("error fetching player with {client:?} client: {e}; retrying with {next_client:?} client");
|
||||
}
|
||||
last_e = Some(Error::Extraction(e));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Err(last_e.unwrap_or(Error::Other("no clients".into())))
|
||||
}
|
||||
|
||||
async fn get_player_po_token(&self, video_id: &str) -> Result<PlayerPoToken, Error> {
|
||||
if let Some(bg) = &self.client.inner.botguard {
|
||||
let visitor_data = self.get_visitor_data(false).await?;
|
||||
if bg.po_token_cache {
|
||||
let session_token = self.get_session_po_token(&visitor_data).await?;
|
||||
Ok(PlayerPoToken {
|
||||
visitor_data: Some(visitor_data),
|
||||
session_po_token: Some(session_token),
|
||||
content_po_token: None,
|
||||
})
|
||||
} else {
|
||||
let (po_tokens, valid_until) =
|
||||
self.get_po_tokens(&[video_id, &visitor_data]).await?;
|
||||
let mut po_tokens = po_tokens.into_iter();
|
||||
let po_token = po_tokens.next().unwrap();
|
||||
let session_po_token = po_tokens.next().unwrap();
|
||||
Ok(PlayerPoToken {
|
||||
visitor_data: Some(visitor_data),
|
||||
session_po_token: Some(PoToken {
|
||||
po_token: session_po_token,
|
||||
valid_until,
|
||||
}),
|
||||
content_po_token: Some(ServiceIntegrity { po_token }),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Ok(PlayerPoToken::default())
|
||||
}
|
||||
Err(last_e)
|
||||
}
|
||||
|
||||
/// Get YouTube player data (video/audio streams + basic metadata) using the specified client
|
||||
|
@ -192,37 +125,32 @@ impl RustyPipeQuery {
|
|||
client_type: ClientType,
|
||||
) -> Result<VideoPlayer, Error> {
|
||||
let video_id = video_id.as_ref();
|
||||
let deobf = self.client.get_deobf_data().await?;
|
||||
|
||||
let (deobf, player_po) = tokio::try_join!(
|
||||
async {
|
||||
if client_type.needs_deobf() {
|
||||
Ok::<_, Error>(Some(self.client.get_deobf_data().await?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
},
|
||||
async {
|
||||
if client_type.needs_po_token() {
|
||||
self.get_player_po_token(video_id).await
|
||||
} else {
|
||||
Ok(PlayerPoToken::default())
|
||||
}
|
||||
}
|
||||
)?;
|
||||
|
||||
let playback_context = deobf.as_ref().map(|deobf| QPlaybackContext {
|
||||
let request_body = if client_type.is_web() {
|
||||
QPlayer {
|
||||
playback_context: Some(QPlaybackContext {
|
||||
content_playback_context: QContentPlaybackContext {
|
||||
signature_timestamp: &deobf.sts,
|
||||
referer: format!("https://www.youtube.com/watch?v={video_id}"),
|
||||
},
|
||||
});
|
||||
|
||||
let request_body = QPlayer {
|
||||
playback_context,
|
||||
}),
|
||||
cpn: None,
|
||||
video_id,
|
||||
content_check_ok: true,
|
||||
racy_check_ok: true,
|
||||
service_integrity_dimensions: player_po.content_po_token,
|
||||
params: None,
|
||||
}
|
||||
} else {
|
||||
QPlayer {
|
||||
playback_context: None,
|
||||
cpn: Some(util::generate_content_playback_nonce()),
|
||||
video_id,
|
||||
content_check_ok: true,
|
||||
racy_check_ok: true,
|
||||
// Source: https://github.com/TeamNewPipe/NewPipeExtractor/pull/1168
|
||||
params: Some("CgIIAQ%3D%3D").filter(|_| client_type == ClientType::Android),
|
||||
}
|
||||
};
|
||||
|
||||
self.execute_request_ctx::<response::Player, _, _>(
|
||||
|
@ -231,65 +159,13 @@ impl RustyPipeQuery {
|
|||
video_id,
|
||||
"player",
|
||||
&request_body,
|
||||
MapRespOptions {
|
||||
visitor_data: player_po.visitor_data.as_deref(),
|
||||
deobf: deobf.as_ref(),
|
||||
unlocalized: true,
|
||||
session_po_token: player_po.session_po_token,
|
||||
MapRespCtxSource {
|
||||
deobf: Some(&deobf),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the default order of client types when fetching player data
|
||||
///
|
||||
/// The order may change in the future in case YouTube applies changes to their
|
||||
/// platform that disable a client or make it less reliable.
|
||||
pub fn player_client_order(&self) -> &'static [ClientType] {
|
||||
if self.client.inner.botguard.is_some() {
|
||||
&[ClientType::Desktop, ClientType::Ios, ClientType::Tv]
|
||||
} else {
|
||||
&[ClientType::Ios, ClientType::Tv]
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a license to play back DRM protected videos
|
||||
///
|
||||
/// Requires authentication (either via OAuth or cookies).
|
||||
#[tracing::instrument(skip(self), level = "error")]
|
||||
pub async fn drm_license(
|
||||
&self,
|
||||
video_id: &str,
|
||||
drm_system: DrmSystem,
|
||||
session_id: &str,
|
||||
drm_params: &str,
|
||||
license_request: &[u8],
|
||||
) -> Result<DrmLicense, Error> {
|
||||
let client_type = self
|
||||
.auth_enabled_client(&[ClientType::Desktop, ClientType::Tv])
|
||||
.ok_or(Error::Auth(AuthError::NoLogin))?;
|
||||
let request_body = QDrmLicense {
|
||||
drm_system: drm_system.req_param(),
|
||||
video_id,
|
||||
cpn: &util::generate_content_playback_nonce(),
|
||||
session_id,
|
||||
license_request: &data_encoding::BASE64.encode(license_request),
|
||||
drm_params,
|
||||
drm_video_feature: "DRM_VIDEO_FEATURE_SDR",
|
||||
};
|
||||
|
||||
self.clone()
|
||||
.authenticated()
|
||||
.execute_request::<response::DrmLicense, _, _>(
|
||||
client_type,
|
||||
"drm_license",
|
||||
video_id,
|
||||
"player/get_drm_license",
|
||||
&request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl MapResponse<VideoPlayer> for response::Player {
|
||||
|
@ -321,9 +197,8 @@ impl MapResponse<VideoPlayer> for response::Player {
|
|||
"Premium" => Some(UnavailabilityReason::Premium),
|
||||
"members-only" => Some(UnavailabilityReason::MembersOnly),
|
||||
"country" => Some(UnavailabilityReason::Geoblocked),
|
||||
"version" | "websites" => Some(UnavailabilityReason::UnsupportedClient),
|
||||
"Android" | "websites" => Some(UnavailabilityReason::UnsupportedClient),
|
||||
"bot" => Some(UnavailabilityReason::IpBan),
|
||||
"later." => Some(UnavailabilityReason::TryAgain),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
@ -401,10 +276,7 @@ impl MapResponse<VideoPlayer> for response::Player {
|
|||
};
|
||||
|
||||
let streams = if !is_live {
|
||||
let mut mapper = StreamsMapper::new(
|
||||
ctx.deobf,
|
||||
ctx.session_po_token.as_ref().map(|t| t.po_token.as_str()),
|
||||
)?;
|
||||
let mut mapper = StreamsMapper::new(Deobfuscator::new(ctx.deobf.unwrap())?);
|
||||
mapper.map_streams(streaming_data.formats);
|
||||
mapper.map_streams(streaming_data.adaptive_formats);
|
||||
let mut res = mapper.output()?;
|
||||
|
@ -478,30 +350,6 @@ impl MapResponse<VideoPlayer> for response::Player {
|
|||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let drm = streaming_data
|
||||
.drm_params
|
||||
.zip(self.heartbeat_params.drm_session_id)
|
||||
.map(|(drm_params, drm_session_id)| VideoPlayerDrm {
|
||||
widevine_service_cert: self
|
||||
.player_config
|
||||
.web_drm_config
|
||||
.and_then(|c| c.widevine_service_cert)
|
||||
.and_then(|c| data_encoding::BASE64URL.decode(c.as_bytes()).ok()),
|
||||
drm_params,
|
||||
authorized_track_types: streaming_data
|
||||
.initial_authorized_drm_track_types
|
||||
.into_iter()
|
||||
.map(|t| t.into())
|
||||
.collect(),
|
||||
drm_session_id,
|
||||
});
|
||||
|
||||
let mut valid_until = OffsetDateTime::now_utc()
|
||||
+ time::Duration::seconds(streaming_data.expires_in_seconds.into());
|
||||
if let Some(pot) = &ctx.session_po_token {
|
||||
valid_until = valid_until.min(pot.valid_until);
|
||||
}
|
||||
|
||||
Ok(MapResult {
|
||||
c: VideoPlayer {
|
||||
details: video_info,
|
||||
|
@ -510,11 +358,9 @@ impl MapResponse<VideoPlayer> for response::Player {
|
|||
audio_streams: streams.audio_streams,
|
||||
subtitles,
|
||||
expires_in_seconds: streaming_data.expires_in_seconds,
|
||||
valid_until,
|
||||
hls_manifest_url: streaming_data.hls_manifest_url,
|
||||
dash_manifest_url: streaming_data.dash_manifest_url,
|
||||
preview_frames,
|
||||
drm,
|
||||
client_type: ctx.client_type,
|
||||
visitor_data: self
|
||||
.response_context
|
||||
|
@ -526,9 +372,8 @@ impl MapResponse<VideoPlayer> for response::Player {
|
|||
}
|
||||
}
|
||||
|
||||
struct StreamsMapper<'a> {
|
||||
deobf: Option<Deobfuscator>,
|
||||
session_po_token: Option<&'a str>,
|
||||
struct StreamsMapper {
|
||||
deobf: Deobfuscator,
|
||||
streams: Streams,
|
||||
warnings: Vec<String>,
|
||||
/// First stream mapping error
|
||||
|
@ -546,25 +391,16 @@ struct Streams {
|
|||
audio_streams: Vec<AudioStream>,
|
||||
}
|
||||
|
||||
impl<'a> StreamsMapper<'a> {
|
||||
fn new(
|
||||
deobf_data: Option<&DeobfData>,
|
||||
session_po_token: Option<&'a str>,
|
||||
) -> Result<Self, DeobfError> {
|
||||
let deobf = match deobf_data {
|
||||
Some(deobf_data) => Some(Deobfuscator::new(deobf_data)?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
impl StreamsMapper {
|
||||
fn new(deobf: Deobfuscator) -> Self {
|
||||
Self {
|
||||
deobf,
|
||||
session_po_token,
|
||||
streams: Streams::default(),
|
||||
warnings: Vec::new(),
|
||||
first_err: None,
|
||||
last_nsig: String::new(),
|
||||
last_nsig_deobf: String::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn map_streams(&mut self, mut streams: MapResult<Vec<Format>>) {
|
||||
|
@ -624,12 +460,6 @@ impl<'a> StreamsMapper<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
fn deobf(&self) -> Result<&Deobfuscator, DeobfError> {
|
||||
self.deobf
|
||||
.as_ref()
|
||||
.ok_or(DeobfError::Other("no deobfuscator"))
|
||||
}
|
||||
|
||||
fn cipher_to_url_params(
|
||||
&self,
|
||||
signature_cipher: &str,
|
||||
|
@ -650,7 +480,7 @@ impl<'a> StreamsMapper<'a> {
|
|||
let (url_base, mut url_params) =
|
||||
util::url_to_params(raw_url).or(Err(DeobfError::Extraction("url params")))?;
|
||||
|
||||
let deobf_sig = self.deobf()?.deobfuscate_sig(sig)?;
|
||||
let deobf_sig = self.deobf.deobfuscate_sig(sig)?;
|
||||
url_params.insert(sp.to_string(), deobf_sig);
|
||||
|
||||
Ok((url_base, url_params))
|
||||
|
@ -661,7 +491,7 @@ impl<'a> StreamsMapper<'a> {
|
|||
let nsig = if n == &self.last_nsig {
|
||||
self.last_nsig_deobf.to_owned()
|
||||
} else {
|
||||
let nsig = self.deobf()?.deobfuscate_nsig(n)?;
|
||||
let nsig = self.deobf.deobfuscate_nsig(n)?;
|
||||
self.last_nsig.clone_from(n);
|
||||
self.last_nsig_deobf.clone_from(&nsig);
|
||||
nsig
|
||||
|
@ -698,10 +528,6 @@ impl<'a> StreamsMapper<'a> {
|
|||
}?;
|
||||
|
||||
self.deobf_nsig(&mut url_params)?;
|
||||
if let Some(pot) = self.session_po_token {
|
||||
url_params.insert("pot".to_owned(), pot.to_owned());
|
||||
}
|
||||
|
||||
let url = Url::parse_with_params(url_base.as_str(), url_params.iter())
|
||||
.map_err(|_| ExtractionError::InvalidData("could not combine URL".into()))?;
|
||||
|
||||
|
@ -748,8 +574,6 @@ impl<'a> StreamsMapper<'a> {
|
|||
format,
|
||||
codec: get_video_codec(codecs),
|
||||
mime: f.mime_type,
|
||||
drm_track_type: f.drm_track_type.map(|t| t.into()),
|
||||
drm_systems: f.drm_families.into_iter().map(|t| t.into()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -773,11 +597,7 @@ impl<'a> StreamsMapper<'a> {
|
|||
itag: f.itag,
|
||||
bitrate: f.bitrate,
|
||||
average_bitrate: f.average_bitrate.unwrap_or(f.bitrate),
|
||||
size: f.content_length.ok_or_else(|| {
|
||||
ExtractionError::InvalidData(
|
||||
format!("no audio content length. itag: {}", f.itag).into(),
|
||||
)
|
||||
})?,
|
||||
size: f.content_length.unwrap(),
|
||||
index_range: f.index_range,
|
||||
init_range: f.init_range,
|
||||
duration_ms: f.approx_duration_ms,
|
||||
|
@ -789,8 +609,6 @@ impl<'a> StreamsMapper<'a> {
|
|||
track: f
|
||||
.audio_track
|
||||
.map(|t| self.map_audio_track(t, map_res.xtags)),
|
||||
drm_track_type: f.drm_track_type.map(|t| t.into()),
|
||||
drm_systems: f.drm_families.into_iter().map(|t| t.into()).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -892,52 +710,17 @@ fn get_audio_codec(codecs: Vec<&str>) -> AudioCodec {
|
|||
return AudioCodec::Mp4a;
|
||||
} else if codec.starts_with("opus") {
|
||||
return AudioCodec::Opus;
|
||||
} else if codec.starts_with("ac-3") {
|
||||
return AudioCodec::Ac3;
|
||||
} else if codec.starts_with("ec-3") {
|
||||
return AudioCodec::Ec3;
|
||||
}
|
||||
}
|
||||
AudioCodec::Unknown
|
||||
}
|
||||
|
||||
impl MapResponse<DrmLicense> for response::DrmLicense {
|
||||
fn map_response(self, _ctx: &MapRespCtx<'_>) -> Result<MapResult<DrmLicense>, ExtractionError> {
|
||||
if self.status != "LICENSE_STATUS_OK" {
|
||||
return Err(ExtractionError::InvalidData(self.status.into()));
|
||||
}
|
||||
|
||||
let license = DrmLicense {
|
||||
license: data_encoding::BASE64URL
|
||||
.decode(self.license.as_bytes())
|
||||
.map_err(|_| ExtractionError::InvalidData("license: invalid b64".into()))?,
|
||||
authorized_formats: self
|
||||
.authorized_formats
|
||||
.into_iter()
|
||||
.filter_map(|f| {
|
||||
let key: Option<[u8; 16]> = data_encoding::BASE64URL
|
||||
.decode(f.key_id.as_bytes())
|
||||
.ok()
|
||||
.and_then(|k| k.try_into().ok());
|
||||
key.map(|k| (f.track_type.into(), k))
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
|
||||
Ok(MapResult {
|
||||
c: license,
|
||||
warnings: Vec::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fs::File, io::BufReader};
|
||||
|
||||
use path_macro::path;
|
||||
use rstest::rstest;
|
||||
use time::UtcOffset;
|
||||
|
||||
use super::*;
|
||||
use crate::{deobfuscate::DeobfData, param::Language, util::tests::TESTFILES};
|
||||
|
@ -969,13 +752,10 @@ mod tests {
|
|||
.map_response(&MapRespCtx {
|
||||
id: "pPvd8UxmSbQ",
|
||||
lang: Language::En,
|
||||
utc_offset: UtcOffset::UTC,
|
||||
deobf: Some(&DEOBF_DATA),
|
||||
visitor_data: None,
|
||||
client_type,
|
||||
artist: None,
|
||||
authenticated: false,
|
||||
session_po_token: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
@ -984,15 +764,24 @@ mod tests {
|
|||
"deserialization/mapping warnings: {:?}",
|
||||
map_res.warnings
|
||||
);
|
||||
let is_desktop = name == "desktop" || name == "desktopmusic";
|
||||
insta::assert_ron_snapshot!(format!("map_player_data_{name}"), map_res.c, {
|
||||
".valid_until" => "[date]"
|
||||
".details.publish_date" => insta::dynamic_redaction(move |value, _path| {
|
||||
if is_desktop {
|
||||
assert!(value.as_str().unwrap().starts_with("2019-05-30T00:00:00"));
|
||||
"2019-05-30T00:00:00"
|
||||
} else {
|
||||
assert_eq!(value, insta::internals::Content::None);
|
||||
"~"
|
||||
}
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cipher_to_url() {
|
||||
let signature_cipher = "s=w%3DAe%3DA6aDNQLkViKS7LOm9QtxZJHKwb53riq9qEFw-ecBWJCAiA%3DcEg0tn3dty9jEHszfzh4Ud__bg9CEHVx4ix-7dKsIPAhIQRw8JQ0qOA&sp=sig&url=https://rr5---sn-h0jelnez.googlevideo.com/videoplayback%3Fexpire%3D1659376413%26ei%3Dvb7nYvH5BMK8gAfBj7ToBQ%26ip%3D2003%253Ade%253Aaf06%253A6300%253Ac750%253A1b77%253Ac74a%253A80e3%26id%3Do-AB_BABwrXZJN428ZwDxq5ScPn2AbcGODnRlTVhCQ3mj2%26itag%3D251%26source%3Dyoutube%26requiressl%3Dyes%26mh%3DhH%26mm%3D31%252C26%26mn%3Dsn-h0jelnez%252Csn-4g5ednsl%26ms%3Dau%252Conr%26mv%3Dm%26mvi%3D5%26pl%3D37%26initcwndbps%3D1588750%26spc%3DlT-Khi831z8dTejFIRCvCEwx_6romtM%26vprv%3D1%26mime%3Daudio%252Fwebm%26ns%3Db_Mq_qlTFcSGlG9RpwpM9xQH%26gir%3Dyes%26clen%3D3781277%26dur%3D229.301%26lmt%3D1655510291473933%26mt%3D1659354538%26fvip%3D5%26keepalive%3Dyes%26fexp%3D24001373%252C24007246%26c%3DWEB%26rbqsm%3Dfr%26txp%3D4532434%26n%3Dd2g6G2hVqWIXxedQ%26sparams%3Dexpire%252Cei%252Cip%252Cid%252Citag%252Csource%252Crequiressl%252Cspc%252Cvprv%252Cmime%252Cns%252Cgir%252Cclen%252Cdur%252Clmt%26lsparams%3Dmh%252Cmm%252Cmn%252Cms%252Cmv%252Cmvi%252Cpl%252Cinitcwndbps%26lsig%3DAG3C_xAwRQIgCKCGJ1iu4wlaGXy3jcJyU3inh9dr1FIfqYOZEG_MdmACIQCbungkQYFk7EhD6K2YvLaHFMjKOFWjw001_tLb0lPDtg%253D%253D";
|
||||
let mut mapper = StreamsMapper::new(Some(&DEOBF_DATA), None).unwrap();
|
||||
let mut mapper = StreamsMapper::new(Deobfuscator::new(&DEOBF_DATA).unwrap());
|
||||
let url = mapper
|
||||
.map_url(&None, &Some(signature_cipher.to_owned()))
|
||||
.unwrap()
|
||||
|
|
|
@ -203,12 +203,7 @@ impl MapResponse<Playlist> for response::Playlist {
|
|||
.as_deref()
|
||||
.or(last_update_txt2.as_deref())
|
||||
.and_then(|txt| {
|
||||
timeago::parse_textual_date_or_warn(
|
||||
ctx.lang,
|
||||
ctx.utc_offset,
|
||||
txt,
|
||||
&mut mapper.warnings,
|
||||
)
|
||||
timeago::parse_textual_date_or_warn(ctx.lang, txt, &mut mapper.warnings)
|
||||
.map(OffsetDateTime::date)
|
||||
});
|
||||
|
||||
|
@ -222,7 +217,6 @@ impl MapResponse<Playlist> for response::Playlist {
|
|||
mapper.ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
ContinuationEndpoint::Browse,
|
||||
ctx.authenticated,
|
||||
),
|
||||
video_count: n_videos,
|
||||
thumbnail: thumbnails.into(),
|
||||
|
|
|
@ -2,14 +2,11 @@ use serde::Deserialize;
|
|||
use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError};
|
||||
|
||||
use super::{
|
||||
video_item::YouTubeListRenderer, Alert, AttachmentRun, AvatarViewModel, ChannelBadge,
|
||||
ContentRenderer, ContentsRenderer, ContinuationActionWrap, ImageView,
|
||||
PageHeaderRendererContent, PhMetadataView, ResponseContext, Thumbnails, TwoColumnBrowseResults,
|
||||
};
|
||||
use crate::{
|
||||
model::Verification,
|
||||
serializer::text::{AttributedText, Text, TextComponent},
|
||||
video_item::YouTubeListRenderer, Alert, ChannelBadge, ContentRenderer, ContentsRenderer,
|
||||
ContinuationActionWrap, ImageView, PageHeaderRendererContent, PhMetadataView, ResponseContext,
|
||||
Thumbnails, TwoColumnBrowseResults,
|
||||
};
|
||||
use crate::serializer::text::{AttributedText, Text, TextComponent};
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -124,7 +121,7 @@ pub(crate) enum CarouselHeaderRendererItem {
|
|||
pub(crate) struct PageHeaderRendererInner {
|
||||
/// Channel title (only used to extract verification badges)
|
||||
#[serde_as(as = "DefaultOnError")]
|
||||
pub title: Option<PhTitleView>,
|
||||
pub title: PhTitleView,
|
||||
/// Channel avatar
|
||||
pub image: PhAvatarView,
|
||||
/// Channel metadata (subscribers, video count)
|
||||
|
@ -133,7 +130,7 @@ pub(crate) struct PageHeaderRendererInner {
|
|||
pub banner: PhBannerView,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct PhTitleView {
|
||||
pub dynamic_text_view_model: PhTitleView2,
|
||||
|
@ -153,6 +150,58 @@ pub(crate) struct PhTitleView3 {
|
|||
pub attachment_runs: Vec<AttachmentRun>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRun {
|
||||
pub element: AttachmentRunElement,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElement {
|
||||
#[serde(rename = "type")]
|
||||
pub typ: AttachmentRunElementType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElementType {
|
||||
pub image_type: AttachmentRunElementImageType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElementImageType {
|
||||
pub image: AttachmentRunElementImage,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElementImage {
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub sources: Vec<AttachmentRunElementImageSource>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElementImageSource {
|
||||
pub client_resource: ClientResource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ClientResource {
|
||||
pub image_name: IconName,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub(crate) enum IconName {
|
||||
CheckCircleFilled,
|
||||
MusicFilled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct PhAvatarView {
|
||||
|
@ -162,7 +211,13 @@ pub(crate) struct PhAvatarView {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct PhAvatarView2 {
|
||||
pub avatar: AvatarViewModel,
|
||||
pub avatar: PhAvatarView3,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct PhAvatarView3 {
|
||||
pub avatar_view_model: ImageView,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
|
@ -275,9 +330,15 @@ impl From<PhTitleView> for crate::model::Verification {
|
|||
.dynamic_text_view_model
|
||||
.text
|
||||
.attachment_runs
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(Verification::from)
|
||||
.iter()
|
||||
.find_map(|r| {
|
||||
r.element.typ.image_type.image.sources.first().map(|s| {
|
||||
match s.client_resource.image_name {
|
||||
IconName::CheckCircleFilled => crate::model::Verification::Verified,
|
||||
IconName::MusicFilled => crate::model::Verification::Artist,
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use super::{video_item::YouTubeListRendererWrap, Tab, TwoColumnBrowseResults};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct History {
|
||||
pub contents: TwoColumnBrowseResults<Tab<YouTubeListRendererWrap>>,
|
||||
}
|
|
@ -30,7 +30,6 @@ pub(crate) use music_new::MusicNew;
|
|||
pub(crate) use music_playlist::MusicPlaylist;
|
||||
pub(crate) use music_search::MusicSearch;
|
||||
pub(crate) use music_search::MusicSearchSuggestion;
|
||||
pub(crate) use player::DrmLicense;
|
||||
pub(crate) use player::Player;
|
||||
pub(crate) use playlist::Playlist;
|
||||
pub(crate) use search::Search;
|
||||
|
@ -47,15 +46,6 @@ pub(crate) mod channel_rss;
|
|||
#[cfg(feature = "rss")]
|
||||
pub(crate) use channel_rss::ChannelRss;
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
pub(crate) mod history;
|
||||
#[cfg(feature = "userdata")]
|
||||
pub(crate) use history::History;
|
||||
#[cfg(feature = "userdata")]
|
||||
pub(crate) mod music_history;
|
||||
#[cfg(feature = "userdata")]
|
||||
pub(crate) use music_history::MusicHistory;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::marker::PhantomData;
|
||||
|
@ -123,12 +113,6 @@ pub(crate) struct ImageView {
|
|||
pub image: Thumbnails,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AvatarViewModel {
|
||||
pub avatar_view_model: ImageView,
|
||||
}
|
||||
|
||||
/// List of images in different resolutions.
|
||||
/// Not only used for thumbnails, but also for avatars and banners.
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
|
@ -204,92 +188,23 @@ pub(crate) enum ChannelBadgeStyle {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Alert {
|
||||
pub alert_renderer: TextBox,
|
||||
pub alert_renderer: AlertRenderer,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct TextBox {
|
||||
pub(crate) struct AlertRenderer {
|
||||
#[serde_as(as = "Text")]
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct SimpleHeaderRenderer {
|
||||
#[serde_as(as = "Text")]
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct TextComponentBox {
|
||||
#[serde_as(as = "AttributedText")]
|
||||
pub text: TextComponent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ResponseContext {
|
||||
pub visitor_data: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRun {
|
||||
pub element: AttachmentRunElement,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElement {
|
||||
#[serde(rename = "type")]
|
||||
pub typ: AttachmentRunElementType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElementType {
|
||||
pub image_type: AttachmentRunElementImageType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElementImageType {
|
||||
pub image: AttachmentRunElementImage,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElementImage {
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub sources: Vec<AttachmentRunElementImageSource>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AttachmentRunElementImageSource {
|
||||
pub client_resource: ClientResource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ClientResource {
|
||||
pub image_name: IconName,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum IconName {
|
||||
CheckCircleFilled,
|
||||
#[serde(alias = "AUDIO_BADGE")]
|
||||
MusicFilled,
|
||||
}
|
||||
|
||||
// CONTINUATION
|
||||
|
||||
#[serde_as]
|
||||
|
@ -428,17 +343,6 @@ impl From<Thumbnails> for Vec<crate::model::Thumbnail> {
|
|||
}
|
||||
}
|
||||
|
||||
impl ContentImage {
|
||||
pub(crate) fn into_image(self) -> ImageViewOl {
|
||||
match self {
|
||||
ContentImage::ThumbnailViewModel(image) => image,
|
||||
ContentImage::CollectionThumbnailViewModel { primary_thumbnail } => {
|
||||
primary_thumbnail.thumbnail_view_model
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ChannelBadge>> for crate::model::Verification {
|
||||
fn from(badges: Vec<ChannelBadge>) -> Self {
|
||||
badges
|
||||
|
@ -462,25 +366,6 @@ impl From<Icon> for crate::model::Verification {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<AttachmentRun> for crate::model::Verification {
|
||||
fn from(value: AttachmentRun) -> Self {
|
||||
match value
|
||||
.element
|
||||
.typ
|
||||
.image_type
|
||||
.image
|
||||
.sources
|
||||
.into_iter()
|
||||
.next()
|
||||
.map(|s| s.client_resource.image_name)
|
||||
{
|
||||
Some(IconName::CheckCircleFilled) => Self::Verified,
|
||||
Some(IconName::MusicFilled) => Self::Artist,
|
||||
None => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn alerts_to_err(id: &str, alerts: Option<Vec<Alert>>) -> ExtractionError {
|
||||
ExtractionError::NotFound {
|
||||
id: id.to_owned(),
|
||||
|
@ -595,11 +480,9 @@ pub(crate) struct PhMetadataView {
|
|||
pub content_metadata_view_model: PhMetadataView2,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct PhMetadataView2 {
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub metadata_rows: Vec<PhMetadataRow>,
|
||||
}
|
||||
|
||||
|
@ -615,26 +498,17 @@ pub(crate) struct PhMetadataRow {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum MetadataPart {
|
||||
Text(#[serde_as(as = "AttributedText")] TextComponent),
|
||||
Text(#[serde_as(deserialize_as = "AttributedText")] String),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
AvatarStack {
|
||||
avatar_stack_view_model: TextComponentBox,
|
||||
avatar_stack_view_model: AvatarStackViewModel,
|
||||
},
|
||||
}
|
||||
|
||||
impl MetadataPart {
|
||||
pub fn into_text_component(self) -> TextComponent {
|
||||
match self {
|
||||
MetadataPart::Text(text_component) => text_component,
|
||||
MetadataPart::AvatarStack {
|
||||
avatar_stack_view_model,
|
||||
} => avatar_stack_view_model.text,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
MetadataPart::Text(s) => s.as_str(),
|
||||
MetadataPart::Text(s) => s,
|
||||
MetadataPart::AvatarStack {
|
||||
avatar_stack_view_model,
|
||||
} => avatar_stack_view_model.text.as_str(),
|
||||
|
@ -642,50 +516,10 @@ impl MetadataPart {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum ContentImage {
|
||||
ThumbnailViewModel(ImageViewOl),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
CollectionThumbnailViewModel {
|
||||
primary_thumbnail: ThumbnailViewModelWrap,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ThumbnailViewModelWrap {
|
||||
pub thumbnail_view_model: ImageViewOl,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ImageViewOl {
|
||||
pub image: Thumbnails,
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub overlays: Vec<ImageViewOverlay>,
|
||||
pub(crate) struct AvatarStackViewModel {
|
||||
#[serde_as(deserialize_as = "AttributedText")]
|
||||
pub text: TextComponent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ImageViewOverlay {
|
||||
pub thumbnail_overlay_badge_view_model: ThumbnailOverlayBadgeViewModel,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ThumbnailOverlayBadgeViewModel {
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub thumbnail_badges: Vec<ThumbnailBadges>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ThumbnailBadges {
|
||||
pub thumbnail_badge_view_model: TextBox,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Empty {}
|
||||
|
|
|
@ -14,7 +14,7 @@ use super::{
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct MusicArtist {
|
||||
pub contents: SingleColumnBrowseResult<Tab<SectionList<ItemSection>>>,
|
||||
pub contents: SingleColumnBrowseResult<Tab<Option<SectionList<ItemSection>>>>,
|
||||
pub header: Header,
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ use serde_with::{serde_as, DefaultOnError, VecSkipError};
|
|||
|
||||
use crate::serializer::text::Text;
|
||||
|
||||
use super::AlertRenderer;
|
||||
use super::ContentsRenderer;
|
||||
use super::TextBox;
|
||||
use super::{
|
||||
music_item::{ItemSection, PlaylistPanelRenderer},
|
||||
ContentRenderer,
|
||||
|
@ -115,7 +115,7 @@ pub(crate) struct MusicLyrics {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum ListOrMessage<T> {
|
||||
SectionListRenderer(ContentsRenderer<T>),
|
||||
MessageRenderer(TextBox),
|
||||
MessageRenderer(AlertRenderer),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
use super::music_playlist::Contents;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct MusicHistory {
|
||||
pub contents: Contents,
|
||||
}
|
|
@ -4,7 +4,7 @@ use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkip
|
|||
use crate::{
|
||||
model::{
|
||||
self, traits::FromYtItem, AlbumId, AlbumItem, AlbumType, ArtistId, ArtistItem, ChannelId,
|
||||
MusicItem, MusicItemType, MusicPlaylistItem, TrackItem, UserItem,
|
||||
MusicItem, MusicItemType, MusicPlaylistItem, TrackItem,
|
||||
},
|
||||
param::Language,
|
||||
serializer::{
|
||||
|
@ -18,15 +18,10 @@ use super::{
|
|||
url_endpoint::{
|
||||
BrowseEndpointWrap, MusicPage, MusicPageType, MusicVideoType, NavigationEndpoint, PageType,
|
||||
},
|
||||
ContentsRenderer, ContinuationActionWrap, ContinuationEndpoint, MusicContinuationData,
|
||||
SimpleHeaderRenderer, Thumbnails, ThumbnailsWrap,
|
||||
ContentsRenderer, MusicContinuationData, Thumbnails, ThumbnailsWrap,
|
||||
};
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
use crate::model::HistoryItem;
|
||||
#[cfg(feature = "userdata")]
|
||||
use time::UtcOffset;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum ItemSection {
|
||||
|
@ -44,9 +39,6 @@ pub(crate) enum ItemSection {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct MusicShelf {
|
||||
#[cfg(feature = "userdata")]
|
||||
#[serde_as(as = "Option<Text>")]
|
||||
pub title: Option<String>,
|
||||
/// Playlist ID (only for playlists)
|
||||
pub playlist_id: Option<String>,
|
||||
pub contents: MapResult<Vec<MusicResponseItem>>,
|
||||
|
@ -93,10 +85,6 @@ pub(crate) enum MusicResponseItem {
|
|||
MusicResponsiveListItemRenderer(ListMusicItem),
|
||||
MusicTwoRowItemRenderer(CoverMusicItem),
|
||||
MessageRenderer(serde::de::IgnoredAny),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ContinuationItemRenderer {
|
||||
continuation_endpoint: ContinuationEndpoint,
|
||||
},
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
|
@ -284,7 +272,7 @@ pub(crate) struct QueueMusicItem {
|
|||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct MusicThumbnailRenderer {
|
||||
#[serde(default, alias = "croppedSquareThumbnailRenderer")]
|
||||
#[serde(alias = "croppedSquareThumbnailRenderer")]
|
||||
pub music_thumbnail_renderer: ThumbnailsWrap,
|
||||
}
|
||||
|
||||
|
@ -333,14 +321,10 @@ impl From<MusicThumbnailRenderer> for Vec<model::Thumbnail> {
|
|||
}
|
||||
|
||||
/// Music list continuation response model
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct MusicContinuation {
|
||||
pub continuation_contents: Option<ContinuationContents>,
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub on_response_received_actions: Vec<ContinuationActionWrap<MusicResponseItem>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -412,7 +396,15 @@ pub(crate) struct GridRenderer {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct GridHeader {
|
||||
pub grid_header_renderer: SimpleHeaderRenderer,
|
||||
pub grid_header_renderer: GridHeaderRenderer,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct GridHeaderRenderer {
|
||||
#[serde_as(as = "Text")]
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -427,6 +419,14 @@ pub(crate) struct SimpleHeader {
|
|||
pub music_header_renderer: SimpleHeaderRenderer,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct SimpleHeaderRenderer {
|
||||
#[serde_as(as = "Text")]
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum TrackBadge {
|
||||
|
@ -443,13 +443,10 @@ pub(crate) struct MusicListMapper {
|
|||
/// Artists list + various artists flag
|
||||
artists: Option<(Vec<ArtistId>, bool)>,
|
||||
album: Option<AlbumId>,
|
||||
/// Default album type in case an album is unlabeled
|
||||
pub album_type: AlbumType,
|
||||
artist_page: bool,
|
||||
search_suggestion: bool,
|
||||
items: Vec<MusicItem>,
|
||||
warnings: Vec<String>,
|
||||
pub ctoken: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -466,12 +463,10 @@ impl MusicListMapper {
|
|||
lang,
|
||||
artists: None,
|
||||
album: None,
|
||||
album_type: AlbumType::Single,
|
||||
artist_page: false,
|
||||
search_suggestion: false,
|
||||
items: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
ctoken: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -480,12 +475,10 @@ impl MusicListMapper {
|
|||
lang,
|
||||
artists: None,
|
||||
album: None,
|
||||
album_type: AlbumType::Single,
|
||||
artist_page: false,
|
||||
search_suggestion: true,
|
||||
items: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
ctoken: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -495,12 +488,10 @@ impl MusicListMapper {
|
|||
lang,
|
||||
artists: Some((vec![artist], false)),
|
||||
album: None,
|
||||
album_type: AlbumType::Single,
|
||||
artist_page: true,
|
||||
search_suggestion: false,
|
||||
items: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
ctoken: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,12 +501,10 @@ impl MusicListMapper {
|
|||
lang,
|
||||
artists: Some((artists, by_va)),
|
||||
album: Some(album),
|
||||
album_type: AlbumType::Single,
|
||||
artist_page: false,
|
||||
search_suggestion: false,
|
||||
items: Vec::new(),
|
||||
warnings: Vec::new(),
|
||||
ctoken: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -527,12 +516,6 @@ impl MusicListMapper {
|
|||
// Tile
|
||||
MusicResponseItem::MusicTwoRowItemRenderer(item) => self.map_tile(item),
|
||||
MusicResponseItem::MessageRenderer(_) => Ok(None),
|
||||
MusicResponseItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
} => {
|
||||
self.ctoken = Some(continuation_endpoint.continuation_command.token);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -552,7 +535,7 @@ impl MusicListMapper {
|
|||
etype
|
||||
}
|
||||
|
||||
/// Map a ListMusicItem (album/playlist item, search result)
|
||||
/// Map a ListMusicItem (album/playlist tile)
|
||||
fn map_list_item(&mut self, item: ListMusicItem) -> Result<Option<MusicItemType>, String> {
|
||||
let mut columns = item.flex_columns.into_iter();
|
||||
let c1 = columns.next();
|
||||
|
@ -776,7 +759,7 @@ impl MusicListMapper {
|
|||
artist_id,
|
||||
album,
|
||||
view_count,
|
||||
track_type: vtype.into(),
|
||||
is_video: vtype.is_video(),
|
||||
track_nr,
|
||||
by_va,
|
||||
}));
|
||||
|
@ -784,16 +767,8 @@ impl MusicListMapper {
|
|||
}
|
||||
// Artist / Album / Playlist
|
||||
Some((page_type, id)) => {
|
||||
// Ignore "Shuffle all" button and builtin "Liked music" and "Saved episodes" playlists
|
||||
if page_type == MusicPageType::None
|
||||
|| (page_type == (MusicPageType::Playlist { is_podcast: false })
|
||||
&& matches!(id.as_str(), "MLCT" | "LM" | "SE"))
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut subtitle_parts = c2
|
||||
.ok_or_else(|| format!("{id}: could not get subtitle"))?
|
||||
.ok_or_else(|| "could not get subtitle".to_owned())?
|
||||
.renderer
|
||||
.text
|
||||
.split(util::DOT_SEPARATOR)
|
||||
|
@ -854,7 +829,7 @@ impl MusicListMapper {
|
|||
}));
|
||||
Ok(Some(MusicItemType::Album))
|
||||
}
|
||||
MusicPageType::Playlist { is_podcast } => {
|
||||
MusicPageType::Playlist => {
|
||||
// Part 1 may be the "Playlist" label
|
||||
let (channel_p, tcount_p) = match subtitle_p3 {
|
||||
Some(_) => (subtitle_p2, subtitle_p3),
|
||||
|
@ -880,23 +855,9 @@ impl MusicListMapper {
|
|||
channel,
|
||||
track_count,
|
||||
from_ytm,
|
||||
is_podcast,
|
||||
}));
|
||||
Ok(Some(MusicItemType::Playlist))
|
||||
}
|
||||
MusicPageType::User => {
|
||||
// Part 1 may be the "Profile" label
|
||||
let handle = map_channel_handle(subtitle_p2.as_ref())
|
||||
.or_else(|| map_channel_handle(subtitle_p1.as_ref()));
|
||||
|
||||
self.items.push(MusicItem::User(UserItem {
|
||||
id,
|
||||
name: title,
|
||||
handle,
|
||||
avatar: item.thumbnail.into(),
|
||||
}));
|
||||
Ok(Some(MusicItemType::User))
|
||||
}
|
||||
MusicPageType::None => {
|
||||
// There may be broken YT channels from the artist search. They can be skipped.
|
||||
Ok(None)
|
||||
|
@ -956,7 +917,7 @@ impl MusicListMapper {
|
|||
artists,
|
||||
album: None,
|
||||
view_count,
|
||||
track_type: vtype.into(),
|
||||
is_video: vtype.is_video(),
|
||||
track_nr: None,
|
||||
by_va,
|
||||
}));
|
||||
|
@ -981,7 +942,7 @@ impl MusicListMapper {
|
|||
}
|
||||
MusicPageType::Album => {
|
||||
let mut year = None;
|
||||
let mut album_type = self.album_type;
|
||||
let mut album_type = AlbumType::Single;
|
||||
|
||||
let (artists, by_va) =
|
||||
match (subtitle_p1, subtitle_p2, &self.artists, self.artist_page) {
|
||||
|
@ -1028,7 +989,7 @@ impl MusicListMapper {
|
|||
}));
|
||||
Ok(Some(MusicItemType::Album))
|
||||
}
|
||||
MusicPageType::Playlist { is_podcast } => {
|
||||
MusicPageType::Playlist => {
|
||||
// When the playlist subtitle has only 1 part, it is a playlist from YT Music
|
||||
// (featured on the startpage or in genres)
|
||||
let from_ytm = subtitle_p2
|
||||
|
@ -1045,11 +1006,10 @@ impl MusicListMapper {
|
|||
channel,
|
||||
track_count: None,
|
||||
from_ytm,
|
||||
is_podcast,
|
||||
}));
|
||||
Ok(Some(MusicItemType::Playlist))
|
||||
}
|
||||
MusicPageType::None | MusicPageType::User => Ok(None),
|
||||
MusicPageType::None => Ok(None),
|
||||
},
|
||||
None => Err("could not determine item type".to_owned()),
|
||||
}
|
||||
|
@ -1120,7 +1080,7 @@ impl MusicListMapper {
|
|||
artists,
|
||||
album: None,
|
||||
view_count: None,
|
||||
track_type: vtype.into(),
|
||||
is_video: vtype.is_video(),
|
||||
track_nr: None,
|
||||
by_va,
|
||||
}));
|
||||
|
@ -1157,14 +1117,14 @@ impl MusicListMapper {
|
|||
artists,
|
||||
album,
|
||||
view_count,
|
||||
track_type: vtype.into(),
|
||||
is_video: vtype.is_video(),
|
||||
track_nr: None,
|
||||
by_va,
|
||||
}));
|
||||
}
|
||||
Some(MusicItemType::Track)
|
||||
}
|
||||
MusicPageType::Playlist { is_podcast } => {
|
||||
MusicPageType::Playlist => {
|
||||
let from_ytm = subtitle_p2
|
||||
.as_ref()
|
||||
.and_then(|p| p.0.first())
|
||||
|
@ -1181,23 +1141,9 @@ impl MusicListMapper {
|
|||
channel,
|
||||
track_count,
|
||||
from_ytm,
|
||||
is_podcast,
|
||||
}));
|
||||
Some(MusicItemType::Playlist)
|
||||
}
|
||||
MusicPageType::User => {
|
||||
// Part 1 may be the "Profile" label
|
||||
let handle = map_channel_handle(subtitle_p2.as_ref())
|
||||
.or_else(|| map_channel_handle(subtitle_p1.as_ref()));
|
||||
|
||||
self.items.push(MusicItem::User(UserItem {
|
||||
id: music_page.id,
|
||||
name: card.title,
|
||||
handle,
|
||||
avatar: card.thumbnail.into(),
|
||||
}));
|
||||
Some(MusicItemType::User)
|
||||
}
|
||||
MusicPageType::None => None,
|
||||
},
|
||||
None => {
|
||||
|
@ -1260,7 +1206,6 @@ impl MusicListMapper {
|
|||
MusicItem::Album(album) => albums.push(album),
|
||||
MusicItem::Artist(artist) => artists.push(artist),
|
||||
MusicItem::Playlist(playlist) => playlists.push(playlist),
|
||||
MusicItem::User(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1274,33 +1219,6 @@ impl MusicListMapper {
|
|||
warnings: self.warnings,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
pub fn conv_history_items(
|
||||
self,
|
||||
date_txt: Option<String>,
|
||||
utc_offset: UtcOffset,
|
||||
res: &mut MapResult<Vec<HistoryItem<TrackItem>>>,
|
||||
) {
|
||||
res.warnings.extend(self.warnings);
|
||||
res.c.extend(
|
||||
self.items
|
||||
.into_iter()
|
||||
.filter_map(TrackItem::from_ytm_item)
|
||||
.map(|item| HistoryItem {
|
||||
item,
|
||||
playback_date: date_txt.as_deref().and_then(|s| {
|
||||
timeago::parse_textual_date_to_d(
|
||||
self.lang,
|
||||
utc_offset,
|
||||
s,
|
||||
&mut res.warnings,
|
||||
)
|
||||
}),
|
||||
playback_date_txt: date_txt.clone(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Map TextComponents containing artist names to a list of artists and a 'Various Artists' flag
|
||||
|
@ -1338,12 +1256,6 @@ fn map_artist_id_fallback(
|
|||
.or_else(|| fallback_artist.and_then(|a| a.id.clone()))
|
||||
}
|
||||
|
||||
fn map_channel_handle(st: Option<&TextComponents>) -> Option<String> {
|
||||
st.map(|t| t.first_str())
|
||||
.filter(|t| t.starts_with('@'))
|
||||
.map(str::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn map_artist_id(entries: Vec<MusicItemMenuEntry>) -> Option<String> {
|
||||
entries.into_iter().find_map(|i| {
|
||||
if let NavigationEndpoint::Browse {
|
||||
|
@ -1414,7 +1326,7 @@ pub(crate) fn map_queue_item(item: QueueMusicItem, lang: Language) -> MapResult<
|
|||
artist_id,
|
||||
album,
|
||||
view_count,
|
||||
track_type: MusicVideoType::from_is_video(is_video).into(),
|
||||
is_video,
|
||||
track_nr: None,
|
||||
by_va,
|
||||
},
|
||||
|
@ -1435,18 +1347,13 @@ mod tests {
|
|||
fn map_album_type_samples() {
|
||||
let json_path = path!(*TESTFILES / "dict" / "album_type_samples.json");
|
||||
let json_file = File::open(json_path).unwrap();
|
||||
let atype_samples: BTreeMap<Language, BTreeMap<String, String>> =
|
||||
let atype_samples: BTreeMap<Language, BTreeMap<AlbumType, String>> =
|
||||
serde_json::from_reader(BufReader::new(json_file)).unwrap();
|
||||
|
||||
for (lang, entry) in &atype_samples {
|
||||
for (album_type_str, txt) in entry {
|
||||
let album_type_n = album_type_str.split('_').next().unwrap();
|
||||
let album_type = serde_plain::from_str::<AlbumType>(album_type_n).unwrap();
|
||||
for (album_type, txt) in entry {
|
||||
let res = map_album_type(txt, *lang);
|
||||
assert_eq!(
|
||||
res, album_type,
|
||||
"{album_type_str}: lang: {lang}, txt: {txt}"
|
||||
);
|
||||
assert_eq!(res, *album_type, "lang: {lang}, txt: {txt}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use serde::Deserialize;
|
||||
use serde_with::{serde_as, DefaultOnError, VecSkipError};
|
||||
|
||||
use crate::serializer::text::{AttributedText, Text, TextComponents};
|
||||
use crate::serializer::text::{Text, TextComponents};
|
||||
|
||||
use super::{
|
||||
music_item::{
|
||||
Button, ItemSection, MusicContentsRenderer, MusicItemMenuEntry, MusicThumbnailRenderer,
|
||||
},
|
||||
url_endpoint::OnTapWrap,
|
||||
ContentsRenderer, SectionList, Tab,
|
||||
};
|
||||
|
||||
|
@ -84,10 +83,6 @@ pub(crate) struct HeaderRenderer {
|
|||
#[serde(default)]
|
||||
#[serde_as(as = "Text")]
|
||||
pub second_subtitle: Vec<String>,
|
||||
/// Channel (newer data model)
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "DefaultOnError")]
|
||||
pub facepile: Option<AvatarStackViewModelWrap>,
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "DefaultOnError")]
|
||||
pub menu: Option<HeaderMenu>,
|
||||
|
@ -140,29 +135,6 @@ impl From<Description> for TextComponents {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AvatarStackViewModelWrap {
|
||||
pub avatar_stack_view_model: AvatarStackViewModel,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AvatarStackViewModel {
|
||||
// #[serde(default)]
|
||||
// pub avatars: Vec<AvatarViewModel>,
|
||||
#[serde_as(as = "AttributedText")]
|
||||
pub text: String,
|
||||
pub renderer_context: AvatarStackRendererContext,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AvatarStackRendererContext {
|
||||
pub command_context: Option<OnTapWrap>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Microformat {
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::ops::Range;
|
|||
|
||||
use serde::Deserialize;
|
||||
use serde_with::serde_as;
|
||||
use serde_with::{DefaultOnError, DisplayFromStr, VecSkipError};
|
||||
use serde_with::{DefaultOnError, DisplayFromStr};
|
||||
|
||||
use super::{Empty, ResponseContext, Thumbnails};
|
||||
use super::{ResponseContext, Thumbnails};
|
||||
use crate::serializer::{text::Text, MapResult};
|
||||
|
||||
#[serde_as]
|
||||
|
@ -19,10 +19,6 @@ pub(crate) struct Player {
|
|||
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||
pub storyboards: Option<Storyboards>,
|
||||
pub response_context: ResponseContext,
|
||||
#[serde(default)]
|
||||
pub player_config: PlayerConfig,
|
||||
#[serde(default)]
|
||||
pub heartbeat_params: HeartbeatParams,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
|
@ -61,6 +57,9 @@ pub(crate) enum PlayabilityStatus {
|
|||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Empty {}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ErrorScreen {
|
||||
|
@ -89,10 +88,6 @@ pub(crate) struct StreamingData {
|
|||
pub dash_manifest_url: Option<String>,
|
||||
/// Only on livestreams
|
||||
pub hls_manifest_url: Option<String>,
|
||||
pub drm_params: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde_as(deserialize_as = "VecSkipError<_>")]
|
||||
pub initial_authorized_drm_track_types: Vec<DrmTrackType>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
|
@ -141,16 +136,13 @@ pub(crate) struct Format {
|
|||
pub audio_track: Option<AudioTrack>,
|
||||
|
||||
pub signature_cipher: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
#[serde_as(deserialize_as = "VecSkipError<_>")]
|
||||
pub drm_families: Vec<DrmFamily>,
|
||||
pub drm_track_type: Option<DrmTrackType>,
|
||||
}
|
||||
|
||||
impl Format {
|
||||
pub fn is_audio(&self) -> bool {
|
||||
self.audio_quality.is_some() && self.audio_sample_rate.is_some()
|
||||
self.content_length.is_some()
|
||||
&& self.audio_quality.is_some()
|
||||
&& self.audio_sample_rate.is_some()
|
||||
}
|
||||
|
||||
pub fn is_video(&self) -> bool {
|
||||
|
@ -162,7 +154,7 @@ impl Format {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub(crate) enum Quality {
|
||||
Tiny,
|
||||
|
@ -176,7 +168,7 @@ pub(crate) enum Quality {
|
|||
Hd2160,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub(crate) enum AudioQuality {
|
||||
#[serde(rename = "AUDIO_QUALITY_ULTRALOW")]
|
||||
UltraLow,
|
||||
|
@ -188,7 +180,7 @@ pub(crate) enum AudioQuality {
|
|||
High,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub(crate) enum FormatType {
|
||||
#[default]
|
||||
|
@ -203,7 +195,7 @@ pub(crate) struct ColorInfo {
|
|||
pub primaries: Primaries,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub(crate) enum Primaries {
|
||||
#[default]
|
||||
|
@ -211,24 +203,6 @@ pub(crate) enum Primaries {
|
|||
ColorPrimariesBt2020,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub(crate) enum DrmTrackType {
|
||||
DrmTrackTypeAudio,
|
||||
DrmTrackTypeSd,
|
||||
DrmTrackTypeHd,
|
||||
DrmTrackTypeUhd1,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub(crate) enum DrmFamily {
|
||||
Widevine,
|
||||
Playready,
|
||||
Fairplay,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub(crate) struct AudioTrack {
|
||||
|
@ -290,57 +264,3 @@ pub(crate) struct Storyboards {
|
|||
pub(crate) struct StoryboardRenderer {
|
||||
pub spec: String,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct PlayerConfig {
|
||||
pub web_drm_config: Option<WebDrmConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct WebDrmConfig {
|
||||
pub widevine_service_cert: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct HeartbeatParams {
|
||||
pub drm_session_id: Option<String>,
|
||||
}
|
||||
|
||||
impl From<DrmTrackType> for crate::model::DrmTrackType {
|
||||
fn from(value: DrmTrackType) -> Self {
|
||||
match value {
|
||||
DrmTrackType::DrmTrackTypeAudio => Self::Audio,
|
||||
DrmTrackType::DrmTrackTypeSd => Self::Sd,
|
||||
DrmTrackType::DrmTrackTypeHd => Self::Hd,
|
||||
DrmTrackType::DrmTrackTypeUhd1 => Self::Uhd1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DrmFamily> for crate::model::DrmSystem {
|
||||
fn from(value: DrmFamily) -> Self {
|
||||
match value {
|
||||
DrmFamily::Widevine => Self::Widevine,
|
||||
DrmFamily::Playready => Self::Playready,
|
||||
DrmFamily::Fairplay => Self::Fairplay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct DrmLicense {
|
||||
pub status: String,
|
||||
pub license: String,
|
||||
pub authorized_formats: Vec<AuthorizedFormat>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct AuthorizedFormat {
|
||||
pub track_type: DrmTrackType,
|
||||
pub key_id: String,
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ use serde_with::{serde_as, DefaultOnError, VecSkipError};
|
|||
use crate::serializer::text::{AttributedText, Text, TextComponent, TextComponents};
|
||||
|
||||
use super::{
|
||||
url_endpoint::OnTapWrap, video_item::YouTubeListRenderer, Alert, ContentRenderer,
|
||||
url_endpoint::NavigationEndpoint, video_item::YouTubeListRenderer, Alert, ContentRenderer,
|
||||
ContentsRenderer, ImageView, PageHeaderRendererContent, PhMetadataView, ResponseContext,
|
||||
SectionList, Tab, TextBox, ThumbnailsWrap, TwoColumnBrowseResults,
|
||||
SectionList, Tab, ThumbnailsWrap, TwoColumnBrowseResults,
|
||||
};
|
||||
|
||||
#[serde_as]
|
||||
|
@ -70,7 +70,15 @@ pub(crate) struct PlaylistHeaderBanner {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct Byline {
|
||||
pub playlist_byline_renderer: TextBox,
|
||||
pub playlist_byline_renderer: BylineRenderer,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct BylineRenderer {
|
||||
#[serde_as(as = "Text")]
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -173,5 +181,17 @@ pub(crate) struct ActionsRow {
|
|||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ButtonAction {
|
||||
pub button_view_model: OnTapWrap,
|
||||
pub button_view_model: ButtonViewModel,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ButtonViewModel {
|
||||
pub on_tap: ActionOnTap,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ActionOnTap {
|
||||
pub innertube_command: NavigationEndpoint,
|
||||
}
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
use serde::Deserialize;
|
||||
use serde_with::{serde_as, DefaultOnError};
|
||||
|
||||
use crate::{
|
||||
model::{TrackType, UrlTarget},
|
||||
util,
|
||||
};
|
||||
|
||||
use super::Empty;
|
||||
use crate::{model::UrlTarget, util};
|
||||
|
||||
/// navigation/resolve_url response model
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -37,9 +32,6 @@ pub(crate) enum NavigationEndpoint {
|
|||
WatchPlaylist {
|
||||
watch_playlist_endpoint: WatchPlaylistEndpoint,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[allow(unused)]
|
||||
CreatePlaylist { create_playlist_endpoint: Empty },
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -162,18 +154,6 @@ pub(crate) struct WatchEndpointConfig {
|
|||
pub music_video_type: MusicVideoType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct OnTap {
|
||||
pub innertube_command: NavigationEndpoint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct OnTapWrap {
|
||||
pub on_tap: OnTap,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
|
||||
pub(crate) enum MusicVideoType {
|
||||
#[default]
|
||||
|
@ -199,16 +179,6 @@ impl MusicVideoType {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<MusicVideoType> for TrackType {
|
||||
fn from(value: MusicVideoType) -> Self {
|
||||
match value {
|
||||
MusicVideoType::Video => Self::Video,
|
||||
MusicVideoType::Track => Self::Track,
|
||||
MusicVideoType::Episode => Self::Episode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Deserialize, PartialEq, Eq)]
|
||||
pub(crate) enum PageType {
|
||||
#[serde(
|
||||
|
@ -255,9 +225,8 @@ impl PageType {
|
|||
pub(crate) enum MusicPageType {
|
||||
Artist,
|
||||
Album,
|
||||
Playlist { is_podcast: bool },
|
||||
Playlist,
|
||||
Track { vtype: MusicVideoType },
|
||||
User,
|
||||
None,
|
||||
}
|
||||
|
||||
|
@ -266,13 +235,11 @@ impl From<PageType> for MusicPageType {
|
|||
match t {
|
||||
PageType::Artist => MusicPageType::Artist,
|
||||
PageType::Album => MusicPageType::Album,
|
||||
PageType::Playlist => MusicPageType::Playlist { is_podcast: false },
|
||||
PageType::Podcast => MusicPageType::Playlist { is_podcast: true },
|
||||
PageType::Channel => MusicPageType::User,
|
||||
PageType::Playlist | PageType::Podcast => MusicPageType::Playlist,
|
||||
PageType::Channel | PageType::Unknown => MusicPageType::None,
|
||||
PageType::Episode => MusicPageType::Track {
|
||||
vtype: MusicVideoType::Episode,
|
||||
},
|
||||
PageType::Unknown => MusicPageType::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -341,11 +308,7 @@ impl NavigationEndpoint {
|
|||
watch_playlist_endpoint,
|
||||
} => Some(MusicPage {
|
||||
id: watch_playlist_endpoint.playlist_id,
|
||||
typ: MusicPageType::Playlist { is_podcast: false },
|
||||
}),
|
||||
NavigationEndpoint::CreatePlaylist { .. } => Some(MusicPage {
|
||||
id: String::new(),
|
||||
typ: MusicPageType::None,
|
||||
typ: MusicPageType::Playlist,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -390,7 +353,6 @@ impl NavigationEndpoint {
|
|||
NavigationEndpoint::WatchPlaylist {
|
||||
watch_playlist_endpoint,
|
||||
} => Some(watch_playlist_endpoint.playlist_id),
|
||||
NavigationEndpoint::CreatePlaylist { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -624,7 +624,6 @@ pub(crate) struct CommentViewModelWrap {
|
|||
pub(crate) struct CommentViewModel {
|
||||
pub comment_id: String,
|
||||
pub comment_key: String,
|
||||
pub comment_surface_key: String,
|
||||
pub toolbar_state_key: String,
|
||||
}
|
||||
|
||||
|
@ -696,7 +695,6 @@ pub(crate) struct AuthorCommentBadgeRenderer {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) enum Payload {
|
||||
CommentEntityPayload(CommentEntityPayload),
|
||||
CommentSurfaceEntityPayload(CommentSurfaceEntityPayload),
|
||||
#[serde(rename_all = "camelCase")]
|
||||
EngagementToolbarStateEntityPayload {
|
||||
heart_state: HeartState,
|
||||
|
@ -718,13 +716,6 @@ pub(crate) struct CommentEntityPayload {
|
|||
pub avatar: ImageView,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct CommentSurfaceEntityPayload {
|
||||
pub voice_reply_container_view_model: Option<VoiceReplyContainer>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -781,17 +772,3 @@ pub(crate) struct ContinuationButton {
|
|||
pub(crate) struct ContinuationButtonRenderer {
|
||||
pub command: ContinuationEndpoint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct VoiceReplyContainer {
|
||||
pub voice_reply_container_view_model: VoiceReplyContainer2,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct VoiceReplyContainer2 {
|
||||
#[serde_as(as = "AttributedText")]
|
||||
pub transcript_text: TextComponents,
|
||||
}
|
||||
|
|
|
@ -4,9 +4,12 @@ use serde_with::{
|
|||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use super::{ChannelBadge, ContentImage, ContinuationEndpoint, PhMetadataView, Thumbnails};
|
||||
use super::{ChannelBadge, ContinuationEndpoint, Thumbnails};
|
||||
use crate::{
|
||||
model::{Channel, ChannelItem, ChannelTag, PlaylistItem, VideoItem, YouTubeItem},
|
||||
model::{
|
||||
Channel, ChannelId, ChannelItem, ChannelTag, PlaylistItem, Verification, VideoItem,
|
||||
YouTubeItem,
|
||||
},
|
||||
param::Language,
|
||||
serializer::{
|
||||
text::{AttributedText, Text, TextComponent},
|
||||
|
@ -15,11 +18,6 @@ use crate::{
|
|||
util::{self, timeago, TryRemove},
|
||||
};
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
use crate::{client::response::SimpleHeaderRenderer, model::HistoryItem};
|
||||
#[cfg(feature = "userdata")]
|
||||
use time::UtcOffset;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -35,8 +33,6 @@ pub(crate) enum YouTubeListItem {
|
|||
|
||||
ChannelRenderer(ChannelRenderer),
|
||||
|
||||
LockupViewModel(LockupViewModel),
|
||||
|
||||
/// Continauation items are located at the end of a list
|
||||
/// and contain the continuation token for progressive loading
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -68,8 +64,6 @@ pub(crate) enum YouTubeListItem {
|
|||
/// GridRenderer: contains videos on channel page
|
||||
#[serde(alias = "expandedShelfContentsRenderer", alias = "gridRenderer")]
|
||||
ItemSectionRenderer {
|
||||
#[cfg(feature = "userdata")]
|
||||
header: Option<ItemSectionHeader>,
|
||||
#[serde(alias = "items")]
|
||||
contents: MapResult<Vec<YouTubeListItem>>,
|
||||
},
|
||||
|
@ -171,44 +165,6 @@ pub(crate) struct ShortsOverlayMetadata {
|
|||
pub secondary_text: Option<String>,
|
||||
}
|
||||
|
||||
/// Generalized list item, currently only used for channel playlists and YTM items
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct LockupViewModel {
|
||||
pub content_id: String,
|
||||
#[serde(default)]
|
||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||
pub content_type: LockupContentType,
|
||||
pub content_image: ContentImage,
|
||||
pub metadata: LockupViewModelMetadata,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub(crate) enum LockupContentType {
|
||||
LockupContentTypePlaylist,
|
||||
LockupContentTypeVideo,
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct LockupViewModelMetadata {
|
||||
pub lockup_metadata_view_model: LockupViewModelMetadataInner,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct LockupViewModelMetadataInner {
|
||||
#[serde_as(as = "AttributedText")]
|
||||
pub title: String,
|
||||
pub metadata: PhMetadataView,
|
||||
}
|
||||
|
||||
/// Video displayed in a playlist
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -301,13 +257,6 @@ pub(crate) struct YouTubeListRenderer {
|
|||
pub contents: MapResult<Vec<YouTubeListItem>>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct ItemSectionHeader {
|
||||
pub item_section_header_renderer: SimpleHeaderRenderer,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -523,18 +472,19 @@ impl<T> YouTubeListMapper<T> {
|
|||
thumbnail: video.thumbnail.into(),
|
||||
channel: video
|
||||
.channel
|
||||
.and_then(|c| ChannelTag::try_from(c).ok())
|
||||
.map(|mut c| {
|
||||
c.avatar = video
|
||||
.and_then(|c| {
|
||||
ChannelId::try_from(c).ok().map(|c| ChannelTag {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
avatar: video
|
||||
.channel_thumbnail_supported_renderers
|
||||
.map(|tn| tn.channel_thumbnail_with_link_renderer.thumbnail)
|
||||
.or(video.channel_thumbnail)
|
||||
.unwrap_or_default()
|
||||
.into();
|
||||
if !c.verification.verified() {
|
||||
c.verification = video.owner_badges.into();
|
||||
}
|
||||
c
|
||||
.into(),
|
||||
verification: video.owner_badges.into(),
|
||||
subscriber_count: None,
|
||||
})
|
||||
})
|
||||
.or_else(|| self.channel.clone()),
|
||||
publish_date: video
|
||||
|
@ -616,7 +566,16 @@ impl<T> YouTubeListMapper<T> {
|
|||
}
|
||||
|
||||
fn map_playlist_video(&mut self, video: PlaylistVideoRenderer) -> VideoItem {
|
||||
let channel = ChannelTag::try_from(video.channel).ok();
|
||||
let channel = ChannelId::try_from(video.channel)
|
||||
.ok()
|
||||
.map(|ch| ChannelTag {
|
||||
id: ch.id,
|
||||
name: ch.name,
|
||||
avatar: Vec::new(),
|
||||
verification: Verification::None,
|
||||
subscriber_count: None,
|
||||
});
|
||||
|
||||
let mut video_info = video.video_info.into_iter();
|
||||
let video_info1 = video_info
|
||||
.next()
|
||||
|
@ -679,12 +638,14 @@ impl<T> YouTubeListMapper<T> {
|
|||
.into(),
|
||||
channel: playlist
|
||||
.channel
|
||||
.and_then(|c| ChannelTag::try_from(c).ok())
|
||||
.map(|mut c| {
|
||||
if !c.verification.verified() {
|
||||
c.verification = playlist.owner_badges.into();
|
||||
}
|
||||
c
|
||||
.and_then(|c| {
|
||||
ChannelId::try_from(c).ok().map(|c| ChannelTag {
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
avatar: Vec::new(),
|
||||
verification: playlist.owner_badges.into(),
|
||||
subscriber_count: None,
|
||||
})
|
||||
})
|
||||
.or_else(|| self.channel.clone()),
|
||||
video_count: playlist.video_count.or_else(|| {
|
||||
|
@ -720,89 +681,6 @@ impl<T> YouTubeListMapper<T> {
|
|||
short_description: channel.description_snippet,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_lockup(&mut self, lockup: LockupViewModel) -> Option<YouTubeItem> {
|
||||
let md = lockup.metadata.lockup_metadata_view_model;
|
||||
let tn = lockup.content_image.into_image();
|
||||
match lockup.content_type {
|
||||
LockupContentType::LockupContentTypePlaylist => {
|
||||
Some(YouTubeItem::Playlist(PlaylistItem {
|
||||
id: lockup.content_id,
|
||||
name: md.title,
|
||||
thumbnail: tn.image.into(),
|
||||
channel: self.channel.clone(),
|
||||
video_count: tn
|
||||
.overlays
|
||||
.first()
|
||||
.and_then(|ol| {
|
||||
ol.thumbnail_overlay_badge_view_model
|
||||
.thumbnail_badges
|
||||
.first()
|
||||
})
|
||||
.and_then(|badge| {
|
||||
util::parse_numeric(&badge.thumbnail_badge_view_model.text).ok()
|
||||
}),
|
||||
}))
|
||||
}
|
||||
LockupContentType::LockupContentTypeVideo => {
|
||||
let mut mdr = md
|
||||
.metadata
|
||||
.content_metadata_view_model
|
||||
.metadata_rows
|
||||
.into_iter();
|
||||
let channel = mdr
|
||||
.next()
|
||||
.and_then(|r| r.metadata_parts.into_iter().next())
|
||||
.and_then(|p| ChannelTag::try_from(p.into_text_component()).ok());
|
||||
let (view_count, publish_date_txt) = mdr
|
||||
.next()
|
||||
.map(|metadata_row| {
|
||||
let mut parts = metadata_row.metadata_parts.into_iter();
|
||||
let p1 = parts.next();
|
||||
let p2 = parts.next();
|
||||
(
|
||||
p1.and_then(|p| {
|
||||
util::parse_large_numstr_or_warn(
|
||||
p.as_str(),
|
||||
self.lang,
|
||||
&mut self.warnings,
|
||||
)
|
||||
}),
|
||||
p2.map(|p2| p2.into_text_component().into_string()),
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
Some(YouTubeItem::Video(VideoItem {
|
||||
id: lockup.content_id,
|
||||
name: md.title,
|
||||
duration: tn
|
||||
.overlays
|
||||
.first()
|
||||
.and_then(|ol| {
|
||||
ol.thumbnail_overlay_badge_view_model
|
||||
.thumbnail_badges
|
||||
.first()
|
||||
})
|
||||
.and_then(|badge| {
|
||||
util::parse_video_length(&badge.thumbnail_badge_view_model.text)
|
||||
}),
|
||||
thumbnail: tn.image.into(),
|
||||
channel,
|
||||
publish_date: publish_date_txt.as_deref().and_then(|t| {
|
||||
timeago::parse_timeago_dt_or_warn(self.lang, t, &mut self.warnings)
|
||||
}),
|
||||
publish_date_txt,
|
||||
view_count,
|
||||
is_live: false,
|
||||
is_short: false,
|
||||
is_upcoming: false,
|
||||
short_description: None,
|
||||
}))
|
||||
}
|
||||
LockupContentType::Unknown => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl YouTubeListMapper<YouTubeItem> {
|
||||
|
@ -833,11 +711,6 @@ impl YouTubeListMapper<YouTubeItem> {
|
|||
let mapped = YouTubeItem::Channel(self.map_channel(channel));
|
||||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::LockupViewModel(lockup) => {
|
||||
if let Some(mapped) = self.map_lockup(lockup) {
|
||||
self.items.push(mapped);
|
||||
}
|
||||
}
|
||||
YouTubeListItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
} => self.ctoken = Some(continuation_endpoint.continuation_command.token),
|
||||
|
@ -847,7 +720,7 @@ impl YouTubeListMapper<YouTubeItem> {
|
|||
YouTubeListItem::RichItemRenderer { content } => {
|
||||
self.map_item(*content);
|
||||
}
|
||||
YouTubeListItem::ItemSectionRenderer { mut contents, .. } => {
|
||||
YouTubeListItem::ItemSectionRenderer { mut contents } => {
|
||||
self.warnings.append(&mut contents.warnings);
|
||||
contents.c.into_iter().for_each(|it| self.map_item(it));
|
||||
}
|
||||
|
@ -881,11 +754,6 @@ impl YouTubeListMapper<VideoItem> {
|
|||
let mapped = self.map_playlist_video(video);
|
||||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::LockupViewModel(lockup) => {
|
||||
if let Some(YouTubeItem::Video(mapped)) = self.map_lockup(lockup) {
|
||||
self.items.push(mapped);
|
||||
}
|
||||
}
|
||||
YouTubeListItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
} => self.ctoken = Some(continuation_endpoint.continuation_command.token),
|
||||
|
@ -895,7 +763,7 @@ impl YouTubeListMapper<VideoItem> {
|
|||
YouTubeListItem::RichItemRenderer { content } => {
|
||||
self.map_item(*content);
|
||||
}
|
||||
YouTubeListItem::ItemSectionRenderer { mut contents, .. } => {
|
||||
YouTubeListItem::ItemSectionRenderer { mut contents } => {
|
||||
self.warnings.append(&mut contents.warnings);
|
||||
contents.c.into_iter().for_each(|it| self.map_item(it));
|
||||
}
|
||||
|
@ -907,23 +775,6 @@ impl YouTubeListMapper<VideoItem> {
|
|||
self.warnings.append(&mut res.warnings);
|
||||
res.c.into_iter().for_each(|item| self.map_item(item));
|
||||
}
|
||||
|
||||
#[cfg(feature = "userdata")]
|
||||
pub(crate) fn conv_history_items(
|
||||
self,
|
||||
date_txt: Option<String>,
|
||||
utc_offset: UtcOffset,
|
||||
res: &mut MapResult<Vec<HistoryItem<VideoItem>>>,
|
||||
) {
|
||||
res.warnings.extend(self.warnings);
|
||||
res.c.extend(self.items.into_iter().map(|item| HistoryItem {
|
||||
item,
|
||||
playback_date: date_txt.as_deref().and_then(|s| {
|
||||
timeago::parse_textual_date_to_d(self.lang, utc_offset, s, &mut res.warnings)
|
||||
}),
|
||||
playback_date_txt: date_txt.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
impl YouTubeListMapper<PlaylistItem> {
|
||||
|
@ -933,11 +784,6 @@ impl YouTubeListMapper<PlaylistItem> {
|
|||
let mapped = self.map_playlist(playlist);
|
||||
self.items.push(mapped);
|
||||
}
|
||||
YouTubeListItem::LockupViewModel(lockup) => {
|
||||
if let Some(YouTubeItem::Playlist(mapped)) = self.map_lockup(lockup) {
|
||||
self.items.push(mapped);
|
||||
}
|
||||
}
|
||||
YouTubeListItem::ContinuationItemRenderer {
|
||||
continuation_endpoint,
|
||||
} => self.ctoken = Some(continuation_endpoint.continuation_command.token),
|
||||
|
@ -947,7 +793,7 @@ impl YouTubeListMapper<PlaylistItem> {
|
|||
YouTubeListItem::RichItemRenderer { content } => {
|
||||
self.map_item(*content);
|
||||
}
|
||||
YouTubeListItem::ItemSectionRenderer { mut contents, .. } => {
|
||||
YouTubeListItem::ItemSectionRenderer { mut contents } => {
|
||||
self.warnings.append(&mut contents.warnings);
|
||||
contents.c.into_iter().for_each(|it| self.map_item(it));
|
||||
}
|
||||
|
|
|
@ -120,9 +120,8 @@ impl<T: FromYtItem> MapResponse<SearchResult<T>> for response::Search {
|
|||
.filter_map(T::from_yt_item)
|
||||
.collect(),
|
||||
mapper.ctoken,
|
||||
ctx.visitor_data.map(str::to_owned),
|
||||
None,
|
||||
ContinuationEndpoint::Search,
|
||||
false,
|
||||
),
|
||||
corrected_query: mapper.corrected_query,
|
||||
visitor_data: self
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "NO SCRIPT, NO FEAR, ALL OPINION\nAn off-the-cuff Video Blog about Electronics Engineering, for engineers, hobbyists, enthusiasts, hackers and Makers\nHosted by Dave Jones from Sydney Australia\n\nDONATIONS:\nBitcoin: 3KqyH1U3qrMPnkLufM2oHDU7YB4zVZeFyZ\nEthereum: 0x99ccc4d2654ba40744a1f678d9868ecb15e91206\nPayPal: david@alternatezone.com\n\nPatreon: https://www.patreon.com/eevblog\n\nEEVblog2: http://www.youtube.com/EEVblog2\nEEVdiscover: https://www.youtube.com/channel/UCkGvUEt8iQLmq3aJIMjT2qQ\n\nEMAIL:\nAdvertising/Commercial: eevblog+business@gmail.com\nFan mail: eevblog+fan@gmail.com\nHate Mail: eevblog+hate@gmail.com\n\nI DON\'T DO PAID VIDEO SPONSORSHIPS, DON\'T ASK!\n\nPLEASE:\nDo NOT ask for personal advice on something, post it in the EEVblog forum.\nI read ALL email, but please don\'t be offended if I don\'t have time to reply, I get a LOT of email.\n\nMailbag\nPO Box 7949\nBaulkham Hills NSW 2153\nAUSTRALIA",
|
||||
tags: [
|
||||
"electronics",
|
||||
|
@ -125,7 +125,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -166,7 +166,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -207,7 +207,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -248,7 +248,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -289,7 +289,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -330,7 +330,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -371,7 +371,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -412,7 +412,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -453,7 +453,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -494,7 +494,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -535,7 +535,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -576,7 +576,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -617,7 +617,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -658,7 +658,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -699,7 +699,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -740,7 +740,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -781,7 +781,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -822,7 +822,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -863,7 +863,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -904,7 +904,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -945,7 +945,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -986,7 +986,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1027,7 +1027,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1068,7 +1068,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1109,7 +1109,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1150,7 +1150,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1191,7 +1191,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1232,7 +1232,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1273,7 +1273,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1314,7 +1314,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(884000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "NO SCRIPT, NO FEAR, ALL OPINION\nAn off-the-cuff Video Blog about Electronics Engineering, for engineers, hobbyists, enthusiasts, hackers and Makers\nHosted by Dave Jones from Sydney Australia\n\nDONATIONS:\nBitcoin: 3KqyH1U3qrMPnkLufM2oHDU7YB4zVZeFyZ\nEthereum: 0x99ccc4d2654ba40744a1f678d9868ecb15e91206\nPayPal: david@alternatezone.com\n\nPatreon: https://www.patreon.com/eevblog\n\nEEVblog2: http://www.youtube.com/EEVblog2\nEEVdiscover: https://www.youtube.com/channel/UCkGvUEt8iQLmq3aJIMjT2qQ\n\nEMAIL:\nAdvertising/Commercial: eevblog+business@gmail.com\nFan mail: eevblog+fan@gmail.com\nHate Mail: eevblog+hate@gmail.com\n\nI DON\'T DO PAID VIDEO SPONSORSHIPS, DON\'T ASK!\n\nPLEASE:\nDo NOT ask for personal advice on something, post it in the EEVblog forum.\nI read ALL email, but please don\'t be offended if I don\'t have time to reply, I get a LOT of email.\n\nMailbag\nPO Box 7949\nBaulkham Hills NSW 2153\nAUSTRALIA",
|
||||
tags: [
|
||||
"electronics",
|
||||
|
@ -109,7 +109,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(2),
|
||||
|
@ -128,7 +128,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(1),
|
||||
|
@ -147,7 +147,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(9),
|
||||
|
@ -166,7 +166,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(2),
|
||||
|
@ -185,7 +185,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(4),
|
||||
|
@ -204,7 +204,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(18),
|
||||
|
@ -223,7 +223,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
|
@ -242,7 +242,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(8),
|
||||
|
@ -261,7 +261,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(13),
|
||||
|
@ -280,7 +280,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(9),
|
||||
|
@ -299,7 +299,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(7),
|
||||
|
@ -318,7 +318,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
|
@ -337,7 +337,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(8),
|
||||
|
@ -356,7 +356,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(2),
|
||||
|
@ -375,7 +375,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
|
@ -394,7 +394,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(10),
|
||||
|
@ -413,7 +413,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(1),
|
||||
|
@ -432,7 +432,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(9),
|
||||
|
@ -451,7 +451,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(16),
|
||||
|
@ -470,7 +470,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(7),
|
||||
|
@ -489,7 +489,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(6),
|
||||
|
@ -508,7 +508,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(12),
|
||||
|
@ -527,7 +527,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(1),
|
||||
|
@ -546,7 +546,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(5),
|
||||
|
@ -565,7 +565,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(2),
|
||||
|
@ -584,7 +584,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(4),
|
||||
|
@ -603,7 +603,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(1),
|
||||
|
@ -622,7 +622,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(2),
|
||||
|
@ -641,7 +641,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(9),
|
||||
|
@ -660,7 +660,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(881000),
|
||||
)),
|
||||
video_count: Some(1),
|
|
@ -1,672 +0,0 @@
|
|||
---
|
||||
source: src/client/channel.rs
|
||||
expression: map_res.c
|
||||
---
|
||||
Channel(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
handle: Some("@EEVblog"),
|
||||
subscriber_count: Some(952000),
|
||||
video_count: Some(2),
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/ytc/AIdro_l17lYcTcRSydZeQK-RuiSfEeH5eX9m4irSNQj6109v5MQ=s72-c-k-c0x00ffffff-no-rj",
|
||||
width: 72,
|
||||
height: 72,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/ytc/AIdro_l17lYcTcRSydZeQK-RuiSfEeH5eX9m4irSNQj6109v5MQ=s120-c-k-c0x00ffffff-no-rj",
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/ytc/AIdro_l17lYcTcRSydZeQK-RuiSfEeH5eX9m4irSNQj6109v5MQ=s160-c-k-c0x00ffffff-no-rj",
|
||||
width: 160,
|
||||
height: 160,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
description: "NO SCRIPT, NO FEAR, ALL OPINION\nAn off-the-cuff Video Blog about Electronics Engineering, for engineers, hobbyists, enthusiasts, hackers and Makers\nHosted by Dave Jones from Sydney Australia\n\nDONATIONS:\nBitcoin: 3KqyH1U3qrMPnkLufM2oHDU7YB4zVZeFyZ\nEthereum: 0x99ccc4d2654ba40744a1f678d9868ecb15e91206\nPayPal: david@alternatezone.com\n\nPatreon: https://www.patreon.com/eevblog\n\nEEVblog2: http://www.youtube.com/EEVblog2\nEEVdiscover: https://www.youtube.com/channel/UCkGvUEt8iQLmq3aJIMjT2qQ\n\nEMAIL:\nAdvertising/Commercial: eevblog+business@gmail.com\nFan mail: eevblog+fan@gmail.com\nHate Mail: eevblog+hate@gmail.com\n\nI DON\'T DO PAID VIDEO SPONSORSHIPS, DON\'T ASK!\n\nPLEASE:\nDo NOT ask for personal advice on something, post it in the EEVblog forum.\nI read ALL email, but please don\'t be offended if I don\'t have time to reply, I get a LOT of email.\n\nMailbag\nPO Box 7949\nBaulkham Hills NSW 2153\nAUSTRALIA",
|
||||
tags: [
|
||||
"electronics",
|
||||
"engineering",
|
||||
"maker",
|
||||
"hacker",
|
||||
"design",
|
||||
"circuit",
|
||||
"hardware",
|
||||
"pic",
|
||||
"atmel",
|
||||
"oscilloscope",
|
||||
"multimeter",
|
||||
"diy",
|
||||
"hobby",
|
||||
"review",
|
||||
"teardown",
|
||||
"microcontroller",
|
||||
"arduino",
|
||||
"video",
|
||||
"blog",
|
||||
"tutorial",
|
||||
"how-to",
|
||||
"interview",
|
||||
"rant",
|
||||
"industry",
|
||||
"news",
|
||||
"mailbag",
|
||||
"dumpster diving",
|
||||
"debunking",
|
||||
],
|
||||
banner: [
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1060-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||
width: 1060,
|
||||
height: 175,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1138-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||
width: 1138,
|
||||
height: 188,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w1707-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||
width: 1707,
|
||||
height: 283,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2120-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||
width: 2120,
|
||||
height: 351,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2276-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||
width: 2276,
|
||||
height: 377,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://yt3.googleusercontent.com/yIJ9ad80n49rK-YUcZLe_8bLmR-aGyg5ybDH_XKIc0GDWrC6s1Wzz8lxnq3_hux_5b6NHPZ9=w2560-fcrop64=1,00005a57ffffa5a8-k-c0xffffffff-no-nd-rj",
|
||||
width: 2560,
|
||||
height: 424,
|
||||
),
|
||||
],
|
||||
has_shorts: true,
|
||||
has_live: true,
|
||||
visitor_data: None,
|
||||
content: Paginator(
|
||||
count: None,
|
||||
items: [
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHv268f0mW5m1t_hq_RVGRSA",
|
||||
name: "Jellybean Components Series",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/XYdmX8w8xwI/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCqmf6TGfDinNXhgU29ZxOkv2u9sQ",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(5),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHu46I7nFuUg3LC3PpiWTR4f",
|
||||
name: "Tandy Electronics / Radio Shack & Computers",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/uUXxY6gA-7g/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAlIVvQ4Axx40Xa_i8F56qmppXEXg",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(11),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHuS01_RNCnvpzyk7bycYCmM",
|
||||
name: "Open Source Hardware",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/m_8jh_MpWBE/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBx6U5iikp5rSO78dIWdy1RQ_BLNQ",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(4),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHuwwQ1fpquOJuA5MSfD4iD6",
|
||||
name: "Fluke Multimeters",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/ymJc5oxthlw/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDAOiw39aJajjAdroLnuj_fh60Ryw",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(22),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHs2LwEdDwTp3n7mxb-MyBbo",
|
||||
name: "EEVacademy Digital Design Tutorial Series",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/lJ3q9RHIatU/hqdefault.jpg?sqp=-oaymwExCOADEI4CSFryq4qpAyMIARUAAIhCGAHwAQH4Af4JgALQBYoCDAgAEAEYQyBXKGUwDw==&rs=AOn4CLBaaQaTJzi7H-zjwSsTlNJdBsyqvQ",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(5),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHu2v8THrRMt8E9ziHtRXPm7",
|
||||
name: "AI / ChatGPT",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/g5_Ts9SWbYs/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBmZPW6EiAvTCsI86BFg4BxXLj66A",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHvXuXRmoBUys09Dwi1heNii",
|
||||
name: "Shorts",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/ndvJtQ8nxV4/hqdefault.jpg?sqp=-oaymwExCOADEI4CSFryq4qpAyMIARUAAIhCGAHwAQH4AbYIgAKAD4oCDAgAEAEYNyBTKH8wDw==&rs=AOn4CLDD0qOLs38KPJtqdG6zCeVLQMf62Q",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(1),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHv3gxNg5BGoZJJu9htoAGB6",
|
||||
name: "Microcontrollers",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/L9Wrv7nW-S8/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDiAT5izyig1ntMSUhvSOVuYSsG1Q",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(1),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHvllTQ-vwvY26E3Bvrov93Y",
|
||||
name: "Bypass Capacitors",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/1xicZF9glH0/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAFb2FcbpdtAG1xLjmdkdIm1hFvgA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(4),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHtOV3AEwhuea4TnviddKfAj",
|
||||
name: "MacGyver Project",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/4yosozyeIP4/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAkwsCiJjFkWhYxtcg5NgfnQbkZrA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHuvHE5GQrQJxWXHdmW2l5IF",
|
||||
name: "Calculators",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/S3R4r2xvVYQ/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLB7HH5drG-33c1SyRe9kyZBrXvm3A",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(1),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHs6wRwVSaErU0BEnLiHfnKJ",
|
||||
name: "BM235",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/WPyEFB4cHkA/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAzBuQFV8T9hM8adlPvv58C9TeDug",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(9),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHu4k0ZkKFLsysSB5iava6Qu",
|
||||
name: "Vibration Measurement",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/uus_cpZiqsU/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCqdsjWVFaLOkEcXgbZD2Eca8MnuQ",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHtdQF-m5UFZ5GEjABadI3kI",
|
||||
name: "Component Selection",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/uq1DMWtjL2U/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAbgb1Jdb5P69JGdZQ-a8asLLyYdA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(6),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHtlndPUSOPgsujUdq1c5Mr9",
|
||||
name: "Solar Roadways",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/oIImmlfCyzo/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBxApgyGu3dNXRGoqLctVUnESpEIA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(23),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHvD6M_7WeN071OVsZFE0_q-",
|
||||
name: "Electronics Tutorials - AC Circuit Theory Series",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/rrPtvYYJ2-g/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBEVc71xxSjJ-xlA_dDQaYIjdHyUw",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHtVLq2MDPIz82BWMIZcuwhK",
|
||||
name: "Electronics Tutorial - DC Fundamentals",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/xSRe_4TQbuo/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDP4V24_MG6vzvUZsHep9WFSCCY6Q",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(8),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHvIDfW3x2p4BY6l4RYgfBJE",
|
||||
name: "Oscilloscope Probing",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/OiAmER1OJh4/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAXeGAvEc8y3pEsPUxWdsNIP9UmPw",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(14),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHu6Jjb8U82eKQfvKhJVl0Bu",
|
||||
name: "Thermal Design",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/8ruFVmxf0zs/hqdefault.jpg?sqp=-oaymwExCOADEI4CSFryq4qpAyMIARUAAIhCGAHwAQH4Af4JgALQBYoCDAgAEAEYfyA1KDUwDw==&rs=AOn4CLD6PMawyYXKe8KT1-Y6vWjQc2xIDw",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(9),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHs-X2Awg33PCBNrP2BGFVhC",
|
||||
name: "Electric Cars",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/CPcZm1Tu5VI/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCsm8De0QaHPaeCZqxMp_F464fWzg",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(9),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHuLODLTeq3PM-OJRP2nzNUa",
|
||||
name: "Designing a better uCurrent",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/0AEVilxXAAo/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCjotFuRjPPBHd2LWzt3lviPj9HaA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHtvTKP4RTNW1-08Kmzy1pvA",
|
||||
name: "EMC Compliance & Measurement",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/lYmfVMWbIHQ/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBtygEqMXx7Lwe5SuBWt2q0CSahYA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(8),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHuUTpCrTVX7BdU68l2aVqMv",
|
||||
name: "Power Counter Display Project",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/nTpE1Nw3Yy4/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLAbPl28_i7isizY6A1t2_c6gV8BAQ",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(2),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHvm120Tq40nKrM5SUBlolN3",
|
||||
name: "Live - Ask Dave",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/gQ7TTuiDH1M/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBMnucUil90WeDSIeFz8mZCOtEv9g",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(3),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHsiF93KOLoF1KAHArmIW9lC",
|
||||
name: "Padauk Microcontroller",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/r45r4rV5JOI/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCn4kGWcjBOhk3vN8QPMDa9L3mkKA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(10),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHvxTzBLwUFw4My4rtrNFzED",
|
||||
name: "Other Debunking Videos",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/WopuF9vD7KE/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLBv5buh3qMs4feQaPj6Fy6bxl_vuA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(1),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHt2pJ7X5tumuM4Wa3r1OC7Q",
|
||||
name: "Audio & Speakers",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/qHbkw0Gm7pk/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCJBYXTDttGHTm51j3bfwqxOqVFig",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(9),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHtX7OearWdmqGzqiu4DHKWi",
|
||||
name: "Cameras",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/g9umAQ1-an4/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLCB5jNm9U-rypnpthK_N321LpYWew",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(16),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHu-TaNRp27_PiXjBG5wY9Gv",
|
||||
name: "Cryptocurrency",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/ibPgfzd9zd8/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDe3IXT88HR3XxnxfqrpAxh6pfYMg",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(7),
|
||||
),
|
||||
PlaylistItem(
|
||||
id: "PLvOlSehNtuHvmK-VGcZ33ZuATmcNB8tvH",
|
||||
name: "LCD Tutorial",
|
||||
thumbnail: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/ZYvxgl-9tNM/hqdefault.jpg?sqp=-oaymwEXCOADEI4CSFryq4qpAwkIARUAAIhCGAE=&rs=AOn4CLDv2WT4Chl1_H2G43AjfSFpPcKVoA",
|
||||
width: 480,
|
||||
height: 270,
|
||||
),
|
||||
],
|
||||
channel: Some(ChannelTag(
|
||||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
subscriber_count: Some(952000),
|
||||
)),
|
||||
video_count: Some(6),
|
||||
),
|
||||
],
|
||||
ctoken: Some("4qmFsgLCARIYVUMyRGpGRTdYZjExVVJacVdCaWdjVk9RGnRFZ2x3YkdGNWJHbHpkSE1ZQXlBQk1BRTRBZW9EUEVOblRrUlJhbEZUU2tKSmFWVkZlREpVTW5oVVdsZG9UMlJJVmtsa2JURk1URlphU0ZreGIzcE5NWEF4VVZaU2RGa3dOVU5QU0ZJeVUwTm5PQSUzRCUzRJoCL2Jyb3dzZS1mZWVkVUMyRGpGRTdYZjExVVJacVdCaWdjVk9RcGxheWxpc3RzMTA0"),
|
||||
endpoint: browse,
|
||||
),
|
||||
)
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||
tags: [],
|
||||
banner: [
|
||||
|
@ -81,7 +81,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -107,7 +107,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -133,7 +133,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -159,7 +159,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -185,7 +185,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -211,7 +211,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -237,7 +237,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -263,7 +263,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -289,7 +289,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -315,7 +315,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -341,7 +341,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -367,7 +367,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -393,7 +393,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -419,7 +419,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -445,7 +445,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -471,7 +471,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -497,7 +497,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -523,7 +523,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -549,7 +549,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -575,7 +575,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -601,7 +601,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -627,7 +627,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -653,7 +653,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -679,7 +679,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -705,7 +705,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -731,7 +731,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -757,7 +757,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -783,7 +783,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -809,7 +809,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -835,7 +835,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -861,7 +861,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -887,7 +887,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -913,7 +913,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -939,7 +939,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -965,7 +965,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -991,7 +991,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1017,7 +1017,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1043,7 +1043,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1069,7 +1069,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1095,7 +1095,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1121,7 +1121,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1147,7 +1147,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1173,7 +1173,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1199,7 +1199,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1225,7 +1225,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1251,7 +1251,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1277,7 +1277,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1303,7 +1303,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3360000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 160,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||
tags: [],
|
||||
banner: [
|
||||
|
@ -81,7 +81,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -107,7 +107,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -133,7 +133,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -159,7 +159,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -185,7 +185,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -211,7 +211,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -237,7 +237,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -263,7 +263,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -289,7 +289,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -315,7 +315,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -341,7 +341,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -367,7 +367,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -393,7 +393,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -419,7 +419,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -445,7 +445,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -471,7 +471,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -497,7 +497,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -523,7 +523,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -549,7 +549,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -575,7 +575,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -601,7 +601,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -627,7 +627,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -653,7 +653,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -679,7 +679,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -705,7 +705,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -731,7 +731,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -757,7 +757,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -783,7 +783,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -809,7 +809,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -835,7 +835,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -861,7 +861,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -887,7 +887,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -913,7 +913,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -939,7 +939,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -965,7 +965,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -991,7 +991,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1017,7 +1017,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1043,7 +1043,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1069,7 +1069,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1095,7 +1095,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1121,7 +1121,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1147,7 +1147,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1173,7 +1173,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1199,7 +1199,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1225,7 +1225,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1251,7 +1251,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1277,7 +1277,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1303,7 +1303,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3740000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 160,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||
tags: [],
|
||||
banner: [
|
||||
|
@ -81,7 +81,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -107,7 +107,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -133,7 +133,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -159,7 +159,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -185,7 +185,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -211,7 +211,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -237,7 +237,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -263,7 +263,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -289,7 +289,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -315,7 +315,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -341,7 +341,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -367,7 +367,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -393,7 +393,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -419,7 +419,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -445,7 +445,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -471,7 +471,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -497,7 +497,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -523,7 +523,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -549,7 +549,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -575,7 +575,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -601,7 +601,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -627,7 +627,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -653,7 +653,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -679,7 +679,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -705,7 +705,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -731,7 +731,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -757,7 +757,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -783,7 +783,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -809,7 +809,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -835,7 +835,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -861,7 +861,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -887,7 +887,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -913,7 +913,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -939,7 +939,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -965,7 +965,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -991,7 +991,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1017,7 +1017,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1043,7 +1043,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1069,7 +1069,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1095,7 +1095,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1121,7 +1121,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1147,7 +1147,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1173,7 +1173,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1199,7 +1199,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1225,7 +1225,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1251,7 +1251,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1277,7 +1277,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1303,7 +1303,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(3970000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||
tags: [],
|
||||
banner: [
|
||||
|
@ -96,7 +96,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -137,7 +137,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -178,7 +178,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -219,7 +219,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -260,7 +260,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -301,7 +301,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -342,7 +342,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -383,7 +383,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -424,7 +424,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -465,7 +465,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -506,7 +506,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -547,7 +547,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -588,7 +588,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -629,7 +629,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -670,7 +670,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -711,7 +711,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -752,7 +752,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -793,7 +793,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -834,7 +834,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -875,7 +875,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -916,7 +916,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -957,7 +957,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -998,7 +998,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1039,7 +1039,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1080,7 +1080,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1121,7 +1121,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1162,7 +1162,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1203,7 +1203,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1244,7 +1244,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1285,7 +1285,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2930000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "NO SCRIPT, NO FEAR, ALL OPINION\nAn off-the-cuff Video Blog about Electronics Engineering, for engineers, hobbyists, enthusiasts, hackers and Makers\nHosted by Dave Jones from Sydney Australia\n\nDONATIONS:\nBitcoin: 3KqyH1U3qrMPnkLufM2oHDU7YB4zVZeFyZ\nEthereum: 0x99ccc4d2654ba40744a1f678d9868ecb15e91206\nPayPal: david@alternatezone.com\n\nPatreon: https://www.patreon.com/eevblog\n\nEEVblog2: http://www.youtube.com/EEVblog2\nEEVdiscover: https://www.youtube.com/channel/UCkGvUEt8iQLmq3aJIMjT2qQ\n\nEMAIL:\nAdvertising/Commercial: eevblog+business@gmail.com\nFan mail: eevblog+fan@gmail.com\nHate Mail: eevblog+hate@gmail.com\n\nI DON\'T DO PAID VIDEO SPONSORSHIPS, DON\'T ASK!\n\nPLEASE:\nDo NOT ask for personal advice on something, post it in the EEVblog forum.\nI read ALL email, but please don\'t be offended if I don\'t have time to reply, I get a LOT of email.\n\nMailbag\nPO Box 7949\nBaulkham Hills NSW 2153\nAUSTRALIA",
|
||||
tags: [
|
||||
"electronics",
|
||||
|
@ -125,7 +125,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -166,7 +166,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -207,7 +207,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -248,7 +248,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -289,7 +289,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -330,7 +330,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -371,7 +371,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -412,7 +412,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -453,7 +453,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -494,7 +494,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -535,7 +535,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -576,7 +576,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -617,7 +617,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -658,7 +658,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -699,7 +699,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -740,7 +740,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -781,7 +781,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -822,7 +822,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -863,7 +863,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -904,7 +904,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -945,7 +945,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -986,7 +986,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1027,7 +1027,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1068,7 +1068,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1109,7 +1109,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1150,7 +1150,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1191,7 +1191,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1232,7 +1232,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1273,7 +1273,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1314,7 +1314,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(883000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -20,7 +20,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "April 14-16 & 21-23, 2023\n",
|
||||
tags: [
|
||||
"coachella",
|
||||
|
@ -70,7 +70,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -111,7 +111,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -152,7 +152,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -193,7 +193,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -234,7 +234,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -275,7 +275,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -316,7 +316,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -357,7 +357,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -398,7 +398,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -439,7 +439,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -480,7 +480,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -521,7 +521,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -562,7 +562,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -603,7 +603,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -644,7 +644,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -685,7 +685,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -726,7 +726,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -767,7 +767,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -808,7 +808,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -849,7 +849,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -890,7 +890,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -931,7 +931,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -972,7 +972,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1013,7 +1013,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1054,7 +1054,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1095,7 +1095,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1136,7 +1136,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1177,7 +1177,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1218,7 +1218,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1259,7 +1259,7 @@ Channel(
|
|||
id: "UCHF66aWLOxBW4l6VkSrS3cQ",
|
||||
name: "Coachella",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2710000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 160,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "NO SCRIPT, NO FEAR, ALL OPINION\nAn off-the-cuff Video Blog about Electronics Engineering, for engineers, hobbyists, enthusiasts, hackers and Makers\nHosted by Dave Jones from Sydney Australia\n\nDONATIONS:\nBitcoin: 3KqyH1U3qrMPnkLufM2oHDU7YB4zVZeFyZ\nEthereum: 0x99ccc4d2654ba40744a1f678d9868ecb15e91206\nPayPal: david@alternatezone.com\n\nPatreon: https://www.patreon.com/eevblog\n\nEEVblog2: http://www.youtube.com/EEVblog2\nEEVdiscover: https://www.youtube.com/channel/UCkGvUEt8iQLmq3aJIMjT2qQ\n\nEMAIL:\nAdvertising/Commercial: eevblog+business@gmail.com\nFan mail: eevblog+fan@gmail.com\nHate Mail: eevblog+hate@gmail.com\n\nI DON\'T DO PAID VIDEO SPONSORSHIPS, DON\'T ASK!\n\nPLEASE:\nDo NOT ask for personal advice on something, post it in the EEVblog forum.\nI read ALL email, but please don\'t be offended if I don\'t have time to reply, I get a LOT of email.\n\nMailbag\nPO Box 7949\nBaulkham Hills NSW 2153\nAUSTRALIA",
|
||||
tags: [
|
||||
"electronics",
|
||||
|
@ -125,7 +125,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -166,7 +166,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -207,7 +207,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -248,7 +248,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -289,7 +289,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -330,7 +330,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -371,7 +371,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -412,7 +412,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -453,7 +453,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -494,7 +494,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -535,7 +535,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -576,7 +576,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -617,7 +617,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -658,7 +658,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -699,7 +699,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -740,7 +740,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -781,7 +781,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -822,7 +822,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -863,7 +863,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -904,7 +904,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -945,7 +945,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -986,7 +986,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1027,7 +1027,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1068,7 +1068,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1109,7 +1109,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1150,7 +1150,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1191,7 +1191,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1232,7 +1232,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1273,7 +1273,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1314,7 +1314,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(933000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "NO SCRIPT, NO FEAR, ALL OPINION\nAn off-the-cuff Video Blog about Electronics Engineering, for engineers, hobbyists, enthusiasts, hackers and Makers\nHosted by Dave Jones from Sydney Australia\n\nDONATIONS:\nBitcoin: 3KqyH1U3qrMPnkLufM2oHDU7YB4zVZeFyZ\nEthereum: 0x99ccc4d2654ba40744a1f678d9868ecb15e91206\nPayPal: david@alternatezone.com\n\nPatreon: https://www.patreon.com/eevblog\n\nEEVblog2: http://www.youtube.com/EEVblog2\nEEVdiscover: https://www.youtube.com/channel/UCkGvUEt8iQLmq3aJIMjT2qQ\n\nEMAIL:\nAdvertising/Commercial: eevblog+business@gmail.com\nFan mail: eevblog+fan@gmail.com\nHate Mail: eevblog+hate@gmail.com\n\nI DON\'T DO PAID VIDEO SPONSORSHIPS, DON\'T ASK!\n\nPLEASE:\nDo NOT ask for personal advice on something, post it in the EEVblog forum.\nI read ALL email, but please don\'t be offended if I don\'t have time to reply, I get a LOT of email.\n\nMailbag\nPO Box 7949\nBaulkham Hills NSW 2153\nAUSTRALIA",
|
||||
tags: [
|
||||
"electronics",
|
||||
|
@ -125,7 +125,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -166,7 +166,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -207,7 +207,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -248,7 +248,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -289,7 +289,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -330,7 +330,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -371,7 +371,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -412,7 +412,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -453,7 +453,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -494,7 +494,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -535,7 +535,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -576,7 +576,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -617,7 +617,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -658,7 +658,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -699,7 +699,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -740,7 +740,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -781,7 +781,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -822,7 +822,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -863,7 +863,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -904,7 +904,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -945,7 +945,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -986,7 +986,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1027,7 +1027,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1068,7 +1068,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1109,7 +1109,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1150,7 +1150,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1191,7 +1191,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1232,7 +1232,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1273,7 +1273,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1314,7 +1314,7 @@ Channel(
|
|||
id: "UC2DjFE7Xf11URZqWBigcVOQ",
|
||||
name: "EEVblog",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(880000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: none,
|
||||
verification: None,
|
||||
description: "",
|
||||
tags: [],
|
||||
banner: [],
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "Welcome to The Good Life by Sensual Musique.\nThe second official channel of Sensual Musique. You can find a lot of music, live streams and some other things on this channel. Stay tuned :)\n\nSubmit your music here: submit.sensualmusiquenetwork@gmail.com",
|
||||
tags: [
|
||||
"live radio",
|
||||
|
@ -109,7 +109,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -150,7 +150,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -191,7 +191,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -232,7 +232,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -273,7 +273,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -314,7 +314,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -355,7 +355,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -396,7 +396,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -437,7 +437,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -478,7 +478,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -519,7 +519,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -560,7 +560,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -601,7 +601,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -642,7 +642,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -683,7 +683,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -724,7 +724,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -765,7 +765,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -806,7 +806,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -847,7 +847,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -888,7 +888,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -929,7 +929,7 @@ Channel(
|
|||
id: "UChs0pSaEoNLV4mevBFGaoKA",
|
||||
name: "The Good Life Radio x Sensual Musique",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(760000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: none,
|
||||
verification: None,
|
||||
description: "",
|
||||
tags: [],
|
||||
banner: [
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "Hi, I’m Tina, aka Doobydobap!\n\nFood is the medium I use to tell stories and connect with people who share the same passion as I do. Whether it’s because you’re hungry at midnight or trying to learn how to cook, I hope you enjoy watching my content and recipes. Don\'t yuck my yum!\n\nwww.doobydobap.com\n",
|
||||
tags: [],
|
||||
banner: [
|
||||
|
@ -81,7 +81,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -107,7 +107,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -148,7 +148,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -174,7 +174,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -200,7 +200,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -226,7 +226,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -267,7 +267,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -293,7 +293,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -319,7 +319,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -345,7 +345,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -386,7 +386,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -412,7 +412,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -453,7 +453,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -479,7 +479,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -505,7 +505,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -531,7 +531,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -557,7 +557,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -583,7 +583,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -609,7 +609,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -635,7 +635,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -676,7 +676,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -702,7 +702,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -728,7 +728,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -754,7 +754,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -780,7 +780,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -806,7 +806,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -832,7 +832,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -858,7 +858,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -899,7 +899,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -925,7 +925,7 @@ Channel(
|
|||
id: "UCh8gHdtzO2tXd593_bjErWg",
|
||||
name: "Doobydobap",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(2840000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -25,7 +25,7 @@ Channel(
|
|||
height: 176,
|
||||
),
|
||||
],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
description: "BRAND NEW SECOND CHANNEL: https://youtube.com/channel/UCcsQYra-bISsFxNqnd6Javw\n\nJoin my Discord: https://discord.gg/2YcarWsc8S\n",
|
||||
tags: [
|
||||
"politics",
|
||||
|
@ -113,7 +113,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: Some("2022-09-27T16:00:00Z"),
|
||||
|
@ -154,7 +154,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -195,7 +195,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -236,7 +236,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -277,7 +277,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -318,7 +318,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -359,7 +359,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -400,7 +400,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -441,7 +441,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -482,7 +482,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -523,7 +523,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -564,7 +564,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -605,7 +605,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -646,7 +646,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -687,7 +687,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -728,7 +728,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -769,7 +769,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -810,7 +810,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -851,7 +851,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -892,7 +892,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -933,7 +933,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -974,7 +974,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1015,7 +1015,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1056,7 +1056,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1097,7 +1097,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1138,7 +1138,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1179,7 +1179,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1220,7 +1220,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1261,7 +1261,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
@ -1302,7 +1302,7 @@ Channel(
|
|||
id: "UCcvfHa-GHSOHFAjU0-Ie57A",
|
||||
name: "Adam Something",
|
||||
avatar: [],
|
||||
verification: verified,
|
||||
verification: Verified,
|
||||
subscriber_count: Some(947000),
|
||||
)),
|
||||
publish_date: "[date]",
|
||||
|
|
|
@ -1,943 +0,0 @@
|
|||
---
|
||||
source: src/client/music_artist.rs
|
||||
expression: artist
|
||||
---
|
||||
MusicArtist(
|
||||
id: "UCOR4_bSVIXPsGa4BbCSt60Q",
|
||||
name: "Trailerpark",
|
||||
header_image: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/II101BviJo-tGcGg1KKWSU8D3EZjALHQMbQ4v-7-hP4Zfk1pBESaTCLcz8eQb-hggzxq4Z1MuFkBeRE=w540-h225-p-l90-rj",
|
||||
width: 540,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/II101BviJo-tGcGg1KKWSU8D3EZjALHQMbQ4v-7-hP4Zfk1pBESaTCLcz8eQb-hggzxq4Z1MuFkBeRE=w721-h300-p-l90-rj",
|
||||
width: 721,
|
||||
height: 300,
|
||||
),
|
||||
],
|
||||
description: None,
|
||||
wikipedia_url: None,
|
||||
subscriber_count: Some(270000),
|
||||
tracks: [
|
||||
TrackItem(
|
||||
id: "YvidasjVLXk",
|
||||
name: "Bleib in der Schule",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/V_tvMqbuXgDgoAKuYZ-VFRru3cUb2WQvwO6vVBKY8pdFYAl1dkuIv_W2afjMUNN6uVNxet6r7mHISh0s=w60-h60-l90-rj",
|
||||
width: 60,
|
||||
height: 60,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/V_tvMqbuXgDgoAKuYZ-VFRru3cUb2WQvwO6vVBKY8pdFYAl1dkuIv_W2afjMUNN6uVNxet6r7mHISh0s=w120-h120-l90-rj",
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: Some(AlbumId(
|
||||
id: "MPREb_8PsIyll0LFV",
|
||||
name: "Bleib in der Schule",
|
||||
)),
|
||||
view_count: Some(71000000),
|
||||
track_type: track,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "h3T_NXRUUjM",
|
||||
name: "Fledermausland",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/1fPBoTszY4e6Nf8egSwBTHWsQT8hotwhDnjArd1SHS8gZc5asCoo_3Z2WhN1IO2KMqyYly0xm7mMZ43d=w60-h60-l90-rj",
|
||||
width: 60,
|
||||
height: 60,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/1fPBoTszY4e6Nf8egSwBTHWsQT8hotwhDnjArd1SHS8gZc5asCoo_3Z2WhN1IO2KMqyYly0xm7mMZ43d=w120-h120-l90-rj",
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: Some(AlbumId(
|
||||
id: "MPREb_POeT6m0bw9q",
|
||||
name: "Crackstreet Boys II X Version",
|
||||
)),
|
||||
view_count: Some(30000000),
|
||||
track_type: track,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "XZfoFwWvkGQ",
|
||||
name: "Sterben kannst du überall",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/eQCwnR4YLYnizEhQKeSDDE3rulSTo64cTfs8fxR1K-3iWUfC477SHV0ZOOoQa2vJuvr_9i_WDYI-wbo=w60-h60-l90-rj",
|
||||
width: 60,
|
||||
height: 60,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/eQCwnR4YLYnizEhQKeSDDE3rulSTo64cTfs8fxR1K-3iWUfC477SHV0ZOOoQa2vJuvr_9i_WDYI-wbo=w120-h120-l90-rj",
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: Some(AlbumId(
|
||||
id: "MPREb_UYdRV1nnK2J",
|
||||
name: "TP4L",
|
||||
)),
|
||||
view_count: Some(40000000),
|
||||
track_type: track,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "LOuVxwVFJhs",
|
||||
name: "Selbstbefriedigung",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/1fPBoTszY4e6Nf8egSwBTHWsQT8hotwhDnjArd1SHS8gZc5asCoo_3Z2WhN1IO2KMqyYly0xm7mMZ43d=w60-h60-l90-rj",
|
||||
width: 60,
|
||||
height: 60,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/1fPBoTszY4e6Nf8egSwBTHWsQT8hotwhDnjArd1SHS8gZc5asCoo_3Z2WhN1IO2KMqyYly0xm7mMZ43d=w120-h120-l90-rj",
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: Some(AlbumId(
|
||||
id: "MPREb_POeT6m0bw9q",
|
||||
name: "Crackstreet Boys II X Version",
|
||||
)),
|
||||
view_count: Some(13000000),
|
||||
track_type: track,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "GePZUYeIQQQ",
|
||||
name: "Falsche Band",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/MIuap-H2LxqP5O7Dry1LdShBFBbg5YTjIPjuXOHWyrKlmnOogsO5cTk6yXH97DhI3WjZg0z3y-jkQxaM=w60-h60-l90-rj",
|
||||
width: 60,
|
||||
height: 60,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/MIuap-H2LxqP5O7Dry1LdShBFBbg5YTjIPjuXOHWyrKlmnOogsO5cTk6yXH97DhI3WjZg0z3y-jkQxaM=w120-h120-l90-rj",
|
||||
width: 120,
|
||||
height: 120,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: Some(AlbumId(
|
||||
id: "MPREb_bi34SGT1xlc",
|
||||
name: "Crackstreet Boys 3 (Bonus Tracks Version)",
|
||||
)),
|
||||
view_count: Some(13000000),
|
||||
track_type: track,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "0mcING0Zdis",
|
||||
name: "Trailerpark - TP4L (Live Abschiedskonzert)",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/0mcING0Zdis/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3k5JY0WRBeKNaotfUYrpbObz1mceA",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/0mcING0Zdis/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3kinVfBJUF-SDFagYKazKmS_ad75w",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(13000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "EAC-2ttHCyk",
|
||||
name: "Fledermausland (Bonus Track)",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/EAC-2ttHCyk/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nlrgFTz_pwbBwXFbaASgklpX78vA",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/EAC-2ttHCyk/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3nHzhiahqhmIkZ0eUXD09BGak2MHQ",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(25000000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "Bret5VaVzJk",
|
||||
name: "New Kids on the Blech (Bonus Track)",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/Bret5VaVzJk/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nFa4qUxqJzCtxr-zPdzP15Ixvu-A",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/Bret5VaVzJk/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3l1hGZVAWUwaJQbZXmbRpcbsMdTeQ",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(6900000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "EqP1_IcjW-s",
|
||||
name: "Pimpulsiv feat. DNP, Sudden & Dana - Wohnwagensiedlung",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/EqP1_IcjW-s/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3lIeltSLpA_XwwZzdJfHnNZ0vqBzA",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/EqP1_IcjW-s/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3nfiByY3RfcFYGfg92C5Vlkar0GJA",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(7100000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "3EoF9Of98e4",
|
||||
name: "Armut treibt Jugendliche in die Popmusik",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/3EoF9Of98e4/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3kvWHX-5mYREKEkf-CM3TLfjrLjlw",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/3EoF9Of98e4/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3lItzsg6wamh_xSdpoZxTWOHHLS-g",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(5400000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "qr0eN_uIcTs",
|
||||
name: "Bleib in der Schule (Live in Berlin)",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/qr0eN_uIcTs/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nspTbohIYzDFOjTg90KEmKecVVvg",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/qr0eN_uIcTs/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3n0SIeq4dPTPvbGv4STsTWNt24cig",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(56000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "McgSyiug6XE",
|
||||
name: "We Are Family",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/McgSyiug6XE/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3nxe3Xz99BVFg-VOra20J682me5JQ",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/McgSyiug6XE/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3lSGwKx_hnqYA-CkoLHapr1PiyX6w",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
ArtistId(
|
||||
id: Some("UC5HSrFHr6lMzwAyGjlClm0A"),
|
||||
name: "Timi Hendrix",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(1800000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "ioZxvVhjFs8",
|
||||
name: "Schlechter Tag",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/ioZxvVhjFs8/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3ltQmZbH1DF9nmho5HLGehqLSGzTw",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/ioZxvVhjFs8/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3lsluKPeCNxP7QoOCc24tZy4jsn7Q",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(7100000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "3jyZJEcomkw",
|
||||
name: "Timi Hendrix feat. Alligatoah - Schlaflos in Guantanamo ► prod. by Mantra",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/3jyZJEcomkw/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3k46-OFTCnpEJry_PNst1C11FPA1A",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/3jyZJEcomkw/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3kN1ryaQSy4M_Y9bQGh9S-tbYGqdg",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(1500000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
TrackItem(
|
||||
id: "9oM-cflYhGk",
|
||||
name: "Timi Hendrix - Kaiser von China (Official Video) 🐲",
|
||||
duration: None,
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/9oM-cflYhGk/sddefault.jpg?sqp=-oaymwEWCJADEOEBIAQqCghqEJQEGHgg6AJIWg&rs=AMzJL3m6MksfA1NWyIMv6cTk03J21pA0NQ",
|
||||
width: 400,
|
||||
height: 225,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://i.ytimg.com/vi/9oM-cflYhGk/hq720.jpg?sqp=-oaymwEXCKAGEMIDIAQqCwjVARCqCBh4INgESFo&rs=AMzJL3n7oy_XobzQBkUVxEx08iSKNPIB0Q",
|
||||
width: 800,
|
||||
height: 450,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album: None,
|
||||
view_count: Some(1100000),
|
||||
track_type: video,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
],
|
||||
albums: [
|
||||
AlbumItem(
|
||||
id: "MPREb_UYdRV1nnK2J",
|
||||
name: "TP4L",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/eQCwnR4YLYnizEhQKeSDDE3rulSTo64cTfs8fxR1K-3iWUfC477SHV0ZOOoQa2vJuvr_9i_WDYI-wbo=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/eQCwnR4YLYnizEhQKeSDDE3rulSTo64cTfs8fxR1K-3iWUfC477SHV0ZOOoQa2vJuvr_9i_WDYI-wbo=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: album,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_bi34SGT1xlc",
|
||||
name: "Crackstreet Boys 3 (Bonus Tracks Version)",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/MIuap-H2LxqP5O7Dry1LdShBFBbg5YTjIPjuXOHWyrKlmnOogsO5cTk6yXH97DhI3WjZg0z3y-jkQxaM=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/MIuap-H2LxqP5O7Dry1LdShBFBbg5YTjIPjuXOHWyrKlmnOogsO5cTk6yXH97DhI3WjZg0z3y-jkQxaM=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: album,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_5gkbwhqC4AJ",
|
||||
name: "Goldener Schluss (Live in Berlin)",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/ilzR9UxpZFwHZnYOL0L504H6a0Y8k_zPk0AYOhBiBqIjq4TGnX-B1uKcNah56dmjPZoDvp9vGWyfgY8=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/ilzR9UxpZFwHZnYOL0L504H6a0Y8k_zPk0AYOhBiBqIjq4TGnX-B1uKcNah56dmjPZoDvp9vGWyfgY8=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: album,
|
||||
year: Some(2024),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_HPXN9BBzFpV",
|
||||
name: "TP4L",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/8Ftr5oIt1q6RbGkdiW7cefw-XGUplUXcjXXN7QntI1Nzh_6oR0euh7Lj2Ner3yXV--U-hVxJewkeq8A=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/8Ftr5oIt1q6RbGkdiW7cefw-XGUplUXcjXXN7QntI1Nzh_6oR0euh7Lj2Ner3yXV--U-hVxJewkeq8A=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: single,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_hcK0fXETEf9",
|
||||
name: "Endlich normale Leute",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/MW37LppS1rjDQIl5GaG0BxKeWk5fs4xphr6rU0z-KmJiHbvMbA3K5ZzrA9avinP2LjNrDGwB5tSLLsqe=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/MW37LppS1rjDQIl5GaG0BxKeWk5fs4xphr6rU0z-KmJiHbvMbA3K5ZzrA9avinP2LjNrDGwB5tSLLsqe=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: single,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_R6EV2L1q0oc",
|
||||
name: "Armut treibt Jugendliche in die Popmusik",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/kqKBF4JPQhKY1099AzRpJFGc2P7TFuFa2GeM7z8GGfTJ_DkfAzKTdV8gPtfVkyA5HQ0uZn3XG-VtMVj0=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/kqKBF4JPQhKY1099AzRpJFGc2P7TFuFa2GeM7z8GGfTJ_DkfAzKTdV8gPtfVkyA5HQ0uZn3XG-VtMVj0=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: single,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_oHieBHkXn3A",
|
||||
name: "Dicks Sucken",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/IVvdOUgbTECe2cVKrwhhCYmhHuipV6p0t5cLqMYWm3E_23zBEABxodGiSuX3H_AxRcEZk2-4V-k3RZw6=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/IVvdOUgbTECe2cVKrwhhCYmhHuipV6p0t5cLqMYWm3E_23zBEABxodGiSuX3H_AxRcEZk2-4V-k3RZw6=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: single,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_8PsIyll0LFV",
|
||||
name: "Bleib in der Schule",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/V_tvMqbuXgDgoAKuYZ-VFRru3cUb2WQvwO6vVBKY8pdFYAl1dkuIv_W2afjMUNN6uVNxet6r7mHISh0s=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/V_tvMqbuXgDgoAKuYZ-VFRru3cUb2WQvwO6vVBKY8pdFYAl1dkuIv_W2afjMUNN6uVNxet6r7mHISh0s=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: single,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_POeT6m0bw9q",
|
||||
name: "Crackstreet Boys II X Version",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/1fPBoTszY4e6Nf8egSwBTHWsQT8hotwhDnjArd1SHS8gZc5asCoo_3Z2WhN1IO2KMqyYly0xm7mMZ43d=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/1fPBoTszY4e6Nf8egSwBTHWsQT8hotwhDnjArd1SHS8gZc5asCoo_3Z2WhN1IO2KMqyYly0xm7mMZ43d=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: ep,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_tdFqP579jQz",
|
||||
name: "Bleib in der Schule (Live in Berlin)",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/VNjspSA1Fm0yFJEKUCuetOziiET6sQG9QXQCiydknEny98Lc_MEmUp8e37FtCbDz1bQ6yvM6AqpsvL0=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/VNjspSA1Fm0yFJEKUCuetOziiET6sQG9QXQCiydknEny98Lc_MEmUp8e37FtCbDz1bQ6yvM6AqpsvL0=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: single,
|
||||
year: Some(2024),
|
||||
by_va: false,
|
||||
),
|
||||
AlbumItem(
|
||||
id: "MPREb_kLvmX2AzYBL",
|
||||
name: "Bleib in der Schule (Live at Wacken 2019)",
|
||||
cover: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/dV3PCeAdRQgLOuSUdIfA4q8jVgNwSoTceeK085ZOCzEe6YBm5c9gNIvO8wGM_K2NKpip-8-PxJtWEPJo=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/dV3PCeAdRQgLOuSUdIfA4q8jVgNwSoTceeK085ZOCzEe6YBm5c9gNIvO8wGM_K2NKpip-8-PxJtWEPJo=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
artists: [
|
||||
ArtistId(
|
||||
id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
name: "Trailerpark",
|
||||
),
|
||||
],
|
||||
artist_id: Some("UCOR4_bSVIXPsGa4BbCSt60Q"),
|
||||
album_type: single,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
],
|
||||
playlists: [],
|
||||
similar_artists: [
|
||||
ArtistItem(
|
||||
id: "UCVRREKn7V1Cb8qvf43dwZ6w",
|
||||
name: "257ers",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/yPjiQ4ZVblOXbft1Yo2jd3uJXKJDuSWOP1MCAG6kTIwYqTWsOKRbZBnPhW4gjzvvVll7yVtjbu3e3Q=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/yPjiQ4ZVblOXbft1Yo2jd3uJXKJDuSWOP1MCAG6kTIwYqTWsOKRbZBnPhW4gjzvvVll7yVtjbu3e3Q=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(67300),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UCuNyvmBfTzQZmWI2rsVX3QQ",
|
||||
name: "Alligatoah",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/ffIVPiIldrcfp9UEoAbDid6fnAOajn_kgI4OisFoFhK28rk3HVdpYfe2h27T3d_hHfNR943PPSOhHw=w226-h226-p-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/ffIVPiIldrcfp9UEoAbDid6fnAOajn_kgI4OisFoFhK28rk3HVdpYfe2h27T3d_hHfNR943PPSOhHw=w544-h544-p-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(779000),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UCO04sIqN7F4ff2-1ycVZSgQ",
|
||||
name: "Sudden",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/TEdMt2cE-UCbnjm6AJtyasWv9-a3LFpdmh2X6w3iBwIMATHUtYIQ_F0cJ30vL5m6uJkqL3qFvNYLpYrN=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/TEdMt2cE-UCbnjm6AJtyasWv9-a3LFpdmh2X6w3iBwIMATHUtYIQ_F0cJ30vL5m6uJkqL3qFvNYLpYrN=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(3660),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UC5k_3LEPSGchsGEGpqoF6dg",
|
||||
name: "K.I.Z",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/PVaIRDAgRRyLMuFp7OTS7h3HEMoY9ejKxt7GLgfgi6aFt3bP-Edb1YU5t1IlGN0Z-qcrb86qspETNoI=w226-h226-p-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/PVaIRDAgRRyLMuFp7OTS7h3HEMoY9ejKxt7GLgfgi6aFt3bP-Edb1YU5t1IlGN0Z-qcrb86qspETNoI=w544-h544-p-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(522000),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UCG8K_22LRSRwqhoJXBWGmbA",
|
||||
name: "FiNCH",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/cofqKPsHr5dzuLexkKAYQF3vVMkKTT2FuZgIMXs6XIO3J8diK29qqfKQkqrga8NOCmwVl7x-w4z3mQ=w226-h226-p-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/cofqKPsHr5dzuLexkKAYQF3vVMkKTT2FuZgIMXs6XIO3J8diK29qqfKQkqrga8NOCmwVl7x-w4z3mQ=w544-h544-p-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(533000),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UC5HSrFHr6lMzwAyGjlClm0A",
|
||||
name: "Timi Hendrix",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/1yi83YgKDBSQ0rgsA2GuZRa0rBABPR2BH41DsuCfGMRmLdF9oR7vv7T6QGLbhNP8FfX6qVHUQci4YM8=w226-h226-p-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/1yi83YgKDBSQ0rgsA2GuZRa0rBABPR2BH41DsuCfGMRmLdF9oR7vv7T6QGLbhNP8FfX6qVHUQci4YM8=w544-h544-p-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(6410),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UC9izv9vxcTVKA1IibcGTrNA",
|
||||
name: "Pimpulsiv",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/QXuirXSQsdO1KUZCz-ZX-kRVSorZxIUC4YrxQD0IeSr1mY-42VwvAjf4TTownRVzm-02-U8kLM3VuETf9w=w226-h226-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/QXuirXSQsdO1KUZCz-ZX-kRVSorZxIUC4YrxQD0IeSr1mY-42VwvAjf4TTownRVzm-02-U8kLM3VuETf9w=w544-h544-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(985),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UCgosMU69MpoCqhuS1JZj6Cw",
|
||||
name: "Sido",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/HZpnexwxNS5FkIrpz6hdHZuNhBS-GKjs0C9NU8nDSTmHFlPaviqxV-dDLS_ubSEbpEvu0m2P2WT3kaQ=w226-h226-p-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/HZpnexwxNS5FkIrpz6hdHZuNhBS-GKjs0C9NU8nDSTmHFlPaviqxV-dDLS_ubSEbpEvu0m2P2WT3kaQ=w544-h544-p-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(1550000),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UCAiLb3B6iCjxv7HhPf1S4ag",
|
||||
name: "Marteria",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/Ms5gYOttabL03qfFYx7SNhRsx-K_Y7hxMN0WXgc7iquYAfLV5cgYZfTBn3nsi0_sN5BaqAaIr1z5iGc=w226-h226-p-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/Ms5gYOttabL03qfFYx7SNhRsx-K_Y7hxMN0WXgc7iquYAfLV5cgYZfTBn3nsi0_sN5BaqAaIr1z5iGc=w544-h544-p-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(422000),
|
||||
),
|
||||
ArtistItem(
|
||||
id: "UCtoec88rzlhABHeo_4d-H8g",
|
||||
name: "Dame",
|
||||
avatar: [
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/lkbE9cB4qTxtRmzkjAaLEHrpIgeCOzeBXaL4BpBRq6wp4PlCoSIFej3ita3du8lqniIA67NRYfsVwuFj=w226-h226-p-l90-rj",
|
||||
width: 226,
|
||||
height: 226,
|
||||
),
|
||||
Thumbnail(
|
||||
url: "https://lh3.googleusercontent.com/lkbE9cB4qTxtRmzkjAaLEHrpIgeCOzeBXaL4BpBRq6wp4PlCoSIFej3ita3du8lqniIA67NRYfsVwuFj=w544-h544-p-l90-rj",
|
||||
width: 544,
|
||||
height: 544,
|
||||
),
|
||||
],
|
||||
subscriber_count: Some(37700),
|
||||
),
|
||||
],
|
||||
tracks_playlist_id: Some("OLAK5uy_miHesZCUQY5S9EwqfoNP2tZR9nZ0NBAeU"),
|
||||
videos_playlist_id: Some("OLAK5uy_mqbgE6T9uvusUWrAxJGiImf4_P4dM7IvQ"),
|
||||
radio_id: Some("RDEM7AbogW0cCnElSU0WYm1GqA"),
|
||||
)
|
File diff suppressed because it is too large
Load diff
|
@ -59,7 +59,6 @@ MusicArtist(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLwkM1QxaP343YqeP6g5VPGsgJdO1_SV4I",
|
||||
|
@ -82,7 +81,6 @@ MusicArtist(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLwkM1QxaP340xbkARIPpiD1aHuzJVuZUg",
|
||||
|
@ -105,7 +103,6 @@ MusicArtist(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLwkM1QxaP342hjju64dtqG5wKqx2hNgjr",
|
||||
|
@ -128,7 +125,6 @@ MusicArtist(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLwkM1QxaP342v1hhoB3XLiruSQOzmdmBt",
|
||||
|
@ -151,7 +147,6 @@ MusicArtist(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLwkM1QxaP342EBMza0AG10nB3oDD65RPY",
|
||||
|
@ -174,7 +169,6 @@ MusicArtist(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLwkM1QxaP342nVAeBVL6_Q8gbbAD8l4wb",
|
||||
|
@ -197,7 +191,6 @@ MusicArtist(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLwkM1QxaP3438x6ta8VJZlJSlDn43FueA",
|
||||
|
@ -220,7 +213,6 @@ MusicArtist(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
],
|
||||
similar_artists: [],
|
||||
|
|
|
@ -64,7 +64,7 @@ MusicArtist(
|
|||
name: "÷ (Deluxe)",
|
||||
)),
|
||||
view_count: Some(5700000000),
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -96,7 +96,7 @@ MusicArtist(
|
|||
name: "Shape of You",
|
||||
)),
|
||||
view_count: Some(8700000000),
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -128,7 +128,7 @@ MusicArtist(
|
|||
name: "x (Deluxe Edition)",
|
||||
)),
|
||||
view_count: Some(3300000000),
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -160,7 +160,7 @@ MusicArtist(
|
|||
name: "x (Deluxe Edition)",
|
||||
)),
|
||||
view_count: Some(4500000000),
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -192,7 +192,7 @@ MusicArtist(
|
|||
name: "Bad Habits",
|
||||
)),
|
||||
view_count: Some(1100000000),
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -221,7 +221,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(378000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -250,7 +250,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(250000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -279,7 +279,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(372000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -308,7 +308,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(1000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -337,7 +337,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(3800000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -366,7 +366,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(6300000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -395,7 +395,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(1400000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -424,7 +424,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(3800000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -453,7 +453,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(641000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -482,7 +482,7 @@ MusicArtist(
|
|||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album: None,
|
||||
view_count: Some(364000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -510,7 +510,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2024),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -536,7 +536,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -562,7 +562,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -588,7 +588,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -614,7 +614,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2021),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -640,7 +640,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2019),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -666,7 +666,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -692,7 +692,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: ep,
|
||||
album_type: Ep,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -718,7 +718,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -744,7 +744,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2011),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -770,7 +770,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -796,7 +796,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -822,7 +822,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -848,7 +848,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -874,7 +874,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -900,7 +900,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2023),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -926,7 +926,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -952,7 +952,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -978,7 +978,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1004,7 +1004,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UClmXPfaYhXOYsNn_QUyheWQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1028,7 +1028,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_mkPdnadBmgXk28mbGxm_5uGeKvHrec208",
|
||||
|
@ -1048,7 +1047,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_m0wlRoNn5iCTTgBedfoOQ19Jq9P3XTLIA",
|
||||
|
@ -1068,7 +1066,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_l1oO11DBO4FD8U7bOrqUKK5Y_PkISUMQM",
|
||||
|
@ -1088,7 +1085,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_khs3a0YMI9WYs2k1Oqb2ukWX3dA3-lnwI",
|
||||
|
@ -1108,7 +1104,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_mfdqvCAl8wodlx2P2_Ai2gNkiRDAufkkI",
|
||||
|
@ -1128,7 +1123,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_nxuz8sV0R7aWiLsbDv5W9_Bvp0X9PxFjY",
|
||||
|
@ -1148,7 +1142,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_kPqJ_FiGk-lbXtgM4IF42uokskSJZiVTI",
|
||||
|
@ -1168,7 +1161,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_nfs_t4FUu00E5ED6lveEBBX1VMYe1mFjk",
|
||||
|
@ -1188,7 +1180,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_kEeMXVnyMll_xhEBH1Aza4lEYO58yeQ0M",
|
||||
|
@ -1208,7 +1199,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
],
|
||||
similar_artists: [
|
||||
|
|
|
@ -64,7 +64,7 @@ MusicArtist(
|
|||
name: "Evolve",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -96,7 +96,7 @@ MusicArtist(
|
|||
name: "Mercury : Acts 1 & 2",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -140,7 +140,7 @@ MusicArtist(
|
|||
name: "Mercury : Acts 1 & 2",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -172,7 +172,7 @@ MusicArtist(
|
|||
name: "Evolve",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -204,7 +204,7 @@ MusicArtist(
|
|||
name: "Night Visions",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -233,7 +233,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(2100000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -262,7 +262,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(2400000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -291,7 +291,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(207000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -320,7 +320,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(324000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -349,7 +349,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(1900000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -378,7 +378,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(1000000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -407,7 +407,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(1400000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -431,7 +431,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(440000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -460,7 +460,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(557000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -489,7 +489,7 @@ MusicArtist(
|
|||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album: None,
|
||||
view_count: Some(877000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -517,7 +517,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -543,7 +543,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -569,7 +569,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -595,7 +595,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -621,7 +621,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -647,7 +647,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2021),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -673,7 +673,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2021),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -699,7 +699,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2021),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -725,7 +725,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2021),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -751,7 +751,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2021),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -777,7 +777,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2019),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -803,7 +803,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2018),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -829,7 +829,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2018),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -855,7 +855,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2018),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -881,7 +881,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2018),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -907,7 +907,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -933,7 +933,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -959,7 +959,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -985,7 +985,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2016),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1011,7 +1011,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2016),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1037,7 +1037,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2015),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1063,7 +1063,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2015),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1089,7 +1089,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1115,7 +1115,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2011),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1141,7 +1141,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: ep,
|
||||
album_type: Ep,
|
||||
year: Some(2010),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1167,7 +1167,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: ep,
|
||||
album_type: Ep,
|
||||
year: Some(2009),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1193,7 +1193,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: ep,
|
||||
album_type: Ep,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1219,7 +1219,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1245,7 +1245,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2021),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1271,7 +1271,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1297,7 +1297,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1323,7 +1323,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2017),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1349,7 +1349,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2015),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1375,7 +1375,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2015),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1401,7 +1401,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2015),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1427,7 +1427,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1453,7 +1453,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1479,7 +1479,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2014),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1505,7 +1505,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UC0aXrjVxG5pZr99v77wZdPQ"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2013),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1529,7 +1529,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_mIpIa-YIJFJe0EAcNbcMPgg-3qCdK9qAk",
|
||||
|
@ -1549,7 +1548,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_nbzJVrwitbeDjlcHvjM7fgF7khtUOoHgU",
|
||||
|
@ -1569,7 +1567,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_nCs5nAmZrJ41ILrSyf36UvOwTBNyx0oEI",
|
||||
|
@ -1589,7 +1586,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_nGXEmbtrmoUF9NG7m0WkxpF_qLKYR3YOU",
|
||||
|
@ -1609,7 +1605,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_mgHrXs_5F6wPwPFA47S8yrzCfjCi4AXDE",
|
||||
|
@ -1629,7 +1624,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_l7u7RCjtiI_I3m5EgnI-V9yWAgx0RNy1E",
|
||||
|
@ -1649,7 +1643,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_k7h5535MeHE8xmgHsrZx7HOKH4lb5vAfY",
|
||||
|
@ -1669,7 +1662,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_mlCByo5eM1tLBhUdMyn2GphTXICCM_W1w",
|
||||
|
@ -1689,7 +1681,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "RDCLAK5uy_ke0QH8jvXz6ynXEhn_mbCBy9m7fbnJ9NY",
|
||||
|
@ -1709,7 +1700,6 @@ MusicArtist(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
],
|
||||
similar_artists: [
|
||||
|
|
|
@ -64,7 +64,7 @@ MusicArtist(
|
|||
name: "고블린 Goblin",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -96,7 +96,7 @@ MusicArtist(
|
|||
name: "고블린 Goblin",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -128,7 +128,7 @@ MusicArtist(
|
|||
name: "고블린 Goblin",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -157,7 +157,7 @@ MusicArtist(
|
|||
artist_id: Some("UCfwCE5VhPMGxNPFxtVv7lRw"),
|
||||
album: None,
|
||||
view_count: Some(20000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -186,7 +186,7 @@ MusicArtist(
|
|||
artist_id: Some("UClGBYGUZmpzUaHgeb9gOBww"),
|
||||
album: None,
|
||||
view_count: Some(211000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -215,7 +215,7 @@ MusicArtist(
|
|||
artist_id: Some("UCfaO3pZL5XOr8BvNZkrKeVA"),
|
||||
album: None,
|
||||
view_count: Some(10000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -244,7 +244,7 @@ MusicArtist(
|
|||
artist_id: Some("UCgVWicpO5Jn3VfxqgIU6cpA"),
|
||||
album: None,
|
||||
view_count: Some(15000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -273,7 +273,7 @@ MusicArtist(
|
|||
artist_id: Some("UCe52oeb7Xv_KaJsEzcKXJJg"),
|
||||
album: None,
|
||||
view_count: Some(1200),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -302,7 +302,7 @@ MusicArtist(
|
|||
artist_id: Some("UCFFvwAcyQhpeQfuAgBN1XZw"),
|
||||
album: None,
|
||||
view_count: Some(12000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -331,7 +331,7 @@ MusicArtist(
|
|||
artist_id: Some("UC_xEL8cbkItBH00KrGz9fbQ"),
|
||||
album: None,
|
||||
view_count: Some(7400),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -360,7 +360,7 @@ MusicArtist(
|
|||
artist_id: Some("UCaFqztcJss3HrXNurzQJyqQ"),
|
||||
album: None,
|
||||
view_count: Some(1400),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -389,7 +389,7 @@ MusicArtist(
|
|||
artist_id: Some("UCMPqKiPdiSoi8eCW5Dou1IQ"),
|
||||
album: None,
|
||||
view_count: Some(25000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -418,7 +418,7 @@ MusicArtist(
|
|||
artist_id: Some("UCe52oeb7Xv_KaJsEzcKXJJg"),
|
||||
album: None,
|
||||
view_count: Some(3700),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -446,7 +446,7 @@ MusicArtist(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UCfwCE5VhPMGxNPFxtVv7lRw"),
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2019),
|
||||
by_va: false,
|
||||
),
|
||||
|
|
|
@ -33,7 +33,7 @@ MusicCharts(
|
|||
artist_id: Some("UCiXhCjTprNP0nuQJ9UsLWeg"),
|
||||
album: None,
|
||||
view_count: Some(56000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -62,7 +62,7 @@ MusicCharts(
|
|||
artist_id: Some("UCybEdRVR5u_WFoV-BLTEBiA"),
|
||||
album: None,
|
||||
view_count: Some(15000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -91,7 +91,7 @@ MusicCharts(
|
|||
artist_id: Some("UCiY3z8HAGD6BlSNKVn2kSvQ"),
|
||||
album: None,
|
||||
view_count: Some(521000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -120,7 +120,7 @@ MusicCharts(
|
|||
artist_id: Some("UCWsDFcIhY2DBi3GB5uykGXA"),
|
||||
album: None,
|
||||
view_count: Some(34000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -149,7 +149,7 @@ MusicCharts(
|
|||
artist_id: Some("UCiY3z8HAGD6BlSNKVn2kSvQ"),
|
||||
album: None,
|
||||
view_count: Some(559000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -178,7 +178,7 @@ MusicCharts(
|
|||
artist_id: Some("UCMXDyVR2tclKWhbqNforSyA"),
|
||||
album: None,
|
||||
view_count: Some(39000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -207,7 +207,7 @@ MusicCharts(
|
|||
artist_id: Some("UCJa2FF4TUB13Mm0GurZAqog"),
|
||||
album: None,
|
||||
view_count: Some(139000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -236,7 +236,7 @@ MusicCharts(
|
|||
artist_id: Some("UCKRnq8aBOCanYlffje7HyvA"),
|
||||
album: None,
|
||||
view_count: Some(311000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -265,7 +265,7 @@ MusicCharts(
|
|||
artist_id: Some("UCR28YDxjDE3ogQROaNdnRbQ"),
|
||||
album: None,
|
||||
view_count: Some(3800000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -294,7 +294,7 @@ MusicCharts(
|
|||
artist_id: Some("UCpcTrCXblq78GZrTUTLWeBw"),
|
||||
album: None,
|
||||
view_count: Some(46000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -323,7 +323,7 @@ MusicCharts(
|
|||
artist_id: Some("UCJa2FF4TUB13Mm0GurZAqog"),
|
||||
album: None,
|
||||
view_count: Some(73000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -356,7 +356,7 @@ MusicCharts(
|
|||
artist_id: Some("UCohgH17dyp4c_V7U9LoBLdA"),
|
||||
album: None,
|
||||
view_count: Some(77000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -385,7 +385,7 @@ MusicCharts(
|
|||
artist_id: Some("UChWPNW87QHcXAsw2mzlsYNw"),
|
||||
album: None,
|
||||
view_count: Some(2600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -414,7 +414,7 @@ MusicCharts(
|
|||
artist_id: Some("UC_z9AthnCGSAk_tZf-KqoFA"),
|
||||
album: None,
|
||||
view_count: Some(17000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -451,7 +451,7 @@ MusicCharts(
|
|||
artist_id: Some("UCdPdi8UM25ZyvzhSJkk1uPw"),
|
||||
album: None,
|
||||
view_count: Some(8600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -480,7 +480,7 @@ MusicCharts(
|
|||
artist_id: Some("UC_z9AthnCGSAk_tZf-KqoFA"),
|
||||
album: None,
|
||||
view_count: Some(15000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -509,7 +509,7 @@ MusicCharts(
|
|||
artist_id: Some("UCXT9NWRyDfHJq9Igm1pDQpQ"),
|
||||
album: None,
|
||||
view_count: Some(31000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -542,7 +542,7 @@ MusicCharts(
|
|||
artist_id: Some("UC7n3gWRN0vQzgiOKc51aZ4w"),
|
||||
album: None,
|
||||
view_count: Some(202000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -571,7 +571,7 @@ MusicCharts(
|
|||
artist_id: Some("UCGJdT8Qip4XObbQZ98Z1CAA"),
|
||||
album: None,
|
||||
view_count: Some(4900000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -600,7 +600,7 @@ MusicCharts(
|
|||
artist_id: Some("UC7n3gWRN0vQzgiOKc51aZ4w"),
|
||||
album: None,
|
||||
view_count: Some(545000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -633,7 +633,7 @@ MusicCharts(
|
|||
artist_id: Some("UC5IkSn-EFsUu3XANYklXc8g"),
|
||||
album: None,
|
||||
view_count: Some(20000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -666,7 +666,7 @@ MusicCharts(
|
|||
artist_id: Some("UC7n3gWRN0vQzgiOKc51aZ4w"),
|
||||
album: None,
|
||||
view_count: Some(36000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -699,7 +699,7 @@ MusicCharts(
|
|||
artist_id: Some("UCgpBsaDW2n_6ruzht3wvP0A"),
|
||||
album: None,
|
||||
view_count: Some(66000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -728,7 +728,7 @@ MusicCharts(
|
|||
artist_id: Some("UCPC0L1d253x-KuMNwa05TpA"),
|
||||
album: None,
|
||||
view_count: Some(68000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -757,7 +757,7 @@ MusicCharts(
|
|||
artist_id: Some("UCju-DqP7JNtCnMWFXhLgPHQ"),
|
||||
album: None,
|
||||
view_count: Some(46000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -790,7 +790,7 @@ MusicCharts(
|
|||
artist_id: Some("UC5IkSn-EFsUu3XANYklXc8g"),
|
||||
album: None,
|
||||
view_count: Some(43000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -823,7 +823,7 @@ MusicCharts(
|
|||
artist_id: Some("UCoC_a7lWbj2v7rt4ujp4n2A"),
|
||||
album: None,
|
||||
view_count: Some(7200000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -852,7 +852,7 @@ MusicCharts(
|
|||
artist_id: Some("UCvUZUUxWhwtKLVQ9bVRjLEA"),
|
||||
album: None,
|
||||
view_count: Some(4000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -881,7 +881,7 @@ MusicCharts(
|
|||
artist_id: Some("UCr_zAwkma5JAyHOWfVXaouA"),
|
||||
album: None,
|
||||
view_count: Some(2900000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -914,7 +914,7 @@ MusicCharts(
|
|||
artist_id: Some("UC_z9AthnCGSAk_tZf-KqoFA"),
|
||||
album: None,
|
||||
view_count: Some(10000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -943,7 +943,7 @@ MusicCharts(
|
|||
artist_id: Some("UCBabNBocAdKiN5sz8RBjIDg"),
|
||||
album: None,
|
||||
view_count: Some(15000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -972,7 +972,7 @@ MusicCharts(
|
|||
artist_id: Some("UC5xaQ6_dP7EGDmGLzVGZ1Ow"),
|
||||
album: None,
|
||||
view_count: Some(16000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1001,7 +1001,7 @@ MusicCharts(
|
|||
artist_id: Some("UCiXhCjTprNP0nuQJ9UsLWeg"),
|
||||
album: None,
|
||||
view_count: Some(21000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1030,7 +1030,7 @@ MusicCharts(
|
|||
artist_id: Some("UC_VCJd8skzwcPktsMLqTz1g"),
|
||||
album: None,
|
||||
view_count: Some(35000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1067,7 +1067,7 @@ MusicCharts(
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(30000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1096,7 +1096,7 @@ MusicCharts(
|
|||
artist_id: Some("UCq_Fb1zqNikdovyMJgRQjcw"),
|
||||
album: None,
|
||||
view_count: Some(18000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1125,7 +1125,7 @@ MusicCharts(
|
|||
artist_id: Some("UChWPNW87QHcXAsw2mzlsYNw"),
|
||||
album: None,
|
||||
view_count: Some(5400000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1154,7 +1154,7 @@ MusicCharts(
|
|||
artist_id: Some("UCiY3z8HAGD6BlSNKVn2kSvQ"),
|
||||
album: None,
|
||||
view_count: Some(312000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1183,7 +1183,7 @@ MusicCharts(
|
|||
artist_id: Some("UC0_1glf30IS53tFQWT8xpxw"),
|
||||
album: None,
|
||||
view_count: Some(28000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1212,7 +1212,7 @@ MusicCharts(
|
|||
artist_id: Some("UC_z9AthnCGSAk_tZf-KqoFA"),
|
||||
album: None,
|
||||
view_count: Some(97000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1238,7 +1238,7 @@ MusicCharts(
|
|||
artist_id: Some("UCGexNm_Kw4rdQjLxmpb2EKw"),
|
||||
album: None,
|
||||
view_count: Some(6000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1262,7 +1262,7 @@ MusicCharts(
|
|||
artist_id: Some("UCybEdRVR5u_WFoV-BLTEBiA"),
|
||||
album: None,
|
||||
view_count: Some(15000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1286,7 +1286,7 @@ MusicCharts(
|
|||
artist_id: Some("UCTP45_DE3fMLujU8sZ-MBzw"),
|
||||
album: None,
|
||||
view_count: Some(10000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1314,7 +1314,7 @@ MusicCharts(
|
|||
artist_id: Some("UC_duTRnaqtLLTCDIlqjRTcQ"),
|
||||
album: None,
|
||||
view_count: Some(3600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1338,7 +1338,7 @@ MusicCharts(
|
|||
artist_id: Some("UCPoQYATXIYvN5WB0c4f6jfQ"),
|
||||
album: None,
|
||||
view_count: Some(524000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1362,7 +1362,7 @@ MusicCharts(
|
|||
artist_id: Some("UCR28YDxjDE3ogQROaNdnRbQ"),
|
||||
album: None,
|
||||
view_count: Some(3800000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1386,7 +1386,7 @@ MusicCharts(
|
|||
artist_id: Some("UCpcTrCXblq78GZrTUTLWeBw"),
|
||||
album: None,
|
||||
view_count: Some(46000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1410,7 +1410,7 @@ MusicCharts(
|
|||
artist_id: Some("UCEf_Bc-KVd7onSeifS3py9g"),
|
||||
album: None,
|
||||
view_count: Some(8300000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1434,7 +1434,7 @@ MusicCharts(
|
|||
artist_id: Some("UCVcAt8IIKIeubRSigcYXgtA"),
|
||||
album: None,
|
||||
view_count: Some(13000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1458,7 +1458,7 @@ MusicCharts(
|
|||
artist_id: Some("UC0_1glf30IS53tFQWT8xpxw"),
|
||||
album: None,
|
||||
view_count: Some(365000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1482,7 +1482,7 @@ MusicCharts(
|
|||
artist_id: Some("UC1_liDR4fRFJgH4HoJeV8cw"),
|
||||
album: None,
|
||||
view_count: Some(754000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1506,7 +1506,7 @@ MusicCharts(
|
|||
artist_id: Some("UCGJdT8Qip4XObbQZ98Z1CAA"),
|
||||
album: None,
|
||||
view_count: Some(4900000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1530,7 +1530,7 @@ MusicCharts(
|
|||
artist_id: Some("UCr_zAwkma5JAyHOWfVXaouA"),
|
||||
album: None,
|
||||
view_count: Some(2900000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1554,7 +1554,7 @@ MusicCharts(
|
|||
artist_id: Some("UCvUZUUxWhwtKLVQ9bVRjLEA"),
|
||||
album: None,
|
||||
view_count: Some(4000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1582,7 +1582,7 @@ MusicCharts(
|
|||
artist_id: Some("UC7n3gWRN0vQzgiOKc51aZ4w"),
|
||||
album: None,
|
||||
view_count: Some(36000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1606,7 +1606,7 @@ MusicCharts(
|
|||
artist_id: Some("UCVcAt8IIKIeubRSigcYXgtA"),
|
||||
album: None,
|
||||
view_count: Some(2000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1630,7 +1630,7 @@ MusicCharts(
|
|||
artist_id: Some("UChWPNW87QHcXAsw2mzlsYNw"),
|
||||
album: None,
|
||||
view_count: Some(2600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1662,7 +1662,7 @@ MusicCharts(
|
|||
artist_id: Some("UC47k7qXysCBKeaYfc1zmkIA"),
|
||||
album: None,
|
||||
view_count: Some(3500000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1686,7 +1686,7 @@ MusicCharts(
|
|||
artist_id: Some("UCjfB7ooJY7C43vBAuuCub_A"),
|
||||
album: None,
|
||||
view_count: Some(367000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1710,7 +1710,7 @@ MusicCharts(
|
|||
artist_id: Some("UC5xaQ6_dP7EGDmGLzVGZ1Ow"),
|
||||
album: None,
|
||||
view_count: Some(1500000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2416,7 +2416,6 @@ MusicCharts(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PL4fGSI1pDJn4fmCoF1vKHLtivI0f9yHiF",
|
||||
|
@ -2436,7 +2435,6 @@ MusicCharts(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PL4fGSI1pDJn5O8siDeZuI_4hbk6JWtTX1",
|
||||
|
@ -2456,7 +2454,6 @@ MusicCharts(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PL4fGSI1pDJn4EBsWVeFpcSAVOFMfhyipg",
|
||||
|
@ -2476,7 +2473,6 @@ MusicCharts(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PL4fGSI1pDJn5LOptOQixqnzXNGjNXAgYY",
|
||||
|
@ -2496,7 +2492,6 @@ MusicCharts(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PL4fGSI1pDJn4w4wTTgOmP_S80PoCtbGrL",
|
||||
|
@ -2516,7 +2511,6 @@ MusicCharts(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PL4fGSI1pDJn7Wkr6Ll6ds1AhA42rT8uaU",
|
||||
|
@ -2536,7 +2530,6 @@ MusicCharts(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PL4fGSI1pDJn4rBU0RHnR6-b1_uE20CzRH",
|
||||
|
@ -2556,7 +2549,6 @@ MusicCharts(
|
|||
channel: None,
|
||||
track_count: None,
|
||||
from_ytm: true,
|
||||
is_podcast: false,
|
||||
),
|
||||
],
|
||||
top_playlist_id: Some("PL4fGSI1pDJn69On1f-8NAvX_CYlx7QyZc"),
|
||||
|
|
|
@ -29,7 +29,7 @@ MusicCharts(
|
|||
artist_id: Some("UCpcTrCXblq78GZrTUTLWeBw"),
|
||||
album: None,
|
||||
view_count: Some(46000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -62,7 +62,7 @@ MusicCharts(
|
|||
artist_id: Some("UC9vrvNSL3xcWGSkV86REBSg"),
|
||||
album: None,
|
||||
view_count: Some(46000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -91,7 +91,7 @@ MusicCharts(
|
|||
artist_id: Some("UCo6JijJGA3IvIiPsawDK3Ww"),
|
||||
album: None,
|
||||
view_count: Some(3300000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -124,7 +124,7 @@ MusicCharts(
|
|||
artist_id: Some("UCONiUl5u7y2bMaVZJcuRDEQ"),
|
||||
album: None,
|
||||
view_count: Some(38000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -157,7 +157,7 @@ MusicCharts(
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(57000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -186,7 +186,7 @@ MusicCharts(
|
|||
artist_id: Some("UCiY3z8HAGD6BlSNKVn2kSvQ"),
|
||||
album: None,
|
||||
view_count: Some(521000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -219,7 +219,7 @@ MusicCharts(
|
|||
artist_id: Some("UC5p07Pr3hlfjXo3YGVCyOgg"),
|
||||
album: None,
|
||||
view_count: Some(76000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -248,7 +248,7 @@ MusicCharts(
|
|||
artist_id: Some("UCfh2j2Dq-aSeLhzuPOsnhVg"),
|
||||
album: None,
|
||||
view_count: Some(276000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -281,7 +281,7 @@ MusicCharts(
|
|||
artist_id: Some("UCeBYRgPhy8kcRmIGQWKuqdQ"),
|
||||
album: None,
|
||||
view_count: Some(136000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -310,7 +310,7 @@ MusicCharts(
|
|||
artist_id: Some("UCiY3z8HAGD6BlSNKVn2kSvQ"),
|
||||
album: None,
|
||||
view_count: Some(559000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -339,7 +339,7 @@ MusicCharts(
|
|||
artist_id: Some("UCDxKh1gFWeYsqePvgVzmPoQ"),
|
||||
album: None,
|
||||
view_count: Some(331000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -368,7 +368,7 @@ MusicCharts(
|
|||
artist_id: Some("UCkbbMCA40i18i7UdjayMPAg"),
|
||||
album: None,
|
||||
view_count: Some(257000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -401,7 +401,7 @@ MusicCharts(
|
|||
artist_id: Some("UC7n3gWRN0vQzgiOKc51aZ4w"),
|
||||
album: None,
|
||||
view_count: Some(36000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -442,7 +442,7 @@ MusicCharts(
|
|||
artist_id: Some("UCKEFjh4JL-OyMI8z3h5Coaw"),
|
||||
album: None,
|
||||
view_count: Some(50000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -475,7 +475,7 @@ MusicCharts(
|
|||
artist_id: Some("UC7n3gWRN0vQzgiOKc51aZ4w"),
|
||||
album: None,
|
||||
view_count: Some(202000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -504,7 +504,7 @@ MusicCharts(
|
|||
artist_id: Some("UCKNGMXJHTiGFdZNSo_zs3fQ"),
|
||||
album: None,
|
||||
view_count: Some(103000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -533,7 +533,7 @@ MusicCharts(
|
|||
artist_id: Some("UCkbbMCA40i18i7UdjayMPAg"),
|
||||
album: None,
|
||||
view_count: Some(453000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -562,7 +562,7 @@ MusicCharts(
|
|||
artist_id: Some("UCUamzwxCTrUvpyAvAt4FEdg"),
|
||||
album: None,
|
||||
view_count: Some(44000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -591,7 +591,7 @@ MusicCharts(
|
|||
artist_id: Some("UCKEFjh4JL-OyMI8z3h5Coaw"),
|
||||
album: None,
|
||||
view_count: Some(81000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -620,7 +620,7 @@ MusicCharts(
|
|||
artist_id: Some("UCJa2FF4TUB13Mm0GurZAqog"),
|
||||
album: None,
|
||||
view_count: Some(73000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -649,7 +649,7 @@ MusicCharts(
|
|||
artist_id: Some("UC6uMb9hMAziN9HZoXfTBAlg"),
|
||||
album: None,
|
||||
view_count: Some(45000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -678,7 +678,7 @@ MusicCharts(
|
|||
artist_id: Some("UC7n3gWRN0vQzgiOKc51aZ4w"),
|
||||
album: None,
|
||||
view_count: Some(545000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -707,7 +707,7 @@ MusicCharts(
|
|||
artist_id: Some("UCJa2FF4TUB13Mm0GurZAqog"),
|
||||
album: None,
|
||||
view_count: Some(139000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -736,7 +736,7 @@ MusicCharts(
|
|||
artist_id: Some("UCeYz6rzUGhVwqxRM37FUo8w"),
|
||||
album: None,
|
||||
view_count: Some(197000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -769,7 +769,7 @@ MusicCharts(
|
|||
artist_id: Some("UCy6qn2oxmoXA4_gBA5Q7zPw"),
|
||||
album: None,
|
||||
view_count: Some(257000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -798,7 +798,7 @@ MusicCharts(
|
|||
artist_id: Some("UCybEdRVR5u_WFoV-BLTEBiA"),
|
||||
album: None,
|
||||
view_count: Some(15000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -827,7 +827,7 @@ MusicCharts(
|
|||
artist_id: Some("UCtGHTwNL20Y3fY9bumjHDOw"),
|
||||
album: None,
|
||||
view_count: Some(55000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -856,7 +856,7 @@ MusicCharts(
|
|||
artist_id: Some("UCWsDFcIhY2DBi3GB5uykGXA"),
|
||||
album: None,
|
||||
view_count: Some(34000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -889,7 +889,7 @@ MusicCharts(
|
|||
artist_id: Some("UCo6JijJGA3IvIiPsawDK3Ww"),
|
||||
album: None,
|
||||
view_count: Some(123000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -918,7 +918,7 @@ MusicCharts(
|
|||
artist_id: Some("UCc3e8O2V5_7OA300ursDyFQ"),
|
||||
album: None,
|
||||
view_count: Some(109000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -947,7 +947,7 @@ MusicCharts(
|
|||
artist_id: Some("UC3QmG1Jn9cE5fTMt14DLuZw"),
|
||||
album: None,
|
||||
view_count: Some(5700000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -976,7 +976,7 @@ MusicCharts(
|
|||
artist_id: Some("UC03jIQv4WXBSHdr1DlCLYDw"),
|
||||
album: None,
|
||||
view_count: Some(872000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1005,7 +1005,7 @@ MusicCharts(
|
|||
artist_id: Some("UCSzWQmDsKG37iKN2vw1G-2Q"),
|
||||
album: None,
|
||||
view_count: Some(7900000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1034,7 +1034,7 @@ MusicCharts(
|
|||
artist_id: Some("UCo6JijJGA3IvIiPsawDK3Ww"),
|
||||
album: None,
|
||||
view_count: Some(750000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1063,7 +1063,7 @@ MusicCharts(
|
|||
artist_id: Some("UCKRnq8aBOCanYlffje7HyvA"),
|
||||
album: None,
|
||||
view_count: Some(311000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1100,7 +1100,7 @@ MusicCharts(
|
|||
artist_id: Some("UCQK0swJm0ceapSOtRKIWr0g"),
|
||||
album: None,
|
||||
view_count: Some(37000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1133,7 +1133,7 @@ MusicCharts(
|
|||
artist_id: Some("UC7PL9aor5qNRhvhWWVXyOqA"),
|
||||
album: None,
|
||||
view_count: Some(377000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1166,7 +1166,7 @@ MusicCharts(
|
|||
artist_id: Some("UC2kPe8FB39lojsUDtyKcqOQ"),
|
||||
album: None,
|
||||
view_count: Some(486000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1195,7 +1195,7 @@ MusicCharts(
|
|||
artist_id: Some("UCrP3Rfz32MT-OH9MZh_N9kA"),
|
||||
album: None,
|
||||
view_count: Some(570000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1224,7 +1224,7 @@ MusicCharts(
|
|||
artist_id: Some("UC0QVToeCjC9-1u-teWToPsg"),
|
||||
album: None,
|
||||
view_count: Some(28000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
|
|
@ -33,7 +33,7 @@ TrackDetails(
|
|||
artist_id: Some("UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
album: None,
|
||||
view_count: Some(235000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
|
|
@ -51,7 +51,7 @@ TrackDetails(
|
|||
name: "INVU - The 3rd Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
|
|
@ -35,7 +35,7 @@ Paginator(
|
|||
artist_id: Some("UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
album: None,
|
||||
view_count: Some(250000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -69,7 +69,7 @@ Paginator(
|
|||
artist_id: Some("UC_4Y1QqJr60C5Z7-eQWy-mw"),
|
||||
album: None,
|
||||
view_count: Some(168000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -103,7 +103,7 @@ Paginator(
|
|||
artist_id: Some("UCAq0pFGa2w9SjxOq0ZxKVIw"),
|
||||
album: None,
|
||||
view_count: Some(464000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -137,7 +137,7 @@ Paginator(
|
|||
artist_id: Some("UCTP45_DE3fMLujU8sZ-MBzw"),
|
||||
album: None,
|
||||
view_count: Some(230000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -171,7 +171,7 @@ Paginator(
|
|||
artist_id: Some("UCkbbMCA40i18i7UdjayMPAg"),
|
||||
album: None,
|
||||
view_count: Some(422000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -205,7 +205,7 @@ Paginator(
|
|||
artist_id: Some("UCHmZYTfdTyVKQEJicLiXEOg"),
|
||||
album: None,
|
||||
view_count: Some(349000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -239,7 +239,7 @@ Paginator(
|
|||
artist_id: Some("UCuKdaTsJ9Jv94hVV_I9aRxQ"),
|
||||
album: None,
|
||||
view_count: Some(167000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -273,7 +273,7 @@ Paginator(
|
|||
artist_id: Some("UC-clMkTZa7k-FxmNgMjoCgQ"),
|
||||
album: None,
|
||||
view_count: Some(124000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -307,7 +307,7 @@ Paginator(
|
|||
artist_id: Some("UCEf_Bc-KVd7onSeifS3py9g"),
|
||||
album: None,
|
||||
view_count: Some(127000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -336,7 +336,7 @@ Paginator(
|
|||
artist_id: Some("UCqTaQGqjAI6fYkr84KZgZEg"),
|
||||
album: None,
|
||||
view_count: Some(239000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -370,7 +370,7 @@ Paginator(
|
|||
artist_id: Some("UCAKvDuIX3m1AUdPpDSqV_3w"),
|
||||
album: None,
|
||||
view_count: Some(140000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -404,7 +404,7 @@ Paginator(
|
|||
artist_id: Some("UC_Cx288SDUD9liYn7CiJLAA"),
|
||||
album: None,
|
||||
view_count: Some(90000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -438,7 +438,7 @@ Paginator(
|
|||
artist_id: Some("UCDDpqmryjNunitS05bv7-8w"),
|
||||
album: None,
|
||||
view_count: Some(137000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -472,7 +472,7 @@ Paginator(
|
|||
artist_id: Some("UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
album: None,
|
||||
view_count: Some(220000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -506,7 +506,7 @@ Paginator(
|
|||
artist_id: Some("UCwPKPUAWE8ah0lkOcvNh8_Q"),
|
||||
album: None,
|
||||
view_count: Some(258000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -540,7 +540,7 @@ Paginator(
|
|||
artist_id: Some("UCWT2ZfW7d8YI-HinHEVhyCA"),
|
||||
album: None,
|
||||
view_count: Some(181000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -574,7 +574,7 @@ Paginator(
|
|||
artist_id: Some("UCjqYTQjO-JG-8vLlt6-4iyQ"),
|
||||
album: None,
|
||||
view_count: Some(165000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -608,7 +608,7 @@ Paginator(
|
|||
artist_id: Some("UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
album: None,
|
||||
view_count: Some(108000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -642,7 +642,7 @@ Paginator(
|
|||
artist_id: Some("UCkbbMCA40i18i7UdjayMPAg"),
|
||||
album: None,
|
||||
view_count: Some(222000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -676,7 +676,7 @@ Paginator(
|
|||
artist_id: Some("UCEUX9tUYqTFfPQdAgVNsKTA"),
|
||||
album: None,
|
||||
view_count: Some(540000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -710,7 +710,7 @@ Paginator(
|
|||
artist_id: Some("UCG81UKNsFg9Perf0uPQOsQw"),
|
||||
album: None,
|
||||
view_count: Some(90000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -744,7 +744,7 @@ Paginator(
|
|||
artist_id: Some("UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
album: None,
|
||||
view_count: Some(90000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -778,7 +778,7 @@ Paginator(
|
|||
artist_id: Some("UCDdCbqagfKo_euzzCV9G2EQ"),
|
||||
album: None,
|
||||
view_count: Some(71000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -807,7 +807,7 @@ Paginator(
|
|||
artist_id: Some("UCTP45_DE3fMLujU8sZ-MBzw"),
|
||||
album: None,
|
||||
view_count: Some(208000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -841,7 +841,7 @@ Paginator(
|
|||
artist_id: Some("UCDnYJA3OXXhRKYPe3jzLGeQ"),
|
||||
album: None,
|
||||
view_count: Some(140000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
|
|
@ -53,7 +53,7 @@ Paginator(
|
|||
name: "LOVE DIVE (LOVE DIVE)",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -105,7 +105,7 @@ Paginator(
|
|||
name: "My Voice - The 1st Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -157,7 +157,7 @@ Paginator(
|
|||
name: "FOREVER 1 - The 7th Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -209,7 +209,7 @@ Paginator(
|
|||
name: "\'The ReVe Festival 2022 - Feel My Rhythm\'",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -261,7 +261,7 @@ Paginator(
|
|||
name: "NewJeans 1st EP \'New Jeans\'",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -313,7 +313,7 @@ Paginator(
|
|||
name: "IU 5th Album \'LILAC\' (IU 5th Album \'LILAC\')",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -365,7 +365,7 @@ Paginator(
|
|||
name: "2021 Winter SMTOWN : SMCU EXPRESS",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -417,7 +417,7 @@ Paginator(
|
|||
name: "Dear OHMYGIRL",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -469,7 +469,7 @@ Paginator(
|
|||
name: "I Love",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -521,7 +521,7 @@ Paginator(
|
|||
name: "BORN PINK",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -573,7 +573,7 @@ Paginator(
|
|||
name: "YOUNG-LUV.COM (YOUNG-LUV.COM)",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -625,7 +625,7 @@ Paginator(
|
|||
name: "ELEVEN (ELEVEN)",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -677,7 +677,7 @@ Paginator(
|
|||
name: "Weekend",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -729,7 +729,7 @@ Paginator(
|
|||
name: "Offset",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -785,7 +785,7 @@ Paginator(
|
|||
name: "Scared to Be Lonely",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -837,7 +837,7 @@ Paginator(
|
|||
name: "After LIKE",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -889,7 +889,7 @@ Paginator(
|
|||
name: "Purpose - The 2nd Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -941,7 +941,7 @@ Paginator(
|
|||
name: "Heart Burn (열이올라요 (Heart Burn))",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -993,7 +993,7 @@ Paginator(
|
|||
name: "Rollin\'",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1045,7 +1045,7 @@ Paginator(
|
|||
name: "The ReVe Festival: Finale",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1097,7 +1097,7 @@ Paginator(
|
|||
name: "Every letter I sent you.",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1149,7 +1149,7 @@ Paginator(
|
|||
name: "Stronger (Deluxe Version)",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1201,7 +1201,7 @@ Paginator(
|
|||
name: "FATE NUMBER FOR",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1253,7 +1253,7 @@ Paginator(
|
|||
name: "ANTIFRAGILE",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1305,7 +1305,7 @@ Paginator(
|
|||
name: "소녀시대 Girls\' Generation",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
|
|
@ -32,7 +32,7 @@ MusicRelated(
|
|||
name: "FOREVER 1 - The 7th Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -64,7 +64,7 @@ MusicRelated(
|
|||
name: "After LIKE",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -96,7 +96,7 @@ MusicRelated(
|
|||
name: "Windy",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -128,7 +128,7 @@ MusicRelated(
|
|||
name: "Girls - The 2nd Mini Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -160,7 +160,7 @@ MusicRelated(
|
|||
name: "Weekend",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -192,7 +192,7 @@ MusicRelated(
|
|||
name: "Hello",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -224,7 +224,7 @@ MusicRelated(
|
|||
name: "Girls - The 2nd Mini Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -256,7 +256,7 @@ MusicRelated(
|
|||
name: "IT\'z Different",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -288,7 +288,7 @@ MusicRelated(
|
|||
name: "LOVE DIVE (LOVE DIVE)",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -320,7 +320,7 @@ MusicRelated(
|
|||
name: "Vanilla",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -352,7 +352,7 @@ MusicRelated(
|
|||
name: "Savage - The 1st Mini Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -384,7 +384,7 @@ MusicRelated(
|
|||
name: "\'The ReVe Festival 2022 - Feel My Rhythm\'",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -416,7 +416,7 @@ MusicRelated(
|
|||
name: "I NEVER DIE",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -448,7 +448,7 @@ MusicRelated(
|
|||
name: "INVU - The 3rd Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -480,7 +480,7 @@ MusicRelated(
|
|||
name: "CHECKMATE",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -512,7 +512,7 @@ MusicRelated(
|
|||
name: "Girls - The 2nd Mini Album",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -544,7 +544,7 @@ MusicRelated(
|
|||
name: "Street Dance Girls Fighter (SGF) Special",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -576,7 +576,7 @@ MusicRelated(
|
|||
name: "Next Level",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -608,7 +608,7 @@ MusicRelated(
|
|||
name: "IT\'z ICY",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -640,7 +640,7 @@ MusicRelated(
|
|||
name: "1/6 (6분의1)",
|
||||
)),
|
||||
view_count: None,
|
||||
track_type: track,
|
||||
is_video: false,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -666,7 +666,7 @@ MusicRelated(
|
|||
artist_id: Some("UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
album: None,
|
||||
view_count: Some(35000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -690,7 +690,7 @@ MusicRelated(
|
|||
artist_id: Some("UCx5Dw_5guQcKu_lMGCh-IuQ"),
|
||||
album: None,
|
||||
view_count: Some(836000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -714,7 +714,7 @@ MusicRelated(
|
|||
artist_id: Some("UCrGYENbzwtva2X16bAPhTbA"),
|
||||
album: None,
|
||||
view_count: Some(1200000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -738,7 +738,7 @@ MusicRelated(
|
|||
artist_id: Some("UCC3bq4PHj5W5y47jdRjOCPA"),
|
||||
album: None,
|
||||
view_count: Some(987000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -766,7 +766,7 @@ MusicRelated(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
album_type: album,
|
||||
album_type: Album,
|
||||
year: Some(2022),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -792,7 +792,7 @@ MusicRelated(
|
|||
),
|
||||
],
|
||||
artist_id: Some("UCEdZAdnnKqbaHOlv8nM6OtA"),
|
||||
album_type: ep,
|
||||
album_type: Ep,
|
||||
year: Some(2021),
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -991,7 +991,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLXE743St3DmXcUceLu--0-1k2FP2EocOk",
|
||||
|
@ -1014,7 +1013,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLrppmyF0pfrfcoUjEygOB3sJpLk7envYZ",
|
||||
|
@ -1037,7 +1035,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLpwgyaUVRzlLwAwXFWUCtIQJgbMS2k5fG",
|
||||
|
@ -1060,7 +1057,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLPhP3bI_bdf1KY5-iN6trq-1XB4AQoZij",
|
||||
|
@ -1083,7 +1079,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLjVRwhW9AxIDrdwuZqGfC_gjmFNfDfXqm",
|
||||
|
@ -1106,7 +1101,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLhBJuM3nUmMEZSJaKFmjA7Y5z-PBzMO0o",
|
||||
|
@ -1129,7 +1123,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PL0Ne18oW010y_gRCR_57arzpFiP9gnVEi",
|
||||
|
@ -1152,7 +1145,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLSNAUEM08rvKpvgkWSThc7PP7R9GJ8WdJ",
|
||||
|
@ -1175,7 +1167,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
MusicPlaylistItem(
|
||||
id: "PLmOj3ylRt-xido1Feaf3O5HFXSKKeBuRR",
|
||||
|
@ -1198,7 +1189,6 @@ MusicRelated(
|
|||
)),
|
||||
track_count: None,
|
||||
from_ytm: false,
|
||||
is_podcast: false,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -28,7 +28,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCs6GGpd9zvsYghuYe0VDFUQ"),
|
||||
album: None,
|
||||
view_count: Some(8600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -57,7 +57,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCicJjripVxiTXbUfociVZwQ"),
|
||||
album: None,
|
||||
view_count: Some(244000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -86,7 +86,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCuO5r4J0jTrgRYnmfEOd3UQ"),
|
||||
album: None,
|
||||
view_count: Some(314000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -115,7 +115,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC4gi504gkSyoXt9vfaGUr9A"),
|
||||
album: None,
|
||||
view_count: Some(265000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -144,7 +144,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCsN8M73DMWa8SPp5o_0IAQQ"),
|
||||
album: None,
|
||||
view_count: Some(47000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -173,7 +173,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCc_iRXEpehN-dDTkPLoBjZg"),
|
||||
album: None,
|
||||
view_count: Some(225000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -210,7 +210,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCsPz48w0M3QUEGAiDP1x17w"),
|
||||
album: None,
|
||||
view_count: Some(124000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -239,7 +239,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCpcTrCXblq78GZrTUTLWeBw"),
|
||||
album: None,
|
||||
view_count: Some(34000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -272,7 +272,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCeCsfhcY09c-UvX7DkQDcUw"),
|
||||
album: None,
|
||||
view_count: Some(39000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -301,7 +301,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCa7FGSUsN2wNRUclibmicMg"),
|
||||
album: None,
|
||||
view_count: Some(400000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
|
|
@ -28,7 +28,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCl7jsog9v_M5SgdWeSEXpRA"),
|
||||
album: None,
|
||||
view_count: Some(428000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -57,7 +57,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCtilVkO8eHizeFSs5s5vvCA"),
|
||||
album: None,
|
||||
view_count: Some(377000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -86,7 +86,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC3i0iDG8jrpTvxgoeDoskbg"),
|
||||
album: None,
|
||||
view_count: Some(2200000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -115,7 +115,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCtNte05ndJf0BEN3hSmVEYQ"),
|
||||
album: None,
|
||||
view_count: Some(123000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -144,7 +144,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCLLh_wXspYOzRoOUWUCyu8A"),
|
||||
album: None,
|
||||
view_count: Some(1700000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -173,7 +173,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCR5Q898Ou0J9eR5gQFFbExw"),
|
||||
album: None,
|
||||
view_count: Some(1000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -202,7 +202,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCAravTV5JMlA34hSiFeLphQ"),
|
||||
album: None,
|
||||
view_count: Some(1100000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -231,7 +231,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCqfnM1n2W4KwqeLZjm70_5w"),
|
||||
album: None,
|
||||
view_count: Some(574000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -264,7 +264,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC2ssqTYToKpthovpsXR_V9A"),
|
||||
album: None,
|
||||
view_count: Some(531000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -293,7 +293,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC7eTn3T3sbweHz9TJJYxoqQ"),
|
||||
album: None,
|
||||
view_count: Some(888000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -322,7 +322,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCzt2dORSFzAiwRNUOpg1Gng"),
|
||||
album: None,
|
||||
view_count: Some(1100000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -351,7 +351,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCS-F4K78yXD6b53lypQXVmA"),
|
||||
album: None,
|
||||
view_count: Some(625000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -380,7 +380,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC4dqLAF7yT-_DqeYisQ001w"),
|
||||
album: None,
|
||||
view_count: Some(6400000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -409,7 +409,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCICfl8cwTSkZ_kUZ7tLnfBw"),
|
||||
album: None,
|
||||
view_count: Some(1700000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -438,7 +438,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCg-EvXRjoIqX6IjDB1F09XA"),
|
||||
album: None,
|
||||
view_count: Some(115000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -467,7 +467,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCb18v2rxFjfofBw68qhdJOg"),
|
||||
album: None,
|
||||
view_count: Some(609000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -496,7 +496,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCkBzk1A-RnBs_1OArpSeAFw"),
|
||||
album: None,
|
||||
view_count: Some(1600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -525,7 +525,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC8DMv_5z36X8mpS-VPATVKA"),
|
||||
album: None,
|
||||
view_count: Some(13000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -554,7 +554,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCBNTNHPngOpC-yeC8VpAPJQ"),
|
||||
album: None,
|
||||
view_count: Some(2000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -583,7 +583,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCX8lsVmQtu9m1l5n3KDniMw"),
|
||||
album: None,
|
||||
view_count: Some(1000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -612,7 +612,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC8RdjfDbLzA8CYRjsi6gvgw"),
|
||||
album: None,
|
||||
view_count: Some(865000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -641,7 +641,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCdKSviUrXNT9SjC8e-jEJQQ"),
|
||||
album: None,
|
||||
view_count: Some(14000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -670,7 +670,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCBI6li04V1aWOFrs9jjPomQ"),
|
||||
album: None,
|
||||
view_count: Some(1600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -699,7 +699,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCCQBpFm05uyBgLNDMFgoBYQ"),
|
||||
album: None,
|
||||
view_count: Some(133000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -728,7 +728,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCH37fMa51kuvqaHjQyPUbQw"),
|
||||
album: None,
|
||||
view_count: Some(2000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -757,7 +757,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCame-clC-0couNMsqr_U21w"),
|
||||
album: None,
|
||||
view_count: Some(1100000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -790,7 +790,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCJ2m-WpROlZCiZZID9r7NSQ"),
|
||||
album: None,
|
||||
view_count: Some(875000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -814,7 +814,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCUApRKc5Uc-rDP2QET6eieg"),
|
||||
album: None,
|
||||
view_count: Some(44000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -843,7 +843,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCwiGKkQcOlzMpXOc1xHFoeg"),
|
||||
album: None,
|
||||
view_count: Some(119000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -872,7 +872,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCSZUc2vHq3Z54Lcw7UgydYQ"),
|
||||
album: None,
|
||||
view_count: Some(298000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -901,7 +901,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCVS6hZxkuygm-EzXHxWKbJQ"),
|
||||
album: None,
|
||||
view_count: Some(2600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -930,7 +930,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCuxZXXFOL3K3kEFGiAnp-Kw"),
|
||||
album: None,
|
||||
view_count: Some(120000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -959,7 +959,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCtNte05ndJf0BEN3hSmVEYQ"),
|
||||
album: None,
|
||||
view_count: Some(100000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -988,7 +988,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC0wV8tkoYYHxuEXgcQEQ7zA"),
|
||||
album: None,
|
||||
view_count: Some(1000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1021,7 +1021,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCEvEiysS7crVEnNAXIx77tw"),
|
||||
album: None,
|
||||
view_count: Some(587000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1050,7 +1050,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCXHklVWUoqHEKrcaJ5JixKw"),
|
||||
album: None,
|
||||
view_count: Some(137000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1079,7 +1079,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCn30hmDp52npfCIHHgULM7A"),
|
||||
album: None,
|
||||
view_count: Some(154000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1108,7 +1108,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(1000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1137,7 +1137,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(913000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1166,7 +1166,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCoHMUugeU6PWB9ePTOV7WJw"),
|
||||
album: None,
|
||||
view_count: Some(1900000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1199,7 +1199,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCLCR-FbVVq1GsETOtdGG-mw"),
|
||||
album: None,
|
||||
view_count: Some(681000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1228,7 +1228,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(19000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1257,7 +1257,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCu3WJjXqlYozV9s8sGYPySA"),
|
||||
album: None,
|
||||
view_count: Some(1700000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1286,7 +1286,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCz51ZodJbYUNfkdPHOjJKKw"),
|
||||
album: None,
|
||||
view_count: Some(7000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1323,7 +1323,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCSRPntvq2xLXQtZwE2fA__w"),
|
||||
album: None,
|
||||
view_count: Some(43000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1352,7 +1352,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCjikjZ91JLx_e-2i9nrhhSg"),
|
||||
album: None,
|
||||
view_count: Some(21000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1381,7 +1381,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCIHQbC5CEmgPkBKO9gOXyBQ"),
|
||||
album: None,
|
||||
view_count: Some(138000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1410,7 +1410,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCC_ZUtrLVilXKVbOJB3wDVA"),
|
||||
album: None,
|
||||
view_count: Some(134000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1439,7 +1439,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCksBsDumncwulY5-MC6xWDA"),
|
||||
album: None,
|
||||
view_count: Some(612000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1468,7 +1468,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCrC-7fsdTCYeaRBpwA6j-Eg"),
|
||||
album: None,
|
||||
view_count: None,
|
||||
track_type: episode,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1497,7 +1497,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCYO-8CIkoBoUG2nOWz57Q9g"),
|
||||
album: None,
|
||||
view_count: Some(1700000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1530,7 +1530,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC5-_N4l38iyasppZ5MPILXg"),
|
||||
album: None,
|
||||
view_count: Some(988000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1559,7 +1559,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCLpm6-66iqUlOG28abww73w"),
|
||||
album: None,
|
||||
view_count: Some(454000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1588,7 +1588,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC1rqUUukLEcgGBMChrH3y_w"),
|
||||
album: None,
|
||||
view_count: Some(738000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1617,7 +1617,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCMXADr_MS_MdbOouS7ZMc8w"),
|
||||
album: None,
|
||||
view_count: Some(234000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1646,7 +1646,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCN3lpPbZIK3CgTWTtriwWMQ"),
|
||||
album: None,
|
||||
view_count: Some(113000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1675,7 +1675,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCiDD2aSYdgxSPE2YUm5lEjg"),
|
||||
album: None,
|
||||
view_count: Some(702000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1704,7 +1704,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCMg_DZ4fMJGCohPYnBTLtpA"),
|
||||
album: None,
|
||||
view_count: Some(72000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1733,7 +1733,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCn5SLnGaBcsusekOkSndmWg"),
|
||||
album: None,
|
||||
view_count: Some(265000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1762,7 +1762,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCiC_vxasVKRDBvZBaMIEGdg"),
|
||||
album: None,
|
||||
view_count: Some(49000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1791,7 +1791,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCCXCwvd49sm9x_gp1nuv7uA"),
|
||||
album: None,
|
||||
view_count: Some(15000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1820,7 +1820,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCPC1pc0VjTVizL2FferDukw"),
|
||||
album: None,
|
||||
view_count: Some(332000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1849,7 +1849,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCylwv7H2IUI_JWiA_2Mt5oA"),
|
||||
album: None,
|
||||
view_count: Some(177000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1873,7 +1873,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCBvTykFO8_qxF0VtPm0ZjmA"),
|
||||
album: None,
|
||||
view_count: Some(362000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1902,7 +1902,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(103000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1931,7 +1931,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC7aCpfjAUTxRWslugOpsjUg"),
|
||||
album: None,
|
||||
view_count: Some(92000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1960,7 +1960,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC2-fS2PfXjiYYOhpmnCfDIw"),
|
||||
album: None,
|
||||
view_count: None,
|
||||
track_type: episode,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -1989,7 +1989,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCzVb0SIXp9q9PeKCcFjsBtA"),
|
||||
album: None,
|
||||
view_count: Some(25000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2018,7 +2018,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCkFIRlbak2lK--nCQXau_6g"),
|
||||
album: None,
|
||||
view_count: Some(1200000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2047,7 +2047,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCOykHV9q0qb0vrBsxO_5fkQ"),
|
||||
album: None,
|
||||
view_count: Some(1100000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2076,7 +2076,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(2000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2105,7 +2105,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCofCirUtHXO7IL8AjWHytZQ"),
|
||||
album: None,
|
||||
view_count: Some(24000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2134,7 +2134,7 @@ expression: map_res.c
|
|||
artist_id: Some("UClWLQP-lNGvEV5qJpy7DLhw"),
|
||||
album: None,
|
||||
view_count: Some(669000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2163,7 +2163,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(590000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2196,7 +2196,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC97hKW1a1rzrMnzkKwaNx_g"),
|
||||
album: None,
|
||||
view_count: Some(2600000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2225,7 +2225,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCR28YDxjDE3ogQROaNdnRbQ"),
|
||||
album: None,
|
||||
view_count: Some(1300000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2254,7 +2254,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCqgwdYBhoUHmjrVxNhEUv4g"),
|
||||
album: None,
|
||||
view_count: Some(1400000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2291,7 +2291,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCrmhl-Xsb9LW5WYtC2iHe6A"),
|
||||
album: None,
|
||||
view_count: Some(12000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2324,7 +2324,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCzmabbKsmXlWnI9N2kKQ4lA"),
|
||||
album: None,
|
||||
view_count: Some(10000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2353,7 +2353,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC4yEk8HA1s1-OmMOJSCyg0A"),
|
||||
album: None,
|
||||
view_count: Some(488000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2382,7 +2382,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(6800000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2411,7 +2411,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCf4l_B9IhzstPp8elVeHnTQ"),
|
||||
album: None,
|
||||
view_count: Some(637000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2440,7 +2440,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCC0ydtpsVWZNHLvKSf4MnYw"),
|
||||
album: None,
|
||||
view_count: Some(3900000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2469,7 +2469,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC7a1tClTvIIttpI-r2PwiQA"),
|
||||
album: None,
|
||||
view_count: Some(1200000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2502,7 +2502,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCBvOwJ62CE_EdXLhrFceElg"),
|
||||
album: None,
|
||||
view_count: Some(272000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2531,7 +2531,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC3PFoXZPFJGy9F4PG84nNQQ"),
|
||||
album: None,
|
||||
view_count: Some(371000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2560,7 +2560,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(3200000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2593,7 +2593,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCQd9dydn5gaib_uuVVkYZTQ"),
|
||||
album: None,
|
||||
view_count: Some(42000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2622,7 +2622,7 @@ expression: map_res.c
|
|||
artist_id: Some("UC1snMKQQ0kl280XfQmg9tpQ"),
|
||||
album: None,
|
||||
view_count: Some(657000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2651,7 +2651,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCsIkF_bbTB9jjGIkT4j82rQ"),
|
||||
album: None,
|
||||
view_count: Some(49000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2680,7 +2680,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCrXRY_7SVVmc6TykwhRGUNQ"),
|
||||
album: None,
|
||||
view_count: Some(6900000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2709,7 +2709,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCof4hiuvv9BPhVCh90QHErw"),
|
||||
album: None,
|
||||
view_count: Some(489000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2738,7 +2738,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCtfmGsduggb9uGGsC9lhfKQ"),
|
||||
album: None,
|
||||
view_count: Some(931000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2767,7 +2767,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCdmScpl5nFGLy3xCnfHjDsw"),
|
||||
album: None,
|
||||
view_count: Some(2100000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2796,7 +2796,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCTfTV3COQIaxnvZPjNbXApw"),
|
||||
album: None,
|
||||
view_count: Some(476000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2825,7 +2825,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCHN9HRrB-BeOtSVKhVT7t_w"),
|
||||
album: None,
|
||||
view_count: Some(328000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2854,7 +2854,7 @@ expression: map_res.c
|
|||
artist_id: Some("UChZSUPxvkA2ATGLY9zZrxnQ"),
|
||||
album: None,
|
||||
view_count: Some(483000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2883,7 +2883,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCzj_b294hukUPPkbwgiMmLQ"),
|
||||
album: None,
|
||||
view_count: Some(1100000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2912,7 +2912,7 @@ expression: map_res.c
|
|||
artist_id: Some("UCoS1Y8yS22Z1r3wVmHllaLA"),
|
||||
album: None,
|
||||
view_count: Some(683000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
@ -2941,7 +2941,7 @@ expression: map_res.c
|
|||
artist_id: None,
|
||||
album: None,
|
||||
view_count: Some(11000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: None,
|
||||
by_va: false,
|
||||
),
|
||||
|
|
|
@ -40,7 +40,7 @@ MusicAlbum(
|
|||
],
|
||||
artist_id: Some("UCXGYZ-OhdOpPBamHX3K9YRg"),
|
||||
description: None,
|
||||
album_type: single,
|
||||
album_type: Single,
|
||||
year: Some(2020),
|
||||
by_va: false,
|
||||
tracks: [
|
||||
|
@ -65,7 +65,7 @@ MusicAlbum(
|
|||
name: "Der Himmel reißt auf",
|
||||
)),
|
||||
view_count: Some(12000000),
|
||||
track_type: video,
|
||||
is_video: true,
|
||||
track_nr: Some(1),
|
||||
by_va: false,
|
||||
),
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue