feat!: add userdata feature for all personal data queries (playback history, subscriptions)
This commit is contained in:
parent
c87bac1856
commit
65cb4244c6
31 changed files with 189 additions and 143 deletions
|
|
@ -35,10 +35,15 @@ jobs:
|
||||||
rustypipe-botguard --version
|
rustypipe-botguard --version
|
||||||
|
|
||||||
- name: 📎 Clippy
|
- name: 📎 Clippy
|
||||||
run: cargo clippy --all --tests --features=rss,indicatif,audiotag -- -D warnings
|
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
|
||||||
|
|
||||||
- name: 🧪 Test
|
- name: 🧪 Test
|
||||||
run: cargo nextest run --config-file ~/.config/nextest.toml --profile ci --retries 2 --features rss --workspace -- --skip 'cookie_auth::'
|
run: cargo nextest run --config-file ~/.config/nextest.toml --profile ci --retries 2 --features rss,userdata --workspace -- --skip 'user_data::'
|
||||||
env:
|
env:
|
||||||
ALL_PROXY: "http://warpproxy:8124"
|
ALL_PROXY: "http://warpproxy:8124"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,8 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: cargo-fmt
|
- id: cargo-fmt
|
||||||
- id: cargo-clippy
|
- id: cargo-clippy
|
||||||
args: ["--all", "--tests", "--features=rss,indicatif,audiotag", "--", "-D", "warnings"]
|
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"]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -84,6 +84,7 @@ rustypipe-downloader = { path = "./downloader", version = "0.2.1", default-featu
|
||||||
default = ["default-tls"]
|
default = ["default-tls"]
|
||||||
|
|
||||||
rss = ["dep:quick-xml"]
|
rss = ["dep:quick-xml"]
|
||||||
|
userdata = []
|
||||||
|
|
||||||
# Reqwest TLS options
|
# Reqwest TLS options
|
||||||
default-tls = ["reqwest/default-tls"]
|
default-tls = ["reqwest/default-tls"]
|
||||||
|
|
@ -126,6 +127,6 @@ tracing-test.workspace = true
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
# To build locally:
|
# To build locally:
|
||||||
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features rss --no-deps --open
|
# RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --features rss,userdata --no-deps --open
|
||||||
features = ["rss"]
|
features = ["rss", "userdata"]
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
|
||||||
16
Justfile
16
Justfile
|
|
@ -1,19 +1,19 @@
|
||||||
test:
|
test:
|
||||||
# cargo test --features=rss
|
# cargo test --features=rss,userdata
|
||||||
cargo nextest run --workspace --features=rss --no-fail-fast --retries 1 -- --skip 'cookie_auth::'
|
cargo nextest run --workspace --features=rss,userdata --no-fail-fast --retries 1 -- --skip 'user_data::'
|
||||||
|
|
||||||
unittest:
|
unittest:
|
||||||
cargo nextest run --features=rss --no-fail-fast --lib
|
cargo nextest run --features=rss,userdata --no-fail-fast --lib
|
||||||
|
|
||||||
testyt:
|
testyt:
|
||||||
cargo nextest run --features=rss --no-fail-fast --retries 1 --test youtube -- --skip 'cookie_auth::'
|
cargo nextest run --features=rss,userdata --no-fail-fast --retries 1 --test youtube -- --skip 'user_data::'
|
||||||
|
|
||||||
testyt-cookie:
|
testyt-cookie:
|
||||||
cargo nextest run --features=rss --no-fail-fast --retries 1 --test youtube
|
cargo nextest run --features=rss,userdata --no-fail-fast --retries 1 --test youtube
|
||||||
|
|
||||||
testyt-localized:
|
testyt-localized:
|
||||||
YT_LANG=th cargo nextest run --features=rss --no-fail-fast --retries 1 --test youtube -- \
|
YT_LANG=th cargo nextest run --features=rss,userdata --no-fail-fast --retries 1 --test youtube -- \
|
||||||
--skip 'cookie_auth::' --skip 'search_suggestion' --skip 'isrc_search_languages'
|
--skip 'user_data::' --skip 'search_suggestion' --skip 'isrc_search_languages'
|
||||||
|
|
||||||
testintl:
|
testintl:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
@ -33,7 +33,7 @@ testintl:
|
||||||
echo "---TESTS FOR $YT_LANG ---"
|
echo "---TESTS FOR $YT_LANG ---"
|
||||||
|
|
||||||
if YT_LANG="$YT_LANG" cargo nextest run --no-fail-fast --retries 1 --test-threads 4 --test youtube -- \
|
if YT_LANG="$YT_LANG" cargo nextest run --no-fail-fast --retries 1 --test-threads 4 --test youtube -- \
|
||||||
--skip 'cookie_auth::' --skip 'search_suggestion' --skip 'isrc_search_languages' --skip 'resolve_'; then
|
--skip 'user_data::' --skip 'search_suggestion' --skip 'isrc_search_languages' --skip 'resolve_'; then
|
||||||
echo "--- $YT_LANG COMPLETED ---"
|
echo "--- $YT_LANG COMPLETED ---"
|
||||||
else
|
else
|
||||||
echo "--- $YT_LANG FAILED ---"
|
echo "--- $YT_LANG FAILED ---"
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ rustls-tls-native-roots = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustypipe = { workspace = true, features = ["rss"] }
|
rustypipe = { workspace = true, features = ["rss", "userdata"] }
|
||||||
rustypipe-downloader.workspace = true
|
rustypipe-downloader.workspace = true
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ repository.workspace = true
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rustypipe = { path = "../" }
|
rustypipe = { path = "../", features = ["userdata"] }
|
||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||||
futures-util.workspace = true
|
futures-util.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,6 @@ pub async fn download_testfiles() {
|
||||||
search_playlists().await;
|
search_playlists().await;
|
||||||
search_empty().await;
|
search_empty().await;
|
||||||
trending().await;
|
trending().await;
|
||||||
history().await;
|
|
||||||
subscriptions().await;
|
|
||||||
subscription_feed().await;
|
|
||||||
|
|
||||||
music_playlist().await;
|
music_playlist().await;
|
||||||
music_playlist_cont().await;
|
music_playlist_cont().await;
|
||||||
|
|
@ -65,6 +62,12 @@ pub async fn download_testfiles() {
|
||||||
music_charts().await;
|
music_charts().await;
|
||||||
music_genres().await;
|
music_genres().await;
|
||||||
music_genre().await;
|
music_genre().await;
|
||||||
|
|
||||||
|
// User data
|
||||||
|
history().await;
|
||||||
|
subscriptions().await;
|
||||||
|
subscription_feed().await;
|
||||||
|
|
||||||
music_history().await;
|
music_history().await;
|
||||||
music_saved_artists().await;
|
music_saved_artists().await;
|
||||||
music_saved_albums().await;
|
music_saved_albums().await;
|
||||||
|
|
@ -464,7 +467,7 @@ async fn trending() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn history() {
|
async fn history() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "history" / "history.json");
|
let json_path = path!(*TESTFILES_DIR / "userdata" / "history.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -474,7 +477,7 @@ async fn history() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn subscriptions() {
|
async fn subscriptions() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "history" / "subscriptions.json");
|
let json_path = path!(*TESTFILES_DIR / "userdata" / "subscriptions.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -484,7 +487,7 @@ async fn subscriptions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn subscription_feed() {
|
async fn subscription_feed() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "history" / "subscription_feed.json");
|
let json_path = path!(*TESTFILES_DIR / "userdata" / "subscription_feed.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -816,7 +819,7 @@ async fn music_genre() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn music_history() {
|
async fn music_history() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "music_history" / "music_history.json");
|
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "music_history.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -826,7 +829,7 @@ async fn music_history() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn music_saved_artists() {
|
async fn music_saved_artists() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "music_history" / "saved_artists.json");
|
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "saved_artists.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -836,7 +839,7 @@ async fn music_saved_artists() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn music_saved_albums() {
|
async fn music_saved_albums() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "music_history" / "saved_albums.json");
|
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "saved_albums.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -846,7 +849,7 @@ async fn music_saved_albums() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn music_saved_tracks() {
|
async fn music_saved_tracks() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "music_history" / "saved_tracks.json");
|
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "saved_tracks.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -856,7 +859,7 @@ async fn music_saved_tracks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn music_saved_playlists() {
|
async fn music_saved_playlists() {
|
||||||
let json_path = path!(*TESTFILES_DIR / "music_history" / "saved_playlists.json");
|
let json_path = path!(*TESTFILES_DIR / "music_userdata" / "saved_playlists.json");
|
||||||
if json_path.exists() {
|
if json_path.exists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,10 @@
|
||||||
pub(crate) mod response;
|
pub(crate) mod response;
|
||||||
|
|
||||||
mod channel;
|
mod channel;
|
||||||
mod history;
|
|
||||||
mod music_artist;
|
mod music_artist;
|
||||||
mod music_charts;
|
mod music_charts;
|
||||||
mod music_details;
|
mod music_details;
|
||||||
mod music_genres;
|
mod music_genres;
|
||||||
mod music_history;
|
|
||||||
mod music_new;
|
mod music_new;
|
||||||
mod music_playlist;
|
mod music_playlist;
|
||||||
mod music_search;
|
mod music_search;
|
||||||
|
|
@ -20,6 +18,13 @@ mod trends;
|
||||||
mod url_resolver;
|
mod url_resolver;
|
||||||
mod video_details;
|
mod video_details;
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||||
|
mod music_userdata;
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||||
|
mod userdata;
|
||||||
|
|
||||||
#[cfg(feature = "rss")]
|
#[cfg(feature = "rss")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "rss")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rss")))]
|
||||||
mod channel_rss;
|
mod channel_rss;
|
||||||
|
|
|
||||||
|
|
@ -122,20 +122,6 @@ impl RustyPipeQuery {
|
||||||
}
|
}
|
||||||
Ok(album)
|
Ok(album)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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(util::map_internal_playlist_err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
impl MapResponse<MusicPlaylist> for response::MusicPlaylist {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
error::{Error, ExtractionError},
|
error::{Error, ExtractionError},
|
||||||
model::{
|
model::{
|
||||||
paginator::{ContinuationEndpoint, Paginator},
|
paginator::{ContinuationEndpoint, Paginator},
|
||||||
AlbumItem, ArtistItem, HistoryItem, MusicPlaylistItem, TrackItem,
|
AlbumItem, ArtistItem, HistoryItem, MusicPlaylist, MusicPlaylistItem, TrackItem,
|
||||||
},
|
},
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
};
|
};
|
||||||
|
|
@ -127,6 +127,20 @@ impl RustyPipeQuery {
|
||||||
)
|
)
|
||||||
.await
|
.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 {
|
impl MapResponse<Paginator<HistoryItem<TrackItem>>> for response::MusicHistory {
|
||||||
|
|
@ -195,7 +209,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_history() {
|
fn map_history() {
|
||||||
let json_path = path!(*TESTFILES / "music_history" / "music_history.json");
|
let json_path = path!(*TESTFILES / "music_userdata" / "music_history.json");
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
||||||
let history: response::MusicHistory =
|
let history: response::MusicHistory =
|
||||||
|
|
@ -6,12 +6,15 @@ use crate::model::{
|
||||||
traits::FromYtItem,
|
traits::FromYtItem,
|
||||||
Comment, MusicItem, YouTubeItem,
|
Comment, MusicItem, YouTubeItem,
|
||||||
};
|
};
|
||||||
use crate::model::{HistoryItem, TrackItem, VideoItem};
|
|
||||||
use crate::serializer::MapResult;
|
use crate::serializer::MapResult;
|
||||||
|
|
||||||
use self::response::YouTubeListItem;
|
#[cfg(feature = "userdata")]
|
||||||
|
use crate::model::{HistoryItem, TrackItem, VideoItem};
|
||||||
|
|
||||||
use super::response::music_item::{map_queue_item, MusicListMapper, PlaylistPanelVideo};
|
use super::response::{
|
||||||
|
music_item::{map_queue_item, MusicListMapper, PlaylistPanelVideo},
|
||||||
|
YouTubeListItem,
|
||||||
|
};
|
||||||
use super::{
|
use super::{
|
||||||
response, ClientType, MapRespCtx, MapRespOptions, MapResponse, QContinuation, RustyPipeQuery,
|
response, ClientType, MapRespCtx, MapRespOptions, MapResponse, QContinuation, RustyPipeQuery,
|
||||||
};
|
};
|
||||||
|
|
@ -225,6 +228,7 @@ impl MapResponse<Paginator<MusicItem>> for response::MusicContinuation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
impl MapResponse<Paginator<HistoryItem<VideoItem>>> for response::Continuation {
|
impl MapResponse<Paginator<HistoryItem<VideoItem>>> for response::Continuation {
|
||||||
fn map_response(
|
fn map_response(
|
||||||
self,
|
self,
|
||||||
|
|
@ -270,6 +274,7 @@ impl MapResponse<Paginator<HistoryItem<VideoItem>>> for response::Continuation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
impl MapResponse<Paginator<HistoryItem<TrackItem>>> for response::MusicContinuation {
|
impl MapResponse<Paginator<HistoryItem<TrackItem>>> for response::MusicContinuation {
|
||||||
fn map_response(
|
fn map_response(
|
||||||
self,
|
self,
|
||||||
|
|
@ -422,6 +427,8 @@ impl Paginator<Comment> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||||
impl Paginator<HistoryItem<VideoItem>> {
|
impl Paginator<HistoryItem<VideoItem>> {
|
||||||
/// Get the next page from the paginator (or `None` if the paginator is exhausted)
|
/// 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> {
|
pub async fn next<Q: AsRef<RustyPipeQuery>>(&self, query: Q) -> Result<Option<Self>, Error> {
|
||||||
|
|
@ -437,6 +444,8 @@ impl Paginator<HistoryItem<VideoItem>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||||
impl Paginator<HistoryItem<TrackItem>> {
|
impl Paginator<HistoryItem<TrackItem>> {
|
||||||
/// Get the next page from the paginator (or `None` if the paginator is exhausted)
|
/// 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> {
|
pub async fn next<Q: AsRef<RustyPipeQuery>>(&self, query: Q) -> Result<Option<Self>, Error> {
|
||||||
|
|
@ -533,7 +542,11 @@ macro_rules! paginator {
|
||||||
}
|
}
|
||||||
|
|
||||||
paginator!(Comment);
|
paginator!(Comment);
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||||
paginator!(HistoryItem<VideoItem>);
|
paginator!(HistoryItem<VideoItem>);
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "userdata")))]
|
||||||
paginator!(HistoryItem<TrackItem>);
|
paginator!(HistoryItem<TrackItem>);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
@ -620,7 +633,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::subscriptions("subscriptions", path!("history" / "subscriptions.json"))]
|
#[case::subscriptions("subscriptions", path!("userdata" / "subscriptions.json"))]
|
||||||
fn map_continuation_channels(#[case] name: &str, #[case] path: PathBuf) {
|
fn map_continuation_channels(#[case] name: &str, #[case] path: PathBuf) {
|
||||||
let json_path = path!(*TESTFILES / path);
|
let json_path = path!(*TESTFILES / path);
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
@ -644,7 +657,7 @@ mod tests {
|
||||||
#[case::playlist_tracks("playlist_tracks", path!("music_playlist" / "playlist_cont.json"))]
|
#[case::playlist_tracks("playlist_tracks", path!("music_playlist" / "playlist_cont.json"))]
|
||||||
#[case::search_tracks("search_tracks", path!("music_search" / "tracks_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::radio_tracks("radio_tracks", path!("music_details" / "radio_cont.json"))]
|
||||||
#[case::saved_tracks("saved_tracks", path!("music_history" / "saved_tracks.json"))]
|
#[case::saved_tracks("saved_tracks", path!("music_userdata" / "saved_tracks.json"))]
|
||||||
fn map_continuation_tracks(#[case] name: &str, #[case] path: PathBuf) {
|
fn map_continuation_tracks(#[case] name: &str, #[case] path: PathBuf) {
|
||||||
let json_path = path!(*TESTFILES / path);
|
let json_path = path!(*TESTFILES / path);
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
@ -665,7 +678,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::saved_artists("saved_artists", path!("music_history" / "saved_artists.json"))]
|
#[case::saved_artists("saved_artists", path!("music_userdata" / "saved_artists.json"))]
|
||||||
fn map_continuation_artists(#[case] name: &str, #[case] path: PathBuf) {
|
fn map_continuation_artists(#[case] name: &str, #[case] path: PathBuf) {
|
||||||
let json_path = path!(*TESTFILES / path);
|
let json_path = path!(*TESTFILES / path);
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
@ -686,7 +699,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::saved_albums("saved_albums", path!("music_history" / "saved_albums.json"))]
|
#[case::saved_albums("saved_albums", path!("music_userdata" / "saved_albums.json"))]
|
||||||
fn map_continuation_albums(#[case] name: &str, #[case] path: PathBuf) {
|
fn map_continuation_albums(#[case] name: &str, #[case] path: PathBuf) {
|
||||||
let json_path = path!(*TESTFILES / path);
|
let json_path = path!(*TESTFILES / path);
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
@ -708,7 +721,7 @@ mod tests {
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::playlist_related("playlist_related", path!("music_playlist" / "playlist_related.json"))]
|
#[case::playlist_related("playlist_related", path!("music_playlist" / "playlist_related.json"))]
|
||||||
#[case::saved_playlists("saved_playlists", path!("music_history" / "saved_playlists.json"))]
|
#[case::saved_playlists("saved_playlists", path!("music_userdata" / "saved_playlists.json"))]
|
||||||
fn map_continuation_music_playlists(#[case] name: &str, #[case] path: PathBuf) {
|
fn map_continuation_music_playlists(#[case] name: &str, #[case] path: PathBuf) {
|
||||||
let json_path = path!(*TESTFILES / path);
|
let json_path = path!(*TESTFILES / path);
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -33,28 +33,6 @@ impl RustyPipeQuery {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get all liked videos of the logged-in user
|
|
||||||
///
|
|
||||||
/// Requires authentication cookies.
|
|
||||||
pub async fn liked_videos(&self) -> Result<Playlist, Error> {
|
|
||||||
self.clone()
|
|
||||||
.authenticated()
|
|
||||||
.playlist("LL")
|
|
||||||
.await
|
|
||||||
.map_err(util::map_internal_playlist_err)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the "Watch later" playlist of the logged-in user
|
|
||||||
///
|
|
||||||
/// Requires authentication cookies.
|
|
||||||
pub async fn watch_later(&self) -> Result<Playlist, Error> {
|
|
||||||
self.clone()
|
|
||||||
.authenticated()
|
|
||||||
.playlist("WL")
|
|
||||||
.await
|
|
||||||
.map_err(util::map_internal_playlist_err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapResponse<Playlist> for response::Playlist {
|
impl MapResponse<Playlist> for response::Playlist {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
pub(crate) mod channel;
|
pub(crate) mod channel;
|
||||||
pub(crate) mod history;
|
|
||||||
pub(crate) mod music_artist;
|
pub(crate) mod music_artist;
|
||||||
pub(crate) mod music_charts;
|
pub(crate) mod music_charts;
|
||||||
pub(crate) mod music_details;
|
pub(crate) mod music_details;
|
||||||
pub(crate) mod music_genres;
|
pub(crate) mod music_genres;
|
||||||
pub(crate) mod music_history;
|
|
||||||
pub(crate) mod music_item;
|
pub(crate) mod music_item;
|
||||||
pub(crate) mod music_new;
|
pub(crate) mod music_new;
|
||||||
pub(crate) mod music_playlist;
|
pub(crate) mod music_playlist;
|
||||||
|
|
@ -19,7 +17,6 @@ pub(crate) mod video_item;
|
||||||
|
|
||||||
pub(crate) use channel::Channel;
|
pub(crate) use channel::Channel;
|
||||||
pub(crate) use channel::ChannelAbout;
|
pub(crate) use channel::ChannelAbout;
|
||||||
pub(crate) use history::History;
|
|
||||||
pub(crate) use music_artist::MusicArtist;
|
pub(crate) use music_artist::MusicArtist;
|
||||||
pub(crate) use music_artist::MusicArtistAlbums;
|
pub(crate) use music_artist::MusicArtistAlbums;
|
||||||
pub(crate) use music_charts::MusicCharts;
|
pub(crate) use music_charts::MusicCharts;
|
||||||
|
|
@ -28,7 +25,6 @@ pub(crate) use music_details::MusicLyrics;
|
||||||
pub(crate) use music_details::MusicRelated;
|
pub(crate) use music_details::MusicRelated;
|
||||||
pub(crate) use music_genres::MusicGenre;
|
pub(crate) use music_genres::MusicGenre;
|
||||||
pub(crate) use music_genres::MusicGenres;
|
pub(crate) use music_genres::MusicGenres;
|
||||||
pub(crate) use music_history::MusicHistory;
|
|
||||||
pub(crate) use music_item::MusicContinuation;
|
pub(crate) use music_item::MusicContinuation;
|
||||||
pub(crate) use music_new::MusicNew;
|
pub(crate) use music_new::MusicNew;
|
||||||
pub(crate) use music_playlist::MusicPlaylist;
|
pub(crate) use music_playlist::MusicPlaylist;
|
||||||
|
|
@ -51,6 +47,15 @@ pub(crate) mod channel_rss;
|
||||||
#[cfg(feature = "rss")]
|
#[cfg(feature = "rss")]
|
||||||
pub(crate) use channel_rss::ChannelRss;
|
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::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError};
|
use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError};
|
||||||
use time::UtcOffset;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
model::{
|
model::{
|
||||||
self, traits::FromYtItem, AlbumId, AlbumItem, AlbumType, ArtistId, ArtistItem, ChannelId,
|
self, traits::FromYtItem, AlbumId, AlbumItem, AlbumType, ArtistId, ArtistItem, ChannelId,
|
||||||
HistoryItem, MusicItem, MusicItemType, MusicPlaylistItem, TrackItem, UserItem,
|
MusicItem, MusicItemType, MusicPlaylistItem, TrackItem, UserItem,
|
||||||
},
|
},
|
||||||
param::Language,
|
param::Language,
|
||||||
serializer::{
|
serializer::{
|
||||||
|
|
@ -23,6 +22,11 @@ use super::{
|
||||||
SimpleHeaderRenderer, Thumbnails, ThumbnailsWrap,
|
SimpleHeaderRenderer, Thumbnails, ThumbnailsWrap,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
use crate::model::HistoryItem;
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
use time::UtcOffset;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) enum ItemSection {
|
pub(crate) enum ItemSection {
|
||||||
|
|
@ -40,6 +44,7 @@ pub(crate) enum ItemSection {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct MusicShelf {
|
pub(crate) struct MusicShelf {
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
#[serde_as(as = "Option<Text>")]
|
#[serde_as(as = "Option<Text>")]
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
/// Playlist ID (only for playlists)
|
/// Playlist ID (only for playlists)
|
||||||
|
|
@ -1270,6 +1275,7 @@ impl MusicListMapper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
pub fn conv_history_items(
|
pub fn conv_history_items(
|
||||||
self,
|
self,
|
||||||
date_txt: Option<String>,
|
date_txt: Option<String>,
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,11 @@ use serde::Deserialize;
|
||||||
use serde_with::{
|
use serde_with::{
|
||||||
rust::deserialize_ignore_any, serde_as, DefaultOnError, DisplayFromStr, VecSkipError,
|
rust::deserialize_ignore_any, serde_as, DefaultOnError, DisplayFromStr, VecSkipError,
|
||||||
};
|
};
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
use super::{
|
use super::{ChannelBadge, ContentImage, ContinuationEndpoint, PhMetadataView, Thumbnails};
|
||||||
ChannelBadge, ContentImage, ContinuationEndpoint, PhMetadataView, SimpleHeaderRenderer,
|
|
||||||
Thumbnails,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
model::{Channel, ChannelItem, ChannelTag, HistoryItem, PlaylistItem, VideoItem, YouTubeItem},
|
model::{Channel, ChannelItem, ChannelTag, PlaylistItem, VideoItem, YouTubeItem},
|
||||||
param::Language,
|
param::Language,
|
||||||
serializer::{
|
serializer::{
|
||||||
text::{AttributedText, Text, TextComponent},
|
text::{AttributedText, Text, TextComponent},
|
||||||
|
|
@ -18,6 +15,11 @@ use crate::{
|
||||||
util::{self, timeago, TryRemove},
|
util::{self, timeago, TryRemove},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
use crate::{client::response::SimpleHeaderRenderer, model::HistoryItem};
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
use time::UtcOffset;
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
@ -66,6 +68,7 @@ pub(crate) enum YouTubeListItem {
|
||||||
/// GridRenderer: contains videos on channel page
|
/// GridRenderer: contains videos on channel page
|
||||||
#[serde(alias = "expandedShelfContentsRenderer", alias = "gridRenderer")]
|
#[serde(alias = "expandedShelfContentsRenderer", alias = "gridRenderer")]
|
||||||
ItemSectionRenderer {
|
ItemSectionRenderer {
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
header: Option<ItemSectionHeader>,
|
header: Option<ItemSectionHeader>,
|
||||||
#[serde(alias = "items")]
|
#[serde(alias = "items")]
|
||||||
contents: MapResult<Vec<YouTubeListItem>>,
|
contents: MapResult<Vec<YouTubeListItem>>,
|
||||||
|
|
@ -298,6 +301,7 @@ pub(crate) struct YouTubeListRenderer {
|
||||||
pub contents: MapResult<Vec<YouTubeListItem>>,
|
pub contents: MapResult<Vec<YouTubeListItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct ItemSectionHeader {
|
pub(crate) struct ItemSectionHeader {
|
||||||
|
|
@ -904,6 +908,7 @@ impl YouTubeListMapper<VideoItem> {
|
||||||
res.c.into_iter().for_each(|item| self.map_item(item));
|
res.c.into_iter().for_each(|item| self.map_item(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
pub(crate) fn conv_history_items(
|
pub(crate) fn conv_history_items(
|
||||||
self,
|
self,
|
||||||
date_txt: Option<String>,
|
date_txt: Option<String>,
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
error::{Error, ExtractionError},
|
error::{Error, ExtractionError},
|
||||||
model::{
|
model::{
|
||||||
paginator::{ContinuationEndpoint, Paginator},
|
paginator::{ContinuationEndpoint, Paginator},
|
||||||
ChannelItem, HistoryItem, PlaylistItem, VideoItem,
|
ChannelItem, HistoryItem, Playlist, PlaylistItem, VideoItem,
|
||||||
},
|
},
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
};
|
};
|
||||||
|
|
@ -148,6 +148,28 @@ impl RustyPipeQuery {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all liked videos of the logged-in user
|
||||||
|
///
|
||||||
|
/// Requires authentication cookies.
|
||||||
|
pub async fn liked_videos(&self) -> Result<Playlist, Error> {
|
||||||
|
self.clone()
|
||||||
|
.authenticated()
|
||||||
|
.playlist("LL")
|
||||||
|
.await
|
||||||
|
.map_err(crate::util::map_internal_playlist_err)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the "Watch later" playlist of the logged-in user
|
||||||
|
///
|
||||||
|
/// Requires authentication cookies.
|
||||||
|
pub async fn watch_later(&self) -> Result<Playlist, Error> {
|
||||||
|
self.clone()
|
||||||
|
.authenticated()
|
||||||
|
.playlist("WL")
|
||||||
|
.await
|
||||||
|
.map_err(crate::util::map_internal_playlist_err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MapResponse<Paginator<HistoryItem<VideoItem>>> for response::History {
|
impl MapResponse<Paginator<HistoryItem<VideoItem>>> for response::History {
|
||||||
|
|
@ -258,7 +280,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_history() {
|
fn map_history() {
|
||||||
let json_path = path!(*TESTFILES / "history" / "history.json");
|
let json_path = path!(*TESTFILES / "userdata" / "history.json");
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
||||||
let history: response::History =
|
let history: response::History =
|
||||||
|
|
@ -278,7 +300,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_subscription_feed() {
|
fn map_subscription_feed() {
|
||||||
let json_path = path!(*TESTFILES / "history" / "subscription_feed.json");
|
let json_path = path!(*TESTFILES / "userdata" / "subscription_feed.json");
|
||||||
let json_file = File::open(json_path).unwrap();
|
let json_file = File::open(json_path).unwrap();
|
||||||
|
|
||||||
let history: response::History =
|
let history: response::History =
|
||||||
|
|
@ -21,7 +21,7 @@ use regex::Regex;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{AuthError, Error, ExtractionError},
|
error::Error,
|
||||||
param::{Country, Language, COUNTRIES},
|
param::{Country, Language, COUNTRIES},
|
||||||
serializer::text::TextComponent,
|
serializer::text::TextComponent,
|
||||||
};
|
};
|
||||||
|
|
@ -581,9 +581,10 @@ where
|
||||||
///
|
///
|
||||||
/// If no user is logged in, YouTube returns a "NotFound" error. This has to be corrected
|
/// If no user is logged in, YouTube returns a "NotFound" error. This has to be corrected
|
||||||
/// into a NoLogin error.
|
/// into a NoLogin error.
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
pub fn map_internal_playlist_err(e: Error) -> Error {
|
pub fn map_internal_playlist_err(e: Error) -> Error {
|
||||||
if let Error::Extraction(ExtractionError::NotFound { .. }) = e {
|
if let Error::Extraction(crate::error::ExtractionError::NotFound { .. }) = e {
|
||||||
Error::Auth(AuthError::NoLogin)
|
Error::Auth(crate::error::AuthError::NoLogin)
|
||||||
} else {
|
} else {
|
||||||
e
|
e
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -347,6 +347,7 @@ pub fn parse_textual_date_to_dt(
|
||||||
/// Parse a textual date (e.g. "29 minutes ago" "Jul 2, 2014") into a Date object.
|
/// Parse a textual date (e.g. "29 minutes ago" "Jul 2, 2014") into a Date object.
|
||||||
///
|
///
|
||||||
/// Returns None if the date could not be parsed.
|
/// Returns None if the date could not be parsed.
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
pub fn parse_textual_date_to_d(
|
pub fn parse_textual_date_to_d(
|
||||||
lang: Language,
|
lang: Language,
|
||||||
utc_offset: UtcOffset,
|
utc_offset: UtcOffset,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::fmt::Display;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use rstest::{fixture, rstest};
|
use rstest::{fixture, rstest};
|
||||||
use rustypipe::model::{HistoryItem, TrackItem, TrackType, VideoItem};
|
use rustypipe::model::TrackType;
|
||||||
use rustypipe::param::{AlbumOrder, LANGUAGES};
|
use rustypipe::param::{AlbumOrder, LANGUAGES};
|
||||||
use time::{macros::date, OffsetDateTime};
|
use time::{macros::date, OffsetDateTime};
|
||||||
|
|
||||||
|
|
@ -2728,9 +2728,12 @@ async fn isrc_search_languages(rp: RustyPipe) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod cookie_auth {
|
#[cfg(feature = "userdata")]
|
||||||
|
mod user_data {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use rustypipe::model::{HistoryItem, TrackItem, VideoItem};
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn history(rp: RustyPipe) {
|
async fn history(rp: RustyPipe) {
|
||||||
|
|
@ -2814,6 +2817,30 @@ mod cookie_auth {
|
||||||
let tracks = rp.query().music_liked_tracks().await.unwrap();
|
let tracks = rp.query().music_liked_tracks().await.unwrap();
|
||||||
assert_next_items(tracks.tracks, rp.query(), 5).await;
|
assert_next_items(tracks.tracks, rp.query(), 5).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assert that the history paginator produces at least n items
|
||||||
|
async fn assert_next_history<Q: AsRef<RustyPipeQuery>>(
|
||||||
|
paginator: Paginator<HistoryItem<VideoItem>>,
|
||||||
|
query: Q,
|
||||||
|
n_items: usize,
|
||||||
|
) {
|
||||||
|
let mut p = paginator;
|
||||||
|
let query = query.as_ref();
|
||||||
|
p.extend_limit(query, n_items).await.unwrap();
|
||||||
|
assert_gte(p.items.len(), n_items, "items");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that the music history paginator produces at least n items
|
||||||
|
async fn assert_next_music_history<Q: AsRef<RustyPipeQuery>>(
|
||||||
|
paginator: Paginator<HistoryItem<TrackItem>>,
|
||||||
|
query: Q,
|
||||||
|
n_items: usize,
|
||||||
|
) {
|
||||||
|
let mut p = paginator;
|
||||||
|
let query = query.as_ref();
|
||||||
|
p.extend_limit(query, n_items).await.unwrap();
|
||||||
|
assert_gte(p.items.len(), n_items, "items");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
|
@ -2940,30 +2967,6 @@ async fn assert_next_items<T: FromYtItem, Q: AsRef<RustyPipeQuery>>(
|
||||||
assert_gte(p.items.len(), n_items, "items");
|
assert_gte(p.items.len(), n_items, "items");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Assert that the history paginator produces at least n items
|
|
||||||
async fn assert_next_history<Q: AsRef<RustyPipeQuery>>(
|
|
||||||
paginator: Paginator<HistoryItem<VideoItem>>,
|
|
||||||
query: Q,
|
|
||||||
n_items: usize,
|
|
||||||
) {
|
|
||||||
let mut p = paginator;
|
|
||||||
let query = query.as_ref();
|
|
||||||
p.extend_limit(query, n_items).await.unwrap();
|
|
||||||
assert_gte(p.items.len(), n_items, "items");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Assert that the music history paginator produces at least n items
|
|
||||||
async fn assert_next_music_history<Q: AsRef<RustyPipeQuery>>(
|
|
||||||
paginator: Paginator<HistoryItem<TrackItem>>,
|
|
||||||
query: Q,
|
|
||||||
n_items: usize,
|
|
||||||
) {
|
|
||||||
let mut p = paginator;
|
|
||||||
let query = query.as_ref();
|
|
||||||
p.extend_limit(query, n_items).await.unwrap();
|
|
||||||
assert_gte(p.items.len(), n_items, "items");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_frameset(frameset: &Frameset) {
|
fn assert_frameset(frameset: &Frameset) {
|
||||||
assert_gte(frameset.frame_height, 20, "frame height");
|
assert_gte(frameset.frame_height, 20, "frame height");
|
||||||
|
|
@ -3025,10 +3028,6 @@ async fn all_send_and_sync() {
|
||||||
rp.query()
|
rp.query()
|
||||||
.drm_license("", rustypipe::model::DrmSystem::Widevine, "", "", &[]),
|
.drm_license("", rustypipe::model::DrmSystem::Widevine, "", "", &[]),
|
||||||
);
|
);
|
||||||
send_and_sync(rp.query().history());
|
|
||||||
send_and_sync(rp.query().history_continuation("", None));
|
|
||||||
send_and_sync(rp.query().history_search(""));
|
|
||||||
send_and_sync(rp.query().liked_videos());
|
|
||||||
send_and_sync(rp.query().music_album(""));
|
send_and_sync(rp.query().music_album(""));
|
||||||
send_and_sync(rp.query().music_artist("", false));
|
send_and_sync(rp.query().music_artist("", false));
|
||||||
send_and_sync(rp.query().music_artist_albums("", None, None));
|
send_and_sync(rp.query().music_artist_albums("", None, None));
|
||||||
|
|
@ -3037,9 +3036,6 @@ async fn all_send_and_sync() {
|
||||||
send_and_sync(rp.query().music_details(""));
|
send_and_sync(rp.query().music_details(""));
|
||||||
send_and_sync(rp.query().music_genre(""));
|
send_and_sync(rp.query().music_genre(""));
|
||||||
send_and_sync(rp.query().music_genres());
|
send_and_sync(rp.query().music_genres());
|
||||||
send_and_sync(rp.query().music_history());
|
|
||||||
send_and_sync(rp.query().music_history_continuation("", None));
|
|
||||||
send_and_sync(rp.query().music_liked_tracks());
|
|
||||||
send_and_sync(rp.query().music_lyrics(""));
|
send_and_sync(rp.query().music_lyrics(""));
|
||||||
send_and_sync(rp.query().music_new_albums());
|
send_and_sync(rp.query().music_new_albums());
|
||||||
send_and_sync(rp.query().music_new_videos());
|
send_and_sync(rp.query().music_new_videos());
|
||||||
|
|
@ -3048,10 +3044,6 @@ async fn all_send_and_sync() {
|
||||||
send_and_sync(rp.query().music_radio_playlist(""));
|
send_and_sync(rp.query().music_radio_playlist(""));
|
||||||
send_and_sync(rp.query().music_radio_track(""));
|
send_and_sync(rp.query().music_radio_track(""));
|
||||||
send_and_sync(rp.query().music_related(""));
|
send_and_sync(rp.query().music_related(""));
|
||||||
send_and_sync(rp.query().music_saved_albums());
|
|
||||||
send_and_sync(rp.query().music_saved_artists());
|
|
||||||
send_and_sync(rp.query().music_saved_playlists());
|
|
||||||
send_and_sync(rp.query().music_saved_tracks());
|
|
||||||
send_and_sync(rp.query().music_search::<MusicItem, _>("", None));
|
send_and_sync(rp.query().music_search::<MusicItem, _>("", None));
|
||||||
send_and_sync(rp.query().music_search_albums(""));
|
send_and_sync(rp.query().music_search_albums(""));
|
||||||
send_and_sync(rp.query().music_search_artists(""));
|
send_and_sync(rp.query().music_search_artists(""));
|
||||||
|
|
@ -3068,17 +3060,32 @@ async fn all_send_and_sync() {
|
||||||
send_and_sync(rp.query().raw(ClientType::Desktop, "", ""));
|
send_and_sync(rp.query().raw(ClientType::Desktop, "", ""));
|
||||||
send_and_sync(rp.query().resolve_string("", false));
|
send_and_sync(rp.query().resolve_string("", false));
|
||||||
send_and_sync(rp.query().resolve_url("", false));
|
send_and_sync(rp.query().resolve_url("", false));
|
||||||
send_and_sync(rp.query().saved_playlists());
|
|
||||||
send_and_sync(rp.query().search::<YouTubeItem, _>(""));
|
send_and_sync(rp.query().search::<YouTubeItem, _>(""));
|
||||||
send_and_sync(
|
send_and_sync(
|
||||||
rp.query()
|
rp.query()
|
||||||
.search_filter::<YouTubeItem, _>("", &SearchFilter::default()),
|
.search_filter::<YouTubeItem, _>("", &SearchFilter::default()),
|
||||||
);
|
);
|
||||||
send_and_sync(rp.query().search_suggestion(""));
|
send_and_sync(rp.query().search_suggestion(""));
|
||||||
send_and_sync(rp.query().subscription_feed());
|
|
||||||
send_and_sync(rp.query().subscriptions());
|
|
||||||
send_and_sync(rp.query().trending());
|
send_and_sync(rp.query().trending());
|
||||||
send_and_sync(rp.query().video_comments("", None));
|
send_and_sync(rp.query().video_comments("", None));
|
||||||
send_and_sync(rp.query().video_details(""));
|
send_and_sync(rp.query().video_details(""));
|
||||||
send_and_sync(rp.query().watch_later());
|
|
||||||
|
#[cfg(feature = "userdata")]
|
||||||
|
{
|
||||||
|
send_and_sync(rp.query().history());
|
||||||
|
send_and_sync(rp.query().history_continuation("", None));
|
||||||
|
send_and_sync(rp.query().history_search(""));
|
||||||
|
send_and_sync(rp.query().liked_videos());
|
||||||
|
send_and_sync(rp.query().watch_later());
|
||||||
|
send_and_sync(rp.query().music_history());
|
||||||
|
send_and_sync(rp.query().music_history_continuation("", None));
|
||||||
|
send_and_sync(rp.query().music_saved_albums());
|
||||||
|
send_and_sync(rp.query().music_saved_artists());
|
||||||
|
send_and_sync(rp.query().music_saved_playlists());
|
||||||
|
send_and_sync(rp.query().music_saved_tracks());
|
||||||
|
send_and_sync(rp.query().saved_playlists());
|
||||||
|
send_and_sync(rp.query().subscription_feed());
|
||||||
|
send_and_sync(rp.query().subscriptions());
|
||||||
|
send_and_sync(rp.query().music_liked_tracks());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in a new issue