chore: bump Invidious client deps, update tests

* set result sorting by most viewed to return the mostly seen video -
  which probably is the one we want (yes, wild assumption I know)
0.3.x
Davide Polonio 2024-01-15 12:50:57 +01:00
parent 30990304d9
commit 0561c49d47
4 changed files with 56 additions and 54 deletions

View File

@ -12,7 +12,7 @@ pretty_env_logger = "0.5.0"
tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"] } tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"] }
rspotify = { version = "0.12.0", features = ["default"]} rspotify = { version = "0.12.0", features = ["default"]}
sentry = "0.32.1" sentry = "0.32.1"
invidious = "0.2.1" invidious = { version = "0.7.4", no-default-features = true, features = ["reqwest_async"]}
itertools = "0.12.0" itertools = "0.12.0"
async-trait = "0.1.56" async-trait = "0.1.56"

View File

@ -1,9 +1,9 @@
use async_trait::async_trait; use async_trait::async_trait;
#[cfg(test)] #[cfg(test)]
use mockall::{automock, mock, predicate::*}; use mockall::{automock, predicate::*};
use rspotify::model::Country::UnitedStates; use rspotify::model::Country::UnitedStates;
use rspotify::model::PlayableItem::{Episode, Track}; use rspotify::model::PlayableItem::{Episode, Track};
use rspotify::model::{AlbumId, PlaylistId, TrackId, Market}; use rspotify::model::{AlbumId, Market, PlaylistId, TrackId};
use rspotify::prelude::*; use rspotify::prelude::*;
use rspotify::{ClientCredsSpotify, Credentials}; use rspotify::{ClientCredsSpotify, Credentials};
use std::sync::Arc; use std::sync::Arc;
@ -101,7 +101,11 @@ impl SearchableClient for Client {
}; };
// Search track from US market // Search track from US market
match self.client.track(track_id, Some(Market::Country(UnitedStates))).await { match self
.client
.track(track_id, Some(Market::Country(UnitedStates)))
.await
{
Ok(track) => Some(TrackInfo { Ok(track) => Some(TrackInfo {
name: track.name, name: track.name,
artists: track.artists.iter().map(|x| x.name.clone()).collect(), artists: track.artists.iter().map(|x| x.name.clone()).collect(),
@ -118,7 +122,11 @@ impl SearchableClient for Client {
}; };
// Search album from US market // Search album from US market
match self.client.album(album_id, Some(Market::Country(UnitedStates))).await { match self
.client
.album(album_id, Some(Market::Country(UnitedStates)))
.await
{
Ok(album) => Some(AlbumInfo { Ok(album) => Some(AlbumInfo {
name: album.name, name: album.name,
artists: album.artists.iter().map(|x| x.name.clone()).collect(), artists: album.artists.iter().map(|x| x.name.clone()).collect(),

View File

@ -35,7 +35,6 @@ async fn should_search_track_by_spotify_id() {
published: 0, published: 0,
published_text: "".to_string(), published_text: "".to_string(),
live_now: false, live_now: false,
paid: false,
premium: false, premium: false,
}], }],
}) })

View File

@ -1,6 +1,9 @@
use async_trait::async_trait; use async_trait::async_trait;
use invidious::hidden::SearchItem;
use invidious::{ClientAsync, ClientAsyncTrait, MethodAsync};
use itertools::Itertools;
#[cfg(test)] #[cfg(test)]
use mockall::{automock, mock, predicate::*}; use mockall::{automock, predicate::*};
use std::error::Error; use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
@ -33,7 +36,6 @@ pub(crate) struct Video {
pub(crate) published: u64, pub(crate) published: u64,
pub(crate) published_text: String, pub(crate) published_text: String,
pub(crate) live_now: bool, pub(crate) live_now: bool,
pub(crate) paid: bool,
pub(crate) premium: bool, pub(crate) premium: bool,
} }
@ -52,16 +54,18 @@ const BY_UPLOAD_DATE: &SearchSortBy = "upload_date";
#[allow(unused_variables)] #[allow(unused_variables)]
const BY_VIEW_COUNT: &SearchSortBy = "view_count"; const BY_VIEW_COUNT: &SearchSortBy = "view_count";
#[derive(Debug, Clone)] #[derive(Clone)]
pub(crate) struct Client { pub(crate) struct Client {
client: Arc<invidious::asynchronous::Client>, client: Arc<ClientAsync>,
} }
impl Client { impl Client {
pub(crate) async fn new() -> Self { pub(crate) async fn new() -> Self {
// TODO check for a stable instance // TODO check for a stable instance, or rotate between a pool of stable ones
let client = let client = ClientAsync::new(
invidious::asynchronous::Client::new(String::from("https://inv.bp.projectsegfau.lt")); String::from("https://inv.bp.projectsegfau.lt"),
MethodAsync::default(),
);
Client { Client {
client: Arc::new(client), client: Arc::new(client),
@ -71,7 +75,7 @@ impl Client {
#[allow(dead_code)] #[allow(dead_code)]
#[allow(unused_variables)] #[allow(unused_variables)]
#[cfg(test)] #[cfg(test)]
pub(crate) fn new_with_dependencies(client: invidious::asynchronous::Client) -> Self { pub(crate) fn new_with_dependencies(client: invidious::ClientAsync) -> Self {
Client { Client {
client: Arc::new(client), client: Arc::new(client),
} }
@ -99,44 +103,31 @@ impl SearchableClient for Client {
let videos: Vec<Video> = search let videos: Vec<Video> = search
.items .items
.iter() .iter()
.map(|search_result| match search_result { .map(|search_result| {
invidious::structs::hidden::SearchItem::Video { let video = match search_result {
title, SearchItem::Video(item) => Some(Video {
videoId, title: item.title.clone(),
author, video_id: item.id.clone(),
authorId, author: item.author.clone(),
authorUrl, author_id: item.author_id.clone(),
lengthSeconds, author_url: item.author_url.clone(),
videoThumbnails: _, length_seconds: item.length.clone() as u64,
description, description: item.description.clone(),
descriptionHtml, description_html: item.description_html.clone(),
viewCount, view_count: item.views.clone(),
published, published: item.published.clone(),
publishedText, published_text: item.published_text.clone(),
liveNow, live_now: item.live.clone(),
paid, premium: item.premium.clone(),
premium, }),
} => Some(Video { _ => None,
title: title.clone(), };
video_id: videoId.clone(), video
author: author.clone(),
author_id: authorId.clone(),
author_url: authorUrl.clone(),
length_seconds: lengthSeconds.clone(),
description: description.clone(),
description_html: descriptionHtml.clone(),
view_count: viewCount.clone(),
published: published.clone(),
published_text: publishedText.clone(),
live_now: liveNow.clone(),
paid: paid.clone(),
premium: premium.clone(),
}),
_ => None,
}) })
.filter(|x| x.is_some()) .filter(|x| x.is_some())
.map(|x| x.unwrap()) .map(|x| x.unwrap())
.filter(|x| !x.premium && !x.paid && !x.live_now) .filter(|x| !x.premium && !x.live_now)
.sorted_by(|a, b| b.view_count.cmp(&a.view_count))
.collect(); .collect();
Result::Ok(VideoSearch { items: videos }) Result::Ok(VideoSearch { items: videos })
@ -149,13 +140,17 @@ mod tests {
#[test] #[test]
fn should_search_properly() { fn should_search_properly() {
// FIXME: the real solution for properly testing this piece of code would be to spin up a Mocked Invidious
// instance (i.e. mock-server) and mock the server responses. With the current implementation, the tests
// will always be flaky.
let client = tokio_test::block_on(Client::new()); let client = tokio_test::block_on(Client::new());
let result = tokio_test::block_on(client.search_video( let result = tokio_test::block_on(
"vfdskvnfdsjklvnfdsjklvnfsdjkldsvmdlfmvkdfslvsdfmklsdvlvfdnjkvnfdsjkvnfsdjk", client.search_video("Eminem - Lose Yourself", Some(BY_UPLOAD_DATE)),
Some(BY_UPLOAD_DATE), )
))
.unwrap(); .unwrap();
assert_eq!(30, result.items.len()) assert_eq!(19, result.items.len());
// Just check the first one for now - in the future we can check for the whole array
assert_eq!("Eminem - Lose Yourself [HD]", result.items[0].title);
} }
} }