DEV Community

Harikrishnan Menon
Harikrishnan Menon

Posted on

Lets build a RedditBot to curate playlist links - III

We've reached the final chapter. The spoils are just ahead of us, lets go grab em.

Obtaining Credentials

We need to generate a different set of credentials to talk with YouTube. Lets go do that step by step..

  1. Logon to Google Developer Console

  2. Click Enable Apis and Service
    Enable API

  3. Search for YouTube Data API v3
    YouTube Data API v3

  4. Click the Enable button to enable the API for our account
    Enable YouTube API

  5. Click the Create Credentials button to start creating credentials for us to use.
    Create Credentials

  6. We need to first describe what kind of credentials have to be generated. Don't worry, just follow the screenshot.
    Generate Credentials

  7. We're Done!
    Done

Handling JSON better

In the last part we used a dynamic JSON to to retrieve the playlist url from Reddit. This time to interact with the YouTube API we wont do that, instead we will one up ourselves and de-serialize the JSON into structs that we define in rust.

We use serde to do handle the heavy lifting of JSON de-serialization

The response of the YouTube API is a bit more well defined than the Reddit API.

{
  "kind": "youtube#playlistItemListResponse",
  "etag": "Fij-lGuELswW5Y6HXEJsEVAZ6Xg",
  "nextPageToken": "CAUQAA",
  "items": [
    {
      "kind": "youtube#playlistItem",
      "etag": "KC_3PIeEyspbfuA_AplI4dv2ITA",
      "id": "UExmM3U4TmhvRWlraFRDNXJhZEdybW1xZGtPSy14TURvWi45ODRDNTg0QjA4NkFBNkQy",
      "snippet": {
        "publishedAt": "2020-08-05T19:31:06Z",
        "channelId": "UC9X86dyEwpbCnpC18qjt33Q",
        "title": "Rusty Days 2020 - Hackathon Submissions",
        "description": "Rules ► https://rusty-days.org/hackathon/\n\nTeams ►\narrugginiti https://github.com/Rust-Wroclaw/rd-hack-arrugginiti\nBox-Team https://github.com/Rust-Wroclaw/rd-hack-Box-Team\nBrighter3D https://github.com/Rust-Wroclaw/rd-hack-Brighter3D\nhexyoungs https://github.com/Rust-Wroclaw/rd-hack-hexyoungs\nLastMinute https://github.com/Rust-Wroclaw/rd-hack-LastMinute\nplanters https://github.com/Rust-Wroclaw/rd-hack-planters\n\nFollow ►\nFacebook: https://rusty-days.org/facebook\nTwitch: https://rusty-days.org/twitch\nTwitter: https://rusty-days.org/twitter",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/QaCvUKrxNLI/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/QaCvUKrxNLI/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/QaCvUKrxNLI/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/QaCvUKrxNLI/sddefault.jpg",
            "width": 640,
            "height": 480
          }
        },
        "channelTitle": "Rust Wrocław",
        "playlistId": "PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ",
        "position": 0,
        "resourceId": {
          "kind": "youtube#video",
          "videoId": "QaCvUKrxNLI"
        }
      }
    },
    {
      "kind": "youtube#playlistItem",
      "etag": "EgGMmoAJ81l2BJFspcg1idaKy-8",
      "id": "UExmM3U4TmhvRWlraFRDNXJhZEdybW1xZGtPSy14TURvWi5EMEEwRUY5M0RDRTU3NDJC",
      "snippet": {
        "publishedAt": "2020-08-01T12:17:09Z",
        "channelId": "UC9X86dyEwpbCnpC18qjt33Q",
        "title": "Rusty Days 2020 - Tim McNamara: How 10 open source projects manage unsafe code",
        "description": "Agenda ► https://rusty-days.org/agenda\nSlides ►https://rusty-days.org/assets/slides/08-how-10-open-source-projects-manage-unsafe-code.pdf\nPlaylist with all talks ► https://www.youtube.com/playlist?list=PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ\n\nFollow ►\nFacebook: https://rusty-days.org/facebook\nTwitch: https://rusty-days.org/twitch\nTwitter: https://rusty-days.org/twitter\n\nThis video ►\nIs it safe to use unsafe? Learn why some projects need unsafe code and how projects manage its risks.\n\nThis talk will briefly discuss what the unsafe keyword enables and what its risks are. The bulk of time will be spent discussing how projects manage those risks. It finishes by providing recommendations based on that analysis.\n\nProjects surveyed include:\n* Servo (Mozilla)\n* Fuchsia OS (Google)\n* fast_rsync (Dropbox)\n* winrt-rs (Microsoft)\n* Firecracker (AWS)\n* Linkerd2",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/9M0NQI5Cp2c/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/9M0NQI5Cp2c/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/9M0NQI5Cp2c/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/9M0NQI5Cp2c/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/9M0NQI5Cp2c/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "Rust Wrocław",
        "playlistId": "PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ",
        "position": 1,
        "resourceId": {
          "kind": "youtube#video",
          "videoId": "9M0NQI5Cp2c"
        }
      }
    },
    {
      "kind": "youtube#playlistItem",
      "etag": "3gm-0cEUcjfm1v1vgh_3EjS6mJg",
      "id": "UExmM3U4TmhvRWlraFRDNXJhZEdybW1xZGtPSy14TURvWi40NzZCMERDMjVEN0RFRThB",
      "snippet": {
        "publishedAt": "2020-08-01T12:16:07Z",
        "channelId": "UC9X86dyEwpbCnpC18qjt33Q",
        "title": "Rusty Days 2020 - Luca Palmieri: Are we observable yet?",
        "description": "Agenda ► https://rusty-days.org/agenda\nSlides ►https://rusty-days.org/assets/slides/07-are-we-observable-yet.pdf\nPlaylist with all talks ► https://www.youtube.com/playlist?list=PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ\n\nFollow ►\nFacebook: https://rusty-days.org/facebook\nTwitch: https://rusty-days.org/twitch\nTwitter: https://rusty-days.org/twitter\n\nThis video ►\nIs Rust ready for mainstream usage in backend development?\n\nThere is a lot of buzz around web frameworks while many other (critical!) Day 2 concerns do not get nearly as much attention.\n\nWe will discuss observability: do the tools currently available in the Rust ecosystem cover most of your telemetry needs?\n\nI will walk you through our journey here at TrueLayer when we built our first production backend system in Rust, Donate Direct.\n\nWe will be touching on the state of Rust tooling for logging, metrics and distributed tracing.",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/HtKnLiFwHJM/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/HtKnLiFwHJM/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/HtKnLiFwHJM/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/HtKnLiFwHJM/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/HtKnLiFwHJM/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "Rust Wrocław",
        "playlistId": "PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ",
        "position": 2,
        "resourceId": {
          "kind": "youtube#video",
          "videoId": "HtKnLiFwHJM"
        }
      }
    },
    {
      "kind": "youtube#playlistItem",
      "etag": "2C9sX2xuTowOxjn0m95AH53JiA4",
      "id": "UExmM3U4TmhvRWlraFRDNXJhZEdybW1xZGtPSy14TURvWi5GNjNDRDREMDQxOThCMDQ2",
      "snippet": {
        "publishedAt": "2020-07-31T11:28:34Z",
        "channelId": "UC9X86dyEwpbCnpC18qjt33Q",
        "title": "Rusty Days 2020 - Jan-Erik Rediger: Leveraging Rust to build cross-platform mobile libraries",
        "description": "Agenda ► https://rusty-days.org/agenda\nSlides ►https://rusty-days.org/assets/slides/06-cross-platform-mobile-libraries.pdf\nPlaylist with all talks ► https://www.youtube.com/playlist?list=PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ\n\nFollow ►\nFacebook: https://rusty-days.org/facebook\nTwitch: https://rusty-days.org/twitch\nTwitter: https://rusty-days.org/twitter\n\n\nThis video ►\nAt Mozilla, Firefox is not the only product we ship. Many others — including a variety of smartphone applications, and certainly not just web browsers — are built by various teams across the organization. These applications are composed of a multitude of libraries which, when possible, are reused across platforms.\n\nIn the past year we used Rust to rebuild one of these libraries: the library powering the telemetry in our mobile applications is now integrated into Android and iOS applications and will soon be powering our Desktop platforms as well.\n\nThis talk will showcase how this small team managed to create a cross-platform Rust library, and ship it to a bunch of platforms all at once.",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/j5rczOF7pzg/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/j5rczOF7pzg/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/j5rczOF7pzg/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/j5rczOF7pzg/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/j5rczOF7pzg/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "Rust Wrocław",
        "playlistId": "PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ",
        "position": 3,
        "resourceId": {
          "kind": "youtube#video",
          "videoId": "j5rczOF7pzg"
        }
      }
    },
    {
      "kind": "youtube#playlistItem",
      "etag": "pB_4gb7ai1HOgVLz8Jx9SJB1P_g",
      "id": "UExmM3U4TmhvRWlraFRDNXJhZEdybW1xZGtPSy14TURvWi45NDk1REZENzhEMzU5MDQz",
      "snippet": {
        "publishedAt": "2020-07-31T09:06:09Z",
        "channelId": "UC9X86dyEwpbCnpC18qjt33Q",
        "title": "Rusty Days 2020 -  Nell Shamrell - Harrington: The Rust Borrow Checker - A Deep Dive",
        "description": "Agenda ► https://rusty-days.org/agenda\nSlides ►https://rusty-days.org/assets/slides/05-the-rust-borrow-checker.pdf\nPlaylist with all talks ► https://www.youtube.com/playlist?list=PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ\n\nFollow ►\nFacebook: https://rusty-days.org/facebook\nTwitch: https://rusty-days.org/twitch\nTwitter: https://rusty-days.org/twitter\n\nThis video ►\n\nThe Rust compiler's borrow checker is critical for ensuring safe Rust code. Even more critical, however, is how the borrow checker provides useful, automated guidance on how to write safe code when the check fails. \n\nEarly in your Rust journey, it may feel like you are fighting the borrow checker. Come to this talk to learn how you can transition from fighting the borrow checker to using its guidance to write safer and more powerful code at any experience level. Walk away not only understanding the what and the how of the borrow checker - but why it works the way it does - and why it is so critical to both the technical functionality and philosophy of Rust.",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/knhpe5IUnlE/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/knhpe5IUnlE/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/knhpe5IUnlE/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/knhpe5IUnlE/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/knhpe5IUnlE/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "Rust Wrocław",
        "playlistId": "PLf3u8NhoEikhTC5radGrmmqdkOK-xMDoZ",
        "position": 4,
        "resourceId": {
          "kind": "youtube#video",
          "videoId": "knhpe5IUnlE"
        }
      }
    }
  ],
  "pageInfo": {
    "totalResults": 9,
    "resultsPerPage": 5
  }
}

