Skip to main content

ytmapi_rs/query/
history.rs

1use super::{GetMethod, GetQuery, PostMethod, PostQuery, Query};
2use crate::auth::LoggedIn;
3use crate::common::{ApiOutcome, FeedbackTokenRemoveFromHistory, SongTrackingUrl, YoutubeID};
4use crate::parse::HistoryPeriod;
5use rand::Rng;
6use serde_json::json;
7use std::borrow::Cow;
8
9pub struct GetHistoryQuery;
10pub struct RemoveHistoryItemsQuery<'a> {
11    feedback_tokens: Vec<FeedbackTokenRemoveFromHistory<'a>>,
12}
13pub struct AddHistoryItemQuery<'a> {
14    song_url: SongTrackingUrl<'a>,
15}
16
17impl<'a> RemoveHistoryItemsQuery<'a> {
18    pub fn new(
19        feedback_tokens: impl IntoIterator<Item = FeedbackTokenRemoveFromHistory<'a>>,
20    ) -> Self {
21        Self {
22            feedback_tokens: feedback_tokens.into_iter().collect(),
23        }
24    }
25}
26
27impl<'a> AddHistoryItemQuery<'a> {
28    pub fn new(song_url: SongTrackingUrl<'a>) -> Self {
29        Self { song_url }
30    }
31}
32
33// TODO: Return played and feedback_token component.
34impl<A: LoggedIn> Query<A> for GetHistoryQuery {
35    type Output = Vec<HistoryPeriod>;
36    type Method = PostMethod;
37}
38impl PostQuery for GetHistoryQuery {
39    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
40        serde_json::Map::from_iter([("browseId".to_string(), json!("FEmusic_history"))])
41    }
42    fn params(&self) -> std::vec::Vec<(&str, std::borrow::Cow<'_, str>)> {
43        vec![]
44    }
45    fn path(&self) -> &str {
46        "browse"
47    }
48}
49
50// NOTE: Does not work on brand accounts
51impl<A: LoggedIn> Query<A> for RemoveHistoryItemsQuery<'_> {
52    type Output = Vec<ApiOutcome>;
53    type Method = PostMethod;
54}
55impl PostQuery for RemoveHistoryItemsQuery<'_> {
56    fn header(&self) -> serde_json::Map<String, serde_json::Value> {
57        serde_json::Map::from_iter([("feedbackTokens".to_string(), json!(self.feedback_tokens))])
58    }
59    fn params(&self) -> std::vec::Vec<(&str, std::borrow::Cow<'_, str>)> {
60        vec![]
61    }
62    fn path(&self) -> &str {
63        "feedback"
64    }
65}
66
67impl<A: LoggedIn> Query<A> for AddHistoryItemQuery<'_> {
68    type Output = ();
69    type Method = GetMethod;
70}
71
72impl GetQuery for AddHistoryItemQuery<'_> {
73    fn url(&self) -> &str {
74        self.song_url.get_raw()
75    }
76    fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
77        // Original implementation by sigma67
78        // https://github.com/sigma67/ytmusicapi/blob/a15d90c4f356a530c6b2596277a9d70c0b117a0c/ytmusicapi/mixins/library.py#L310
79        let possible_chars: Vec<char> =
80            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
81                .chars()
82                .collect();
83        let random_cpn: String = rand::rng()
84            .sample_iter(
85                rand::distr::slice::Choose::new(&possible_chars)
86                    .expect("Provided a hard-coded non-empty slice"),
87            )
88            .take(16)
89            .collect();
90        vec![
91            ("ver", "2".into()),
92            ("c", "WEB_REMIX".into()),
93            ("cpn", random_cpn.into()),
94        ]
95    }
96}