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..
Logon to Google Developer Console
Click the
Create Credentials
button to start creating credentials for us to use.
We need to first describe what kind of credentials have to be generated. Don't worry, just follow the screenshot.
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!
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)