mark watched and unwatched
This commit is contained in:
@@ -143,6 +143,43 @@ impl JellyfinClient {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn mark_played(&self, user_id: &str, item_id: &str) -> Result<(), String> {
|
||||
let base = self.base_url.trim_end_matches('/');
|
||||
let url = format!("{base}/Users/{user_id}/PlayedItems/{item_id}");
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.post(url)
|
||||
.header("X-Emby-Token", &self.token)
|
||||
.send()
|
||||
.map_err(|e| format!("mark_played request failed: {e}"))?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("mark_played failed: HTTP {}", resp.status()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mark_unplayed(&self, user_id: &str, item_id: &str) -> Result<(), String> {
|
||||
let base = self.base_url.trim_end_matches('/');
|
||||
let url = format!("{base}/Users/{user_id}/PlayedItems/{item_id}");
|
||||
|
||||
let resp = self
|
||||
.client
|
||||
.delete(url)
|
||||
.header("X-Emby-Token", &self.token)
|
||||
.send()
|
||||
.map_err(|e| format!("mark_unplayed request failed: {e}"))?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(format!("mark_unplayed failed: HTTP {}", resp.status()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn list_views(&self, user_id: &str) -> Result<Vec<JfItem>, String> {
|
||||
let url = format!("{}/Users/{}/Views", self.base_url, user_id);
|
||||
|
||||
@@ -416,19 +453,19 @@ pub struct JfStudio {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[derive(Deserialize, Clone, Debug, Default)]
|
||||
pub struct JfUserData {
|
||||
#[serde(rename = "Played")]
|
||||
pub played: Option<bool>,
|
||||
|
||||
#[serde(rename = "PlaybackPositionTicks")]
|
||||
pub playback_position_ticks: Option<i64>,
|
||||
|
||||
#[serde(rename = "PlayedPercentage")]
|
||||
pub played_percentage: Option<f64>,
|
||||
|
||||
#[serde(rename = "PlaybackPositionTicks")]
|
||||
pub playback_position_ticks: Option<i64>,
|
||||
|
||||
#[serde(rename = "UnplayedItemCount")]
|
||||
pub unplayed_item_count: Option<i64>,
|
||||
pub unplayed_item_count: Option<i32>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
70
src/main.rs
70
src/main.rs
@@ -668,6 +668,74 @@ fn handle_key(app: &mut App, code: KeyCode) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark watched (w) / Unmark watched (W)
|
||||
if matches!(code, KeyCode::Char('w') | KeyCode::Char('W')) {
|
||||
let unmark = matches!(code, KeyCode::Char('W'));
|
||||
|
||||
let cfg = match &app.config {
|
||||
Some(c) => c.clone(),
|
||||
None => { app.status = "No config loaded".into(); return; }
|
||||
};
|
||||
|
||||
let user_id = match cfg.user_id.as_deref() {
|
||||
Some(u) => u.to_string(),
|
||||
None => { app.status = "Missing user_id (login again)".into(); return; }
|
||||
};
|
||||
|
||||
let (item_id, item_type) = match app.current_view() {
|
||||
View::Library { items, selected, .. } if !items.is_empty() => {
|
||||
let it = &items[*selected];
|
||||
(it.id.clone(), it.item_type.clone().unwrap_or_default())
|
||||
}
|
||||
_ => { app.status = "Not in a library view".into(); return; }
|
||||
};
|
||||
|
||||
// Only do this for playables (you can loosen this later if you want)
|
||||
if item_type != "Episode" && item_type != "Movie" {
|
||||
app.status = "Watched toggle only for Episodes/Movies (for now)".into();
|
||||
return;
|
||||
}
|
||||
|
||||
let client = match core::jellyfin::JellyfinClient::from_config(&cfg) {
|
||||
Ok(c) => c,
|
||||
Err(e) => { app.status = e; return; }
|
||||
};
|
||||
|
||||
let r = if unmark {
|
||||
client.mark_unplayed(&user_id, &item_id)
|
||||
} else {
|
||||
client.mark_played(&user_id, &item_id)
|
||||
};
|
||||
|
||||
match r {
|
||||
Ok(_) => {
|
||||
// Update local UI state so ✅ disappears/appears immediately
|
||||
if let Some(View::Library { items, selected, .. }) = app.view_stack.last_mut() {
|
||||
if let Some(it) = items.get_mut(*selected) {
|
||||
let ud = it.user_data.get_or_insert_with(core::jellyfin::JfUserData::default);
|
||||
|
||||
if unmark {
|
||||
ud.played = Some(false);
|
||||
ud.played_percentage = Some(0.0);
|
||||
ud.playback_position_ticks = Some(0);
|
||||
} else {
|
||||
ud.played = Some(true);
|
||||
ud.played_percentage = Some(100.0);
|
||||
ud.playback_position_ticks = Some(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.status = if unmark { "Marked unplayed".into() } else { "Marked played".into() };
|
||||
}
|
||||
Err(e) => {
|
||||
app.status = format!("Watched update failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// mpv controls (only when not typing into search/login)
|
||||
if app.search.is_none() && !matches!(app.current_view(), View::Login) {
|
||||
@@ -910,8 +978,6 @@ fn handle_key(app: &mut App, code: KeyCode) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
KeyCode::Char('r') => {
|
||||
app.autoplay = false;
|
||||
app.autoplay_next_index = None;
|
||||
|
||||
Reference in New Issue
Block a user