diff options
author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-21 10:49:23 +0200 |
---|---|---|
committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2024-08-21 11:28:43 +0200 |
commit | 1debeb77f7986de1b659dcfdc442de6415e1d9f5 (patch) | |
tree | 4df3e7c3f6a2d1ec116e4088c5ace7f143a8b05f /src/subscribe/mod.rs | |
download | yt-1debeb77f7986de1b659dcfdc442de6415e1d9f5.tar.gz yt-1debeb77f7986de1b659dcfdc442de6415e1d9f5.zip |
chore: Initial Commit
This repository was migrated out of my nixos-config.
Diffstat (limited to 'src/subscribe/mod.rs')
-rw-r--r-- | src/subscribe/mod.rs | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/src/subscribe/mod.rs b/src/subscribe/mod.rs new file mode 100644 index 0000000..1796fb4 --- /dev/null +++ b/src/subscribe/mod.rs @@ -0,0 +1,181 @@ +// yt - A fully featured command line YouTube client +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Yt. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +use std::str::FromStr; + +use anyhow::{bail, Context, Result}; +use futures::FutureExt; +use log::warn; +use serde_json::{json, Value}; +use tokio::io::{AsyncBufRead, AsyncBufReadExt}; +use url::Url; +use yt_dlp::wrapper::info_json::InfoType; + +use crate::{ + app::App, + storage::subscriptions::{ + add_subscription, check_url, get_subscriptions, remove_all_subscriptions, + remove_subscription, Subscription, + }, +}; + +pub async fn unsubscribe(app: &App, name: String) -> Result<()> { + let present_subscriptions = get_subscriptions(&app).await?; + + if let Some(subscription) = present_subscriptions.0.get(&name) { + remove_subscription(&app, subscription).await?; + } else { + bail!("Couldn't find subscription: '{}'", &name); + } + + Ok(()) +} + +pub async fn import<W: AsyncBufRead + AsyncBufReadExt + Unpin>( + app: &App, + reader: W, + force: bool, +) -> Result<()> { + if force { + remove_all_subscriptions(&app).await?; + } + + let mut lines = reader.lines(); + while let Some(line) = lines.next_line().await? { + let url = + Url::from_str(&line).with_context(|| format!("Failed to parse '{}' as url", line))?; + match subscribe(app, None, url) + .await + .with_context(|| format!("Failed to subscribe to: '{}'", line)) + { + Ok(_) => (), + Err(err) => eprintln!( + "Error while subscribing to '{}': '{}'", + line, + err.source().expect("Should have a source").to_string() + ), + } + } + + Ok(()) +} + +pub async fn subscribe(app: &App, name: Option<String>, url: Url) -> Result<()> { + if !(url.as_str().ends_with("videos") + || url.as_str().ends_with("streams") + || url.as_str().ends_with("shorts")) + && url.as_str().contains("youtube.com") + { + warn!("Your youtbe url does not seem like it actually tracks a channels playlist (videos, streams, shorts). Adding subscriptions for each of them..."); + + let url = Url::parse(&(url.as_str().to_owned() + "/")) + .expect("This was an url, it should stay one"); + + if let Some(name) = name { + let out: Result<()> = async move { + actual_subscribe( + &app, + Some(name.clone() + " {Videos}"), + url.join("videos/").expect("Works"), + ) + .await + .with_context(|| { + format!("Failed to subscribe to '{}'", name.clone() + " {Videos}") + })?; + + actual_subscribe( + &app, + Some(name.clone() + " {Streams}"), + url.join("streams/").expect("Works"), + ) + .await + .with_context(|| { + format!("Failed to subscribe to '{}'", name.clone() + " {Streams}") + })?; + + actual_subscribe( + &app, + Some(name.clone() + " {Shorts}"), + url.join("shorts/").expect("Works"), + ) + .await + .with_context(|| format!("Failed to subscribe to '{}'", name + " {Shorts}"))?; + + Ok(()) + } + .boxed() + .await; + + out? + } else { + actual_subscribe(&app, None, url.join("videos/").expect("Works")) + .await + .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Videos}"))?; + + actual_subscribe(&app, None, url.join("streams/").expect("Works")) + .await + .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Streams}"))?; + + actual_subscribe(&app, None, url.join("shorts/").expect("Works")) + .await + .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Shorts}"))?; + } + } else { + actual_subscribe(&app, name, url).await?; + } + + Ok(()) +} + +async fn actual_subscribe(app: &App, name: Option<String>, url: Url) -> Result<()> { + let name = if let Some(name) = name { + if !check_url(&url).await? { + bail!("The url ('{}') does not represent a playlist!", &url) + }; + + name + } else { + let yt_opts = match json!( { + "playliststart": 1, + "playlistend": 10, + "noplaylist": false, + "extract_flat": "in_playlist", + }) { + Value::Object(map) => map, + _ => unreachable!("This is hardcoded"), + }; + + let info = yt_dlp::extract_info(&yt_opts, &url, false, false).await?; + + if info._type == Some(InfoType::Playlist) { + info.title.expect("This should be some for a playlist") + } else { + bail!("The url ('{}') does not represent a playlist!", &url) + } + }; + + let present_subscriptions = get_subscriptions(&app).await?; + + if let Some(subs) = present_subscriptions.0.get(&name) { + bail!( + "The subscription '{}' could not be added, \ + as another one with the same name ('{}') already exists. It links to the Url: '{}'", + name, + name, + subs.url + ); + } + + let sub = Subscription { name, url }; + + add_subscription(&app, &sub).await?; + + Ok(()) +} |