This JSON response can be constructed from simple structs that we can define.
The #[derive(Deserialize)] helps serde understand that it can use this struct to deserialize json into by matching the fields of the struct to those of that in the JSON body.

serde is an amazing library and a bit too vast to explain in this post.

# main.rs

#[derive(Debug, Deserialize)]
struct Snippet {
    title: String,
    position: i32,
}
#[derive(Debug, Deserialize)]
struct Item {
    kind: String,
    snippet: Snippet,
}
#[derive(Debug, Deserialize)]
struct YoutubeResponse {
    items: Vec<Item>,
}

Now that we have defined our struct lets go ahead and call the YouTube API.

# main.rs

let mut reply: String =
    "Sorry couldn't find the YouTube Link! :(".to_string();

if playlist_id.is_some() {
    // Generate api url
    let url = format!("https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId={}&key={}&maxResults={}",playlist_id.unwrap(), YT_KEY, YT_MAX_RESULT);
                        // Fire the API
    let playlist_items = match reqwest::get(&url).await {
                        // Try to convert the response to our struct
        Ok(response) => match response.json::<YoutubeResponse>().await {
                               // Return the array of Item            Ok(yt_response) => Some(yt_response.items),
            Err(e) => {
                error!(
                    "Couldn't parse playlist response for comment {} reason : {}",
                        &message.data.name, e
                            );
                None
            }
        },
        Err(e) => {
            error!(
                "Couldn't fetch YouTube data for comment {} reason : {}",
                &message.data.name, e
            );
            None
        }
    };
    //Loop over each item and then create the message.
    if playlist_items.is_some() {
        let items = playlist_items.unwrap();
        if items.len() > 0 {
            reply = "Playlist Items: \n".to_string();
            for item in items {
                reply.push_str(
                    format!("\n {} \n", item.snippet.title).as_str(),
                )
            }
        }
    }
}

We first define reply with the string that we want to respond with if we fail to identify the playlist id.
If we have a playlistID we then call YouTube API with the key we generated earlier. We then extract the items of the playlist generated our reply text. The code that we have written in the previous part already handles replying to the message.
Lets try it out!

(base) DeltaManiac @ ~/git/rust/vyom
└─ $ cargo run
   Compiling vyom v0.1.0
    Finished dev [unoptimized + debuginfo] target(s) in 5.79s
     Running `target/debug/vyom`
[2020-08-11T18:46:46Z INFO  vyom] Replied to t1_g14nya9
[2020-08-11T18:46:46Z INFO  vyom] Marked t1_g14nya9 as read

And on Reddit it looks just as beautiful!
Final

Oh BTW the code can be found on the part-III branch here

Fin

Thanks for joining along while we built our first bot ❤️.
If this journey has taught you something, feel free to give a shout out!

Top comments (0)