about summary refs log tree commit diff stats
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(())
+}