aboutsummaryrefslogtreecommitdiffstats
path: root/src/subscribe
diff options
context:
space:
mode:
Diffstat (limited to 'src/subscribe')
-rw-r--r--src/subscribe/mod.rs181
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(())
+}