diff --git a/CHANGELOG.md b/CHANGELOG.md index 610950cd1..2aeb4bce3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ - '(?i)\bHD\b' - '(?i)\bSD\b' ``` + - Trakt api config field `key` is now `api_key` ## 🌟 New Features - **Discord Notifications**: Support for Discord notifications via webhooks with optional Handlebars templates. diff --git a/Cargo.lock b/Cargo.lock index 0d8bb0378..871ed35b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1156,7 +1156,7 @@ dependencies = [ [[package]] name = "frontend" -version = "3.2.40" +version = "3.2.41" dependencies = [ "anyhow", "base64", @@ -3983,7 +3983,7 @@ dependencies = [ [[package]] name = "shared" -version = "3.2.40" +version = "3.2.41" dependencies = [ "base64", "bitflags 2.10.0", @@ -4557,7 +4557,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tuliprox" -version = "3.2.40" +version = "3.2.41" dependencies = [ "arc-swap", "async-compression", diff --git a/README.md b/README.md index 4a4591516..675c343a1 100644 --- a/README.md +++ b/README.md @@ -1318,6 +1318,11 @@ You can define a `Trakt` config like resolve_series: false resolve_vod: false trakt: + api: + api_key: "your api key" + version: "2" + url: "https://api.trakt.tv" + user_agent: "Mozilla/5.0" lists: - user: "linaspurinis" list_slug: "top-watched-movies-of-the-week" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 7059cdff0..4384cb5f6 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tuliprox" -version = "3.2.40" +version = "3.2.41" edition = "2021" rust-version = "1.89.0" diff --git a/backend/src/api/model/download.rs b/backend/src/api/model/download.rs index 3aeb26d27..ccafd5086 100644 --- a/backend/src/api/model/download.rs +++ b/backend/src/api/model/download.rs @@ -3,11 +3,10 @@ use std::ffi::OsStr; use std::path::{Path, PathBuf}; use tokio::sync::{RwLock, Mutex}; use std::sync::Arc; -use deunicode::deunicode; use serde::{Deserialize, Serialize}; use crate::model::{VideoDownloadConfig}; -use shared::utils::{CONSTANTS, FILENAME_TRIM_PATTERNS, hash_string_as_hex}; +use shared::utils::{CONSTANTS, FILENAME_TRIM_PATTERNS, hash_string_as_hex, deunicode_string}; /// File-Download information. #[derive(Clone)] @@ -70,7 +69,7 @@ impl FileDownload { pub fn new(req_url: &str, req_filename: &str, download_cfg: &VideoDownloadConfig) -> Option { match reqwest::Url::parse(req_url) { Ok(url) => { - let tmp_filename = CONSTANTS.re_filename.replace_all(&deunicode(req_filename) + let tmp_filename = CONSTANTS.re_filename.replace_all(&deunicode_string(req_filename) .replace(' ', "_"), "") .replace("__", "_") .replace("_-_", "-"); diff --git a/backend/src/model/config/target.rs b/backend/src/model/config/target.rs index 60e63065d..3adb242e3 100644 --- a/backend/src/model/config/target.rs +++ b/backend/src/model/config/target.rs @@ -1,7 +1,8 @@ use crate::model::mapping::Mapping; use crate::model::config::trakt::TraktConfig; use arc_swap::ArcSwapOption; -use shared::model::{ConfigTargetDto, ConfigTargetOptions, HdHomeRunTargetOutputDto, M3uTargetOutputDto, ProcessingOrder, StrmExportStyle, StrmTargetOutputDto, TargetOutputDto, TargetType, TraktConfigDto, XtreamTargetOutputDto}; +use shared::model::{ConfigTargetDto, ConfigTargetOptions, HdHomeRunTargetOutputDto, M3uTargetOutputDto, + ProcessingOrder, StrmExportStyle, StrmTargetOutputDto, TargetOutputDto, TargetType, TraktConfigDto, XtreamTargetOutputDto}; use shared::model::PlaylistItemType; use std::sync::Arc; use regex::Regex; diff --git a/backend/src/model/config/trakt.rs b/backend/src/model/config/trakt.rs index 289a09b34..e60406654 100644 --- a/backend/src/model/config/trakt.rs +++ b/backend/src/model/config/trakt.rs @@ -4,18 +4,20 @@ use crate::model::macros; #[derive(Debug, Clone)] pub struct TraktApiConfig { - pub key: String, + pub api_key: String, pub version: String, pub url: String, + pub user_agent: String, } macros::from_impl!(TraktApiConfig); impl From<&TraktApiConfigDto> for TraktApiConfig { fn from(dto: &TraktApiConfigDto) -> Self { Self { - key: dto.key.clone(), + api_key: dto.api_key.clone(), version: dto.version.clone(), url: dto.url.clone(), + user_agent: dto.user_agent.clone(), } } } @@ -23,9 +25,10 @@ impl From<&TraktApiConfigDto> for TraktApiConfig { impl From<&TraktApiConfig> for TraktApiConfigDto { fn from(instance: &TraktApiConfig) -> Self { Self { - key: instance.key.clone(), + api_key: instance.api_key.clone(), version: instance.version.clone(), url: instance.url.clone(), + user_agent: instance.user_agent.clone(), } } } diff --git a/backend/src/processing/input_cache.rs b/backend/src/processing/input_cache.rs index 64f5b011b..7026ecb20 100644 --- a/backend/src/processing/input_cache.rs +++ b/backend/src/processing/input_cache.rs @@ -4,8 +4,8 @@ use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use std::time::{SystemTime, UNIX_EPOCH}; use log::{error, warn}; - -use crate::repository::storage::get_input_storage_path; +use shared::concat_string; +use crate::repository::storage::{get_input_storage_path, sanitize_name}; pub const STATUS_FILE: &str = "status.json"; @@ -30,10 +30,9 @@ pub struct InputStatus { pub fn resolve_input_storage_path(working_dir: &str, input_name: &str) -> PathBuf { if let Ok(path) = get_input_storage_path(input_name, working_dir) { path } else { - let sanitized_name: String = input_name.chars() - .map(|c| if c.is_alphanumeric() { c } else { '_' }) - .collect(); - Path::new(working_dir).join(format!("input_{sanitized_name}")) + let sanitized_name: String = sanitize_name(input_name); + let name = concat_string!(cap = 6 + sanitized_name.len(); "input_", &sanitized_name); + Path::new(working_dir).join(name) } } diff --git a/backend/src/processing/parser/xmltv.rs b/backend/src/processing/parser/xmltv.rs index f52767cc8..b90bf74ad 100644 --- a/backend/src/processing/parser/xmltv.rs +++ b/backend/src/processing/parser/xmltv.rs @@ -4,11 +4,10 @@ use crate::processing::processor::epg::EpgIdCache; use crate::utils::async_file_reader; use crate::utils::compressed_file_reader_async::CompressedFileReaderAsync; use dashmap::DashMap; -use deunicode::deunicode; use quick_xml::events::{BytesStart, BytesText, Event}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use shared::model::EpgNamePrefix; -use shared::utils::CONSTANTS; +use shared::utils::{deunicode_string, CONSTANTS}; use std::borrow::Cow; use std::cell::RefCell; use std::cmp::min; @@ -92,7 +91,7 @@ fn combine(join: &str, left: &str, right: &str) -> String { /// # Panics pub fn normalize_channel_name(name: &str, normalize_config: &EpgSmartMatchConfig) -> String { - let normalized = deunicode(name.trim()).to_lowercase(); + let normalized = deunicode_string(name.trim()).to_lowercase(); let (channel_name, suffix) = name_prefix(&normalized, normalize_config); // Remove all non-alphanumeric characters (except dashes and underscores). let cleaned_name = normalize_config.normalize_regex.replace_all(channel_name, ""); diff --git a/backend/src/repository/storage.rs b/backend/src/repository/storage.rs index 4564cf246..d4a663445 100644 --- a/backend/src/repository/storage.rs +++ b/backend/src/repository/storage.rs @@ -28,10 +28,14 @@ pub fn get_target_storage_path(cfg: &Config, target_name: &str) -> Option std::io::Result { - let sanitized_name: String = input_name.chars() +pub fn sanitize_name(name: &str) -> String { + name.chars() .map(|c| if c.is_alphanumeric() { c } else { '_' }) - .collect(); + .collect() +} + +pub fn get_input_storage_path(input_name: &str, working_dir: &str) -> std::io::Result { + let sanitized_name: String = sanitize_name(input_name); let name = concat_string!(cap = 6 + sanitized_name.len(); "input_", &sanitized_name); let path = Path::new(working_dir).join(name); // Create the directory and return the path or propagate the error diff --git a/backend/src/utils/trakt/client.rs b/backend/src/utils/trakt/client.rs index 5e34a6914..d04e4c604 100644 --- a/backend/src/utils/trakt/client.rs +++ b/backend/src/utils/trakt/client.rs @@ -3,7 +3,8 @@ use shared::error::TuliproxError; use reqwest::header::{HeaderMap, HeaderValue}; use log::{debug, info}; use shared::error::{info_err}; -use shared::utils::trim_last_slash; +use shared::model::DEFAULT_USER_AGENT; +use shared::utils::{trim_last_slash, TRAKT_API_KEY}; use super::errors::{handle_trakt_api_error}; pub struct TraktClient { @@ -27,8 +28,9 @@ impl TraktClient { let mut headers = HeaderMap::new(); headers.insert(reqwest::header::CONTENT_TYPE, HeaderValue::from_static(mime::APPLICATION_JSON.as_ref())); - headers.insert("trakt-api-key", HeaderValue::from_str(api_config.key.as_str()).unwrap_or_else(|_| HeaderValue::from_static(""))); - headers.insert("trakt-api-version", HeaderValue::from_str(api_config.version.as_str()).unwrap_or_else(|_| HeaderValue::from_static(""))); + headers.insert(reqwest::header::USER_AGENT, HeaderValue::from_str(api_config.user_agent.as_str()).unwrap_or_else(|_| HeaderValue::from_static(DEFAULT_USER_AGENT))); + headers.insert("trakt-api-key", HeaderValue::from_str(api_config.api_key.as_str()).unwrap_or_else(|_| HeaderValue::from_static(TRAKT_API_KEY))); + headers.insert("trakt-api-version", HeaderValue::from_str(api_config.version.as_str()).unwrap_or_else(|_| HeaderValue::from_static("2"))); headers } @@ -42,6 +44,8 @@ impl TraktClient { let url = self.build_list_url(&list_config.user, &list_config.list_slug); + debug!("trakt headers {:?}, {}", self.headers, url); + let response = self.client .get(&url) .headers(self.headers.clone()) diff --git a/backend/src/utils/trakt/mod.rs b/backend/src/utils/trakt/mod.rs index 41db3b443..fcf252197 100644 --- a/backend/src/utils/trakt/mod.rs +++ b/backend/src/utils/trakt/mod.rs @@ -3,14 +3,12 @@ mod client; mod errors; pub use self::client::*; -pub use self::errors::*; -use deunicode::deunicode; -use shared::utils::CONSTANTS; +use shared::utils::{deunicode_string, CONSTANTS}; /// Normalize title for matching - optimized version with reduced allocations pub fn normalize_title_for_matching(title: &str) -> String { - let normalized = deunicode(title.trim()); + let normalized = deunicode_string(title.trim()); let mut result = String::with_capacity(normalized.len()); diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index fb4726151..9e8dc062b 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "frontend" -version = "3.2.40" +version = "3.2.41" edition = "2021" rust-version = "1.89.0" [dependencies] -shared = { version = "3.2.40", path = "../shared" } +shared = { version = "3.2.41", path = "../shared" } chrono = "0" yew = "0.21" yew-router = "0.18" diff --git a/frontend/public/assets/i18n/en.json b/frontend/public/assets/i18n/en.json index d3db3b2dd..3b5ee280d 100644 --- a/frontend/public/assets/i18n/en.json +++ b/frontend/public/assets/i18n/en.json @@ -361,21 +361,19 @@ "EXPORT_STYLE": "Export Style", "STRM_PROPS": "Properties", "ADD_PROPERTY": "Add Property", - "TRAKT_API_KEY": "API-Key", - "TRAKT_API_VERSION": "API-Version", - "TRAKT_API_URL": "API-URL", + "API_KEY": "API-Key", + "API_VERSION": "API-Version", + "API_URL": "API-URL", "TRAKT_LISTS": "Lists", "TRAKT_USER": "Trakt User", "TRAKT_LIST_SLUG": "List Slug", "TRAKT_CATEGORY_NAME": "Category name", - "TRAKT_CONTENT_TYPE": "Content Type", "TRAKT_FUZZY_MATCH_THRESHOLD": "Fuzzy matchthreshold", "ADD_TRAKT_LIST": "Add a Trakt list", "EPG_LOGO_OVERRIDE": "Logo Override", "API_CONFIGURATION": "API Configuration", "HEADER_NAME": "Header Name", "HEADER_VALUE": "Header Value", - "API_KEY": "API-Key", "PANEL": "Panel", "PANEL_CLIENT_INFO": "Client info", "PANEL_CLIENT_NEW": "Client new", @@ -388,7 +386,8 @@ "CACHE_DURATION": "Cache duration", "BODY_TEMPLATE": "Body Template", "WEBHOOK_URL": "Webhook URL", - "AUTO": "Auto" + "AUTO": "Auto", + "API_USER_AGENT": "User Agent" }, "TITLE": { "USER_BOUQUET_EDITOR": "Playlist Category Selection" diff --git a/frontend/src/app/components/filter/filter_input.rs b/frontend/src/app/components/filter/filter_input.rs index 38353a159..4ccb2e9cb 100644 --- a/frontend/src/app/components/filter/filter_input.rs +++ b/frontend/src/app/components/filter/filter_input.rs @@ -1,3 +1,5 @@ +use std::cell::RefCell; +use std::rc::Rc; use crate::app::components::{AppIcon, FilterEditor, FilterView}; use crate::app::ConfigContext; use crate::model::{DialogAction, DialogActions, DialogResult}; @@ -60,13 +62,6 @@ pub fn FilterInput(props: &FilterInputProps) -> Html { }); } - let handle_filter_edit = { - let filter = filter_state.clone(); - Callback::from(move |flt: Option| { - filter.set(flt); - }) - }; - let handle_templates_edit = { let templates = templates_state.clone(); Callback::from(move |templ: Option>| { @@ -79,7 +74,6 @@ pub fn FilterInput(props: &FilterInputProps) -> Html { let filter_state = filter_state.clone(); let templates_state = templates_state.clone(); let on_change = props.on_change.clone(); - let handle_filter_edit = handle_filter_edit.clone(); let handle_templates_edit = handle_templates_edit.clone(); let dialog_actions = dialog_actions.clone(); Callback::from(move |e: MouseEvent| { @@ -87,7 +81,6 @@ pub fn FilterInput(props: &FilterInputProps) -> Html { let original_filter = (*filter_state).clone(); let original_templates = (*templates_state).clone(); let current_filter = (*filter_state).clone(); - let handle_filter_edit = handle_filter_edit.clone(); let handle_templates_edit = handle_templates_edit.clone(); let actions = dialog_actions.clone(); let dlg = dialog.clone(); @@ -95,12 +88,24 @@ pub fn FilterInput(props: &FilterInputProps) -> Html { let templates_state = templates_state.clone(); let on_change = on_change.clone(); spawn_local(async move { + // we need this refcell because the state hook does not update + // when we close the dialog + let filter_ref = Rc::new(RefCell::new((*filter_state).clone())); + let handle_filter_edit = { + let filter_ref_set = filter_ref.clone(); + let filter = filter_state.clone(); + Callback::from(move |flt: Option| { + *filter_ref_set.borrow_mut() = flt.clone(); + filter.set(flt); + }) + }; + let filter_view = html! {}; let result = dlg.content(filter_view, (*actions).clone(), false).await; match result { - DialogResult::Ok => on_change.emit((*filter_state).clone()), + DialogResult::Ok => on_change.emit(filter_ref.borrow().clone()), _ => { filter_state.set(original_filter); templates_state.set(original_templates); diff --git a/frontend/src/app/components/source_editor/output_xtream_form.rs b/frontend/src/app/components/source_editor/output_xtream_form.rs index dc3e4cf2f..af6f69515 100644 --- a/frontend/src/app/components/source_editor/output_xtream_form.rs +++ b/frontend/src/app/components/source_editor/output_xtream_form.rs @@ -15,12 +15,13 @@ const LABEL_SERIES: &str = "LABEL.SERIES"; const LABEL_RESOLVE: &str = "LABEL.RESOLVE"; const LABEL_RESOLVE_DELAY_SEC: &str = "LABEL.RESOLVE_DELAY_SEC"; const LABEL_FILTER: &str = "LABEL.FILTER"; -const LABEL_TRAKT_API_KEY: &str = "LABEL.TRAKT_API_KEY"; -const LABEL_TRAKT_API_VERSION: &str = "LABEL.TRAKT_API_VERSION"; -const LABEL_TRAKT_API_URL: &str = "LABEL.TRAKT_API_URL"; +const LABEL_TRAKT_API_KEY: &str = "LABEL.API_KEY"; +const LABEL_TRAKT_API_VERSION: &str = "LABEL.API_VERSION"; +const LABEL_TRAKT_API_URL: &str = "LABEL.API_URL"; const LABEL_TRAKT_LISTS: &str = "LABEL.TRAKT_LISTS"; const LABEL_ADD_TRAKT_LIST: &str = "LABEL.ADD_TRAKT_LIST"; const LABEL_API_CONFIGURATION: &str = "LABEL.API_CONFIGURATION"; +const LABEL_USER_AGENT: &str = "LABEL.API_USER_AGENT"; #[derive(Copy, Clone, PartialEq, Eq)] enum OutputFormPage { @@ -40,9 +41,10 @@ generate_form_reducer!( state: TraktApiConfigFormState { form: TraktApiConfigDto }, action_name: TraktApiConfigFormAction, fields { - Key => key: String, + ApiKey => api_key: String, Version => version: String, Url => url: String, + UserAgent => user_agent: String, } ); @@ -214,9 +216,10 @@ pub fn XtreamTargetOutputView(props: &XtreamTargetOutputViewProps) -> Html { // Trakt API Configuration

{translate.t(LABEL_API_CONFIGURATION)}

- { edit_field_text!(trakt_api, translate.t(LABEL_TRAKT_API_KEY), key, TraktApiConfigFormAction::Key) } + { edit_field_text!(trakt_api, translate.t(LABEL_TRAKT_API_KEY), api_key, TraktApiConfigFormAction::ApiKey) } { edit_field_text!(trakt_api, translate.t(LABEL_TRAKT_API_VERSION), version, TraktApiConfigFormAction::Version) } { edit_field_text!(trakt_api, translate.t(LABEL_TRAKT_API_URL), url, TraktApiConfigFormAction::Url) } + { edit_field_text!(trakt_api, translate.t(LABEL_USER_AGENT), url, TraktApiConfigFormAction::UserAgent) }
// Trakt Lists diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 565b84537..e1c55c9f0 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shared" -version = "3.2.40" +version = "3.2.41" edition = "2021" rust-version = "1.89.0" diff --git a/shared/src/foundation/filter.rs b/shared/src/foundation/filter.rs index 993990357..1f366a52c 100644 --- a/shared/src/foundation/filter.rs +++ b/shared/src/foundation/filter.rs @@ -12,7 +12,7 @@ use std::collections::HashMap; use crate::error::{info_err_res, TuliproxError}; use crate::info_err; pub use crate::model::{ItemField, PatternTemplate, PlaylistItem, PlaylistItemType, FieldGetAccessor, FieldSetAccessor, TemplateValue}; -use crate::utils::{DirectedGraph, StringInterner, CONSTANTS}; +use crate::utils::{deunicode_string, DirectedGraph, StringInterner, CONSTANTS}; pub fn get_field_value(pli: &PlaylistItem, field: ItemField) -> Cow<'_, str> { let header = &pli.header; @@ -53,7 +53,7 @@ impl ValueProvider<'_> { pub fn get(&self, field: &str) -> Option> { let val = self.pli.header.get_field(field)?; if self.match_as_ascii { - return Some(Cow::Owned(deunicode::deunicode(&val))); + return Some(Cow::Owned(deunicode_string(&val).into_owned())) } Some(val) } @@ -69,7 +69,7 @@ impl ValueAccessor<'_> { pub fn get(&self, field: &str) -> Option> { let val = self.pli.header.get_field(field)?; if self.match_as_ascii { - return Some(Cow::Owned(deunicode::deunicode(&val))); + return Some(Cow::Owned(deunicode_string(&val).into_owned())) } Some(val) } diff --git a/shared/src/model/config/target.rs b/shared/src/model/config/target.rs index d42e3314e..8d965dc74 100644 --- a/shared/src/model/config/target.rs +++ b/shared/src/model/config/target.rs @@ -6,7 +6,7 @@ use crate::foundation::filter::{get_filter, Filter}; use crate::model::{ClusterFlags, ConfigFavouritesDto, ConfigRenameDto, ConfigSortDto, HdHomeRunDeviceOverview, PatternTemplate, ProcessingOrder, StrmExportStyle, TargetType, TraktConfigDto}; use crate::utils::{is_true, is_false, default_as_true, default_resolve_delay_secs, default_as_default, - is_default_resolve_delay_secs, is_zero_u16, is_config_target_options_empty}; + is_default_resolve_delay_secs, is_zero_u16, is_config_target_options_empty, is_default_processing_order}; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)] #[serde(deny_unknown_fields)] pub struct ConfigTargetOptions { @@ -220,7 +220,7 @@ pub struct ConfigTargetDto { pub mapping: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub favourites: Option>, - #[serde(default)] + #[serde(default, skip_serializing_if = "is_default_processing_order")] pub processing_order: ProcessingOrder, #[serde(default, skip_serializing_if = "Option::is_none")] pub watch: Option>, diff --git a/shared/src/model/config/trakt.rs b/shared/src/model/config/trakt.rs index 81a77525b..240642951 100644 --- a/shared/src/model/config/trakt.rs +++ b/shared/src/model/config/trakt.rs @@ -1,13 +1,8 @@ use serde::{Deserialize, Serialize}; use std::fmt; use std::str::FromStr; - -const TRAKT_API_KEY: &str = "0183a05ad97098d87287fe46da4ae286f434f32e8e951caad4cc147c947d79a3"; -const TRAKT_API_VERSION: &str = "2"; -const TRAKT_API_URL: &str = "https://api.trakt.tv"; -fn default_fuzzy_threshold() -> u8 { - 80 -} +use crate::model::DEFAULT_USER_AGENT; +use crate::utils::{TRAKT_API_KEY, TRAKT_API_URL, TRAKT_API_VERSION, default_trakt_fuzzy_threshold}; #[derive(Debug, Default, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] @@ -45,22 +40,26 @@ impl FromStr for TraktContentType { #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(deny_unknown_fields)] pub struct TraktApiConfigDto { - #[serde(default)] - pub key: String, + #[serde(default, alias = "key")] + pub api_key: String, #[serde(default)] pub version: String, #[serde(default)] pub url: String, + #[serde(default)] + pub user_agent: String, } impl TraktApiConfigDto { pub fn prepare(&mut self) { - let key = self.key.trim(); - self.key = String::from(if key.is_empty() { TRAKT_API_KEY } else { key }); + let key = self.api_key.trim(); + self.api_key = String::from(if key.is_empty() { TRAKT_API_KEY } else { key }); let version = self.version.trim(); self.version = String::from(if version.is_empty() { TRAKT_API_VERSION } else { version }); let url = self.url.trim(); self.url = String::from(if url.is_empty() { TRAKT_API_URL } else { url }); + let user_agent = self.user_agent.trim(); + self.user_agent = String::from(if user_agent.is_empty() { DEFAULT_USER_AGENT } else { user_agent }); } } @@ -72,7 +71,7 @@ pub struct TraktListConfigDto { pub list_slug: String, pub category_name: String, pub content_type: TraktContentType, - #[serde(default = "default_fuzzy_threshold")] + #[serde(default = "default_trakt_fuzzy_threshold")] pub fuzzy_match_threshold: u8, // Percentage (0-100) } @@ -83,7 +82,7 @@ impl Default for TraktListConfigDto { list_slug: String::new(), category_name: String::new(), content_type: TraktContentType::default(), - fuzzy_match_threshold: default_fuzzy_threshold(), + fuzzy_match_threshold: default_trakt_fuzzy_threshold(), } } } diff --git a/shared/src/utils/default_utils.rs b/shared/src/utils/default_utils.rs index 930a658a8..1e131dad3 100644 --- a/shared/src/utils/default_utils.rs +++ b/shared/src/utils/default_utils.rs @@ -1,4 +1,4 @@ -use crate::model::{ConfigTargetOptions, LibraryMetadataFormat}; +use crate::model::{ConfigTargetOptions, LibraryMetadataFormat, ProcessingOrder}; pub const fn is_zero_u16(v: &u16) -> bool { *v == 0 } pub const fn is_true(v: &bool) -> bool { *v } @@ -133,6 +133,9 @@ pub fn is_config_target_options_empty(v: &Option) -> bool { v.as_ref().is_none_or(|c| c.is_empty() ) } +pub fn is_default_processing_order(p: &ProcessingOrder) -> bool { + *p == ProcessingOrder::default() +} ////////////////////////////// // HDHomerun Device Defaults @@ -158,3 +161,26 @@ pub fn is_default_firmware_name(value: &String) -> bool { value == DEFAULT_FIRMW pub fn is_default_firmware_version(value: &String) -> bool { value == DEFAULT_FIRMWARE_VERSION } pub fn is_default_device_type(value: &String) -> bool { value == DEFAULT_DEVICE_TYPE } pub fn is_default_device_udn(value: &String) -> bool { value == DEFAULT_DEVICE_UDN } + +////////////////////////// +// trakt +//////////////////////////// +pub const TRAKT_API_KEY: &str = "0183a05ad97098d87287fe46da4ae286f434f32e8e951caad4cc147c947d79a3"; +pub const TRAKT_API_VERSION: &str = "2"; +pub const TRAKT_API_URL: &str = "https://api.trakt.tv"; + +pub fn default_trakt_api_key() -> String { + String::from(TRAKT_API_KEY) +} + +pub fn default_trakt_api_version() -> String { + String::from(TRAKT_API_VERSION) +} + +pub fn default_trakt_api_url() -> String { + String::from(TRAKT_API_URL) +} + +pub fn default_trakt_fuzzy_threshold() -> u8 { + 80 +} diff --git a/shared/src/utils/string_utils.rs b/shared/src/utils/string_utils.rs index 4b338f24b..86244c319 100644 --- a/shared/src/utils/string_utils.rs +++ b/shared/src/utils/string_utils.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use deunicode::deunicode_with_tofu_cow; pub trait Capitalize { fn capitalize(&self) -> String; @@ -127,6 +128,10 @@ pub fn humanize_snake_case(s: &str) -> String { result } +pub fn deunicode_string(s: &str) -> Cow<'_, str> { + deunicode_with_tofu_cow(s, "[?]") +} + pub fn longest<'a>(a: &'a str, b: &'a str) -> &'a str { if a.len() >= b.len() { a } else { b } }