Add account scope for search command

This commit is contained in:
bytedream 2024-04-06 21:23:21 +02:00
parent 4b74299733
commit 25cde6163c
3 changed files with 65 additions and 9 deletions

View file

@ -225,8 +225,6 @@ async fn execute_executor(executor: impl Execute, ctx: Context) {
if let Some(crunchy_error) = err.downcast_mut::<Error>() {
if let Error::Block { message, .. } = crunchy_error {
*message = "Triggered Cloudflare bot protection. Try again later or use a VPN or proxy to spoof your location".to_string()
} else if let Error::Request { message, .. } = crunchy_error {
*message = "You've probably hit a rate limit. Try again later, generally after 10-20 minutes the rate limit is over and you can continue to use the cli".to_string()
}
error!("An error occurred: {}", crunchy_error)

View file

@ -8,6 +8,7 @@ use crunchyroll_rs::common::StreamExt;
use crunchyroll_rs::search::QueryResults;
use crunchyroll_rs::{Episode, Locale, MediaCollection, MovieListing, MusicVideo, Series};
use log::warn;
use std::sync::Arc;
#[derive(Debug, clap::Parser)]
#[clap(about = "Search in videos")]
@ -87,11 +88,16 @@ pub struct Search {
/// concert.premium_only → If the concert is only available with Crunchyroll premium
///
/// stream.locale → Stream locale/language
/// stream.dash_url → Stream url in DASH format
/// stream.is_drm → If `stream.is_drm` is DRM encrypted
/// stream.dash_url → Stream url in DASH format. You need to set the `Authorization` header to `Bearer <account.token>` when requesting this url
/// stream.is_drm → If `stream.dash_url` is DRM encrypted
///
/// subtitle.locale → Subtitle locale/language
/// subtitle.url → Url to the subtitle
///
/// account.token → Access token to make request to restricted endpoints. This token is only valid for a max. of 5 minutes
/// account.id → Internal ID of the user account
/// account.profile_name → Profile name of the account
/// account.email → Email address of the account
#[arg(short, long, verbatim_doc_comment)]
#[arg(default_value = "S{{season.number}}E{{episode.number}} - {{episode.title}}")]
output: String,
@ -143,13 +149,14 @@ impl Execute for Search {
output
};
let crunchy_arc = Arc::new(ctx.crunchy);
for (media_collection, url_filter) in input {
let filter_options = FilterOptions {
audio: self.audio.clone(),
url_filter,
};
let format = Format::new(self.output.clone(), filter_options)?;
let format = Format::new(self.output.clone(), filter_options, crunchy_arc.clone())?;
println!("{}", format.parse(media_collection).await?);
}

View file

@ -2,13 +2,15 @@ use crate::search::filter::FilterOptions;
use anyhow::{bail, Result};
use crunchyroll_rs::media::{Stream, Subtitle};
use crunchyroll_rs::{
Concert, Episode, Locale, MediaCollection, Movie, MovieListing, MusicVideo, Season, Series,
Concert, Crunchyroll, Episode, Locale, MediaCollection, Movie, MovieListing, MusicVideo,
Season, Series,
};
use regex::Regex;
use serde::Serialize;
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::ops::Range;
use std::sync::Arc;
#[derive(Default, Serialize)]
struct FormatSeries {
@ -191,6 +193,27 @@ impl From<&Subtitle> for FormatSubtitle {
}
}
#[derive(Default, Serialize)]
struct FormatAccount {
pub token: String,
pub id: String,
pub profile_name: String,
pub email: String,
}
impl FormatAccount {
pub async fn async_from(value: &Crunchyroll) -> Result<Self> {
let account = value.account().await?;
Ok(Self {
token: value.access_token().await,
id: account.account_id,
profile_name: account.profile_name,
email: account.email,
})
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
enum Scope {
Series,
@ -202,6 +225,7 @@ enum Scope {
Concert,
Stream,
Subtitle,
Account,
}
macro_rules! must_match_if_true {
@ -230,10 +254,15 @@ pub struct Format {
pattern_count: HashMap<Scope, u32>,
input: String,
filter_options: FilterOptions,
crunchyroll: Arc<Crunchyroll>,
}
impl Format {
pub fn new(input: String, filter_options: FilterOptions) -> Result<Self> {
pub fn new(
input: String,
filter_options: FilterOptions,
crunchyroll: Arc<Crunchyroll>,
) -> Result<Self> {
let scope_regex = Regex::new(r"(?m)\{\{\s*(?P<scope>\w+)\.(?P<field>\w+)\s*}}").unwrap();
let mut pattern = vec![];
let mut pattern_count = HashMap::new();
@ -260,6 +289,7 @@ impl Format {
Scope::Concert => FormatConcert
Scope::Stream => FormatStream
Scope::Subtitle => FormatSubtitle
Scope::Account => FormatAccount
);
for capture in scope_regex.captures_iter(&input) {
@ -277,6 +307,7 @@ impl Format {
"concert" => Scope::Concert,
"stream" => Scope::Stream,
"subtitle" => Scope::Subtitle,
"account" => Scope::Account,
_ => bail!("'{}.{}' is not a valid keyword", scope, field),
};
@ -302,6 +333,7 @@ impl Format {
pattern_count,
input,
filter_options,
crunchyroll,
})
}
@ -316,6 +348,7 @@ impl Format {
Scope::Episode,
Scope::Stream,
Scope::Subtitle,
Scope::Account,
])?;
self.parse_series(media_collection).await
@ -326,17 +359,28 @@ impl Format {
Scope::Movie,
Scope::Stream,
Scope::Subtitle,
Scope::Account,
])?;
self.parse_movie_listing(media_collection).await
}
MediaCollection::MusicVideo(_) => {
self.check_scopes(vec![Scope::MusicVideo, Scope::Stream, Scope::Subtitle])?;
self.check_scopes(vec![
Scope::MusicVideo,
Scope::Stream,
Scope::Subtitle,
Scope::Account,
])?;
self.parse_music_video(media_collection).await
}
MediaCollection::Concert(_) => {
self.check_scopes(vec![Scope::Concert, Scope::Stream, Scope::Subtitle])?;
self.check_scopes(vec![
Scope::Concert,
Scope::Stream,
Scope::Subtitle,
Scope::Account,
])?;
self.parse_concert(media_collection).await
}
@ -349,6 +393,7 @@ impl Format {
let episode_empty = self.check_pattern_count_empty(Scope::Episode);
let stream_empty = self.check_pattern_count_empty(Scope::Stream)
&& self.check_pattern_count_empty(Scope::Subtitle);
let account_empty = self.check_pattern_count_empty(Scope::Account);
#[allow(clippy::type_complexity)]
let mut tree: Vec<(Season, Vec<(Episode, Vec<Stream>)>)> = vec![];
@ -431,6 +476,11 @@ impl Format {
}
let mut output = vec![];
let account_map = if !account_empty {
self.serializable_to_json_map(FormatAccount::async_from(&self.crunchyroll).await?)
} else {
Map::default()
};
let series_map = self.serializable_to_json_map(FormatSeries::from(&series));
for (season, episodes) in tree {
let season_map = self.serializable_to_json_map(FormatSeason::from(&season));
@ -442,6 +492,7 @@ impl Format {
output.push(
self.replace_all(
HashMap::from([
(Scope::Account, &account_map),
(Scope::Series, &series_map),
(Scope::Season, &season_map),
(Scope::Episode, &episode_map),