Compare commits

..

6 commits

250 changed files with 17216 additions and 270536 deletions

View file

@ -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' }}

View file

@ -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/*

View file

@ -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
View file

@ -4,5 +4,4 @@
*.snap.new
rustypipe_reports
rustypipe_cache*.json
bg_snapshot.bin
rustypipe_cache.json

View file

@ -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
View file

@ -0,0 +1,3 @@
{
"rust-analyzer.cargo.features": ["rss", "indicatif", "audiotag"]
}

10
.woodpecker.yml Normal file
View 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

View file

@ -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

View file

@ -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"]

View file

@ -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`)

View file

@ -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
View file

@ -1,8 +1,7 @@
# ![RustyPipe](https://codeberg.org/ThetaDev/rustypipe/raw/branch/main/notes/logo.svg)
[![Current crates.io version](https://img.shields.io/crates/v/rustypipe.svg)](https://crates.io/crates/rustypipe)
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](https://opensource.org/licenses/GPL-3.0)
[![Docs](https://img.shields.io/docsrs/rustypipe/latest?style=flat)](https://docs.rs/rustypipe)
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](http://opensource.org/licenses/GPL-3.0)
[![CI status](https://codeberg.org/ThetaDev/rustypipe/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](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`)

View file

@ -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

View file

@ -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"

View file

@ -1,26 +1,14 @@
# ![RustyPipe](https://codeberg.org/ThetaDev/rustypipe/raw/branch/main/notes/logo.svg) CLI
[![Current crates.io version](https://img.shields.io/crates/v/rustypipe-cli.svg)](https://crates.io/crates/rustypipe-cli)
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](https://opensource.org/licenses/GPL-3.0)
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](http://opensource.org/licenses/GPL-3.0)
[![CI status](https://codeberg.org/ThetaDev/rustypipe/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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\""))
}

View file

@ -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,
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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::{

View file

@ -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},

View file

@ -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},

View file

@ -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();
}

View file

@ -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";

View file

@ -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(),

View file

@ -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,
}

View file

@ -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(' ')

View file

@ -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

View file

@ -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

View file

@ -1,8 +1,7 @@
# ![RustyPipe](https://codeberg.org/ThetaDev/rustypipe/raw/branch/main/notes/logo.svg) Downloader
[![Current crates.io version](https://img.shields.io/crates/v/rustypipe-downloader.svg)](https://crates.io/crates/rustypipe-downloader)
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](https://opensource.org/licenses/GPL-3.0)
[![Docs](https://img.shields.io/docsrs/rustypipe-downloader/latest?style=flat)](https://docs.rs/rustypipe-downloader)
[![License](https://img.shields.io/badge/License-GPL--3-blue.svg?style=flat)](http://opensource.org/licenses/GPL-3.0)
[![CI status](https://codeberg.org/ThetaDev/rustypipe/actions/workflows/ci.yaml/badge.svg?style=flat&label=CI)](https://codeberg.org/ThetaDev/rustypipe/actions/?workflow=ci.yaml)
The downloader is a companion crate for RustyPipe that allows for easy and fast

View file

@ -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),

View file

@ -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
.dl
.i
.http
.get(thumbnail.url)
.send()
.await?
.error_for_status()?,
};
let resp = self
.dl
.i
.http
.get(thumbnail.url)
.send()
.await?
.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 {
download_single_file(
&d.url,
&d.file,
http,
user_agent,
#[cfg(feature = "indicatif")]
pb,
)
.await
}
})
.await?;
) -> Result<()> {
let n = downloads.len();
Ok(downloads)
stream::iter(downloads)
.map(|d| {
download_single_file(
&d.url,
&d.file,
http,
user_agent,
pot,
#[cfg(feature = "indicatif")]
pb.clone(),
)
})
.buffer_unordered(n)
.collect::<Vec<_>>()
.await
.into_iter()
.collect::<Result<Vec<_>>>()?;
Ok(())
}
async fn convert_streams(

View file

@ -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());
}

View file

@ -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.

View file

@ -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

View file

@ -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",

View file

@ -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!(

View file

@ -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]

View file

@ -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()),

File diff suppressed because it is too large Load diff

View file

@ -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();

View file

@ -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 {

View file

@ -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,
})
}

View file

@ -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,16 +180,14 @@ impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
let mut mapper = MusicListMapper::new(ctx.lang);
mapper.map_response(shelf.contents);
let ctoken = mapper.ctoken.clone().or_else(|| {
shelf
.continuations
.into_iter()
.next()
.map(|cont| cont.next_continuation_data.continuation)
});
let map_res = mapper.conv_items();
let ctoken = shelf
.continuations
.into_iter()
.next()
.map(|cont| cont.next_continuation_data.continuation);
let track_count = if ctoken.is_some() {
header.as_ref().and_then(|h| {
let parts = h
@ -217,39 +213,14 @@ 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,
};
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)
}
let st = match h.strapline_text_one {
Some(s) => s,
None => h.subtitle,
};
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,
@ -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();

View file

@ -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,
},

View file

@ -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]",
});
}
}

View file

@ -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,51 +90,37 @@ 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
.on_response_received_actions
.and_then(|actions| {
actions
.into_iter()
.map(|action| action.append_continuation_items_action.continuation_items)
.reduce(|mut acc, mut items| {
acc.c.append(&mut items.c);
acc.warnings.append(&mut items.warnings);
acc
})
})
.or_else(|| {
response
.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);
let items = self
.on_response_received_actions
.and_then(|actions| {
actions
.into_iter()
.map(|action| action.append_continuation_items_action.continuation_items)
.reduce(|mut acc, mut items| {
acc.c.append(&mut items.c);
acc.warnings.append(&mut items.warnings);
acc
})
})
.or_else(|| {
self.continuation_contents
.map(|contents| contents.rich_grid_continuation.contents)
})
.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())
.await?,
)
}
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();

View file

@ -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,91 +73,48 @@ 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) {
tracing::info!("{e}; fetching player with login");
if e.use_login() && self.auth_enabled() {
tracing::info!("{e}; fetching player with login");
match self
.clone()
.authenticated()
.player_from_client(video_id, c)
.await
{
Ok(res) => return Ok(res),
Err(Error::Extraction(e)) => {
if !e.switch_client() {
return Err(Error::Extraction(e));
}
match self
.clone()
.authenticated()
.player_from_client(video_id, *client)
.await
{
Ok(res) => return Ok(res),
Err(Error::Extraction(e)) => {
if !e.switch_client() {
return Err(Error::Extraction(e));
}
Err(e) => return Err(e),
}
} else {
return Err(Error::Extraction(e));
Err(e) => return Err(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 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}"),
},
}),
cpn: None,
video_id,
content_check_ok: true,
racy_check_ok: true,
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),
}
)?;
let playback_context = deobf.as_ref().map(|deobf| 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,
video_id,
content_check_ok: true,
racy_check_ok: true,
service_integrity_dimensions: player_po.content_po_token,
};
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()

View file

@ -203,13 +203,8 @@ 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,
)
.map(OffsetDateTime::date)
timeago::parse_textual_date_or_warn(ctx.lang, txt, &mut mapper.warnings)
.map(OffsetDateTime::date)
});
Ok(MapResult {
@ -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(),

View file

@ -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()
}
}

View file

@ -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>>,
}

View file

@ -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 {}

View file

@ -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,
}

View file

@ -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)]

View file

@ -1,8 +0,0 @@
use serde::Deserialize;
use super::music_playlist::Contents;
#[derive(Debug, Deserialize)]
pub(crate) struct MusicHistory {
pub contents: Contents,
}

View file

@ -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}");
}
}
}

View file

@ -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 {

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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,
}
}
}

View file

@ -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,
}

View file

@ -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
.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
.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(),
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));
}

View file

@ -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

View file

@ -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]",

View file

@ -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),

View file

@ -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,
),
)

View file

@ -25,7 +25,7 @@ Channel(
height: 176,
),
],
verification: verified,
verification: Verified,
description: "Hi, Im 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 its because youre 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]",

View file

@ -25,7 +25,7 @@ Channel(
height: 160,
),
],
verification: verified,
verification: Verified,
description: "Hi, Im 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 its because youre 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]",

View file

@ -25,7 +25,7 @@ Channel(
height: 160,
),
],
verification: verified,
verification: Verified,
description: "Hi, Im 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 its because youre 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]",

View file

@ -25,7 +25,7 @@ Channel(
height: 176,
),
],
verification: verified,
verification: Verified,
description: "Hi, Im 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 its because youre 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]",

View file

@ -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]",

View file

@ -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]",

View file

@ -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]",

View file

@ -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]",

View file

@ -25,7 +25,7 @@ Channel(
height: 176,
),
],
verification: none,
verification: None,
description: "",
tags: [],
banner: [],

View file

@ -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]",

View file

@ -25,7 +25,7 @@ Channel(
height: 176,
),
],
verification: none,
verification: None,
description: "",
tags: [],
banner: [

View file

@ -25,7 +25,7 @@ Channel(
height: 176,
),
],
verification: verified,
verification: Verified,
description: "Hi, Im 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 its because youre 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]",

View file

@ -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]",

View file

@ -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"),
)

View file

@ -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: [],

View file

@ -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: [

View file

@ -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: [

View file

@ -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,
),

View file

@ -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"),

View file

@ -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,
),

View file

@ -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,
),

View file

@ -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,
),

View file

@ -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,
),

View file

@ -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,
),

View file

@ -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,
),
],
)

View file

@ -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,
),

View file

@ -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,
),

View file

@ -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