Merge pull request #3 from Trivernis/develop

Develop
main
Julius Riegel 4 years ago committed by GitHub
commit 875fda42ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,42 @@
name: Build and Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main, develop ]
env:
CARGO_TERM_COLOR: always
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: rustup
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Cache build data
uses: actions/cache@v2
with:
path: |
target
~/.cargo/
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Fast Fail Check
run: cargo check --verbose
- name: Build
run: cargo build --verbose --all-features
- name: Run tests
run: cargo test --verbose --all-features

@ -1,10 +1,11 @@
[package] [package]
name = "youtube-metadata" name = "youtube-metadata"
version = "0.1.0" version = "0.2.0"
authors = ["trivernis <trivernis@protonmail.com>"] authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018" edition = "2018"
description = "YouTube video metadata fetcher" description = "YouTube video metadata fetcher"
readme = "README.md" readme = "README.md"
repository = "https://github.com/Trivernis/youtube-metadata-rs"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -12,8 +13,7 @@ license = "MIT"
[dependencies] [dependencies]
reqwest = "0.11.3" reqwest = "0.11.3"
scraper = "0.12.0" scraper = "0.12.0"
thiserror = "1.0.24"
lazy_static = "1.4.0" lazy_static = "1.4.0"
[dev-dependencies] [dev-dependencies]
tokio = {version = "1.5.0", features = ["macros", "rt-multi-thread"]} tokio = { version = "1.5.0", features = ["macros", "rt-multi-thread"] }

@ -1,10 +1,12 @@
use crate::error::YoutubeResult; use crate::error::Result;
use crate::parsing::video_information::parse_video_information; use crate::parsing::video_information::parse_video_information;
use crate::types::VideoInformation; use crate::types::VideoInformation;
/// Returns information about a video /// Returns information about a video
/// ``` /// ```
/// use youtube_metadata::get_video_information; /// use youtube_metadata::get_video_information;
/// # #[tokio::test]
/// # async fn doctest() {
/// let information = get_video_information("https://www.youtube.com/watch?v=dQw4w9WgXcQ") /// let information = get_video_information("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
/// .await /// .await
/// .unwrap(); /// .unwrap();
@ -22,8 +24,9 @@ use crate::types::VideoInformation;
/// information.thumbnail, /// information.thumbnail,
/// Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg".to_string()) /// Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg".to_string())
/// ); /// );
/// # }
/// ``` /// ```
pub async fn get_video_information(url: &str) -> YoutubeResult<VideoInformation> { pub async fn get_video_information(url: &str) -> Result<VideoInformation> {
let response = reqwest::get(url).await?; let response = reqwest::get(url).await?;
let response_text = response.text().await?; let response_text = response.text().await?;

@ -1,18 +1,64 @@
use thiserror::Error; use std::{
error::Error as StdError,
fmt::{Display, Formatter, Result as FmtResult},
};
pub type YoutubeResult<T> = Result<T, YoutubeError>; use reqwest::Error as ReqwestError;
#[derive(Debug, Error)] pub type Result<T> = std::result::Result<T, Error>;
pub enum YoutubeError {
#[error(transparent)]
Reqwest(#[from] reqwest::Error),
#[error("Parse Error: {0}")] #[derive(Debug)]
ParseError(String), pub enum Error {
Reqwest(ReqwestError),
Parse(Parsing),
} }
impl From<&str> for YoutubeError { impl Display for Error {
fn from(s: &str) -> Self { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
Self::ParseError(s.to_string()) use Error::*;
match self {
Reqwest(e) => e.fmt(f),
Parse(_) => write!(f, "parse error"),
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
use Error::*;
match self {
Reqwest(e) => e.source(),
Parse(e) => Some(e),
}
}
}
impl From<Parsing> for Error {
fn from(s: Parsing) -> Self {
Self::Parse(s)
}
}
impl From<ReqwestError> for Error {
fn from(e: ReqwestError) -> Self {
Self::Reqwest(e)
} }
} }
#[derive(Debug)]
pub enum Parsing {
MissingElement(String),
MissingAttribute(String),
}
impl Display for Parsing {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
use Parsing::*;
match self {
MissingAttribute(s) => write!(f, "missing attribute: {}", s),
MissingElement(s) => write!(f, "missing element: {}", s),
}
}
}
impl StdError for Parsing {}

@ -1,17 +1,14 @@
use crate::error::{YoutubeError, YoutubeResult}; use crate::error::{Parsing, Result};
use scraper::{ElementRef, Html, Selector}; use scraper::{ElementRef, Html, Selector};
pub mod video_information; pub mod video_information;
/// Tries selecting one element or fails if the element can't be found /// Tries selecting one element or fails if the element can't be found
fn try_select_one<'a>(document: &'a Html, selector: &Selector) -> YoutubeResult<ElementRef<'a>> { fn try_select_one<'a>(document: &'a Html, selector: &Selector) -> Result<ElementRef<'a>> {
document document
.select(selector) .select(selector)
.next() .next()
.ok_or(YoutubeError::ParseError(format!( .ok_or_else(|| Parsing::MissingElement(format!("{:?}", selector)).into())
"Missing Element: {:?}",
selector
)))
} }
/// Tries to select a given attribute /// Tries to select a given attribute
@ -19,13 +16,10 @@ fn try_select_attribute<'a>(
document: &'a Html, document: &'a Html,
selector: &Selector, selector: &Selector,
attribute: &str, attribute: &str,
) -> YoutubeResult<&'a str> { ) -> Result<&'a str> {
let element = try_select_one(document, selector)?; let element = try_select_one(document, selector)?;
element element
.value() .value()
.attr(attribute) .attr(attribute)
.ok_or(YoutubeError::ParseError(format!( .ok_or_else(|| Parsing::MissingAttribute(attribute.to_string()).into())
"Missing attribute '{}'",
attribute
)))
} }

@ -1,9 +1,10 @@
use crate::error::YoutubeResult; use lazy_static::lazy_static;
use crate::parsing::try_select_attribute;
use crate::types::VideoInformation;
use scraper::{Html, Selector}; use scraper::{Html, Selector};
lazy_static::lazy_static! { use super::try_select_attribute;
use crate::{error::Result, types::VideoInformation};
lazy_static! {
static ref TITLE_SELECTOR: Selector = Selector::parse(r#"meta[property="og:title"]"#).unwrap(); static ref TITLE_SELECTOR: Selector = Selector::parse(r#"meta[property="og:title"]"#).unwrap();
static ref THUMBNAIL_SELECTOR: Selector = Selector::parse(r#"meta[property="og:image"]"#).unwrap(); static ref THUMBNAIL_SELECTOR: Selector = Selector::parse(r#"meta[property="og:image"]"#).unwrap();
static ref URL_SELECTOR: Selector = Selector::parse(r#"link[rel="canonical"]"#).unwrap(); static ref URL_SELECTOR: Selector = Selector::parse(r#"link[rel="canonical"]"#).unwrap();
@ -12,7 +13,7 @@ lazy_static::lazy_static! {
} }
/// Parses information about a video from the html /// Parses information about a video from the html
pub fn parse_video_information(html: &str) -> YoutubeResult<VideoInformation> { pub fn parse_video_information(html: &str) -> Result<VideoInformation> {
let document = Html::parse_document(html); let document = Html::parse_document(html);
let video_id = try_select_attribute(&document, &ID_SELECTOR, "content")?; let video_id = try_select_attribute(&document, &ID_SELECTOR, "content")?;

@ -0,0 +1 @@
mod endpoints;

@ -0,0 +1,10 @@
use crate::get_video_information;
#[tokio::test]
async fn invalid_url_is_err() {
assert!(
get_video_information("https://www.youtube.com/watch?v=FFFFFFFFFFF")
.await
.is_err()
);
}

@ -1,28 +0,0 @@
use crate::endpoints::get_video_information;
#[tokio::test]
async fn test_get_video_information() {
let information = get_video_information("https://www.youtube.com/watch?v=dQw4w9WgXcQ")
.await
.unwrap();
assert_eq!(information.id, "dQw4w9WgXcQ".to_string());
assert_eq!(
information.url,
"https://www.youtube.com/watch?v=dQw4w9WgXcQ".to_string()
);
assert_eq!(information.uploader, "RickAstleyVEVO".to_string());
assert_eq!(
information.title,
"Rick Astley - Never Gonna Give You Up (Video)".to_string()
);
assert_eq!(
information.thumbnail,
Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg".to_string())
);
assert!(
get_video_information("https://www.youtube.com/watch?v=FFFFFFFFFFF")
.await
.is_err()
);
}

@ -1,2 +0,0 @@
#[cfg(test)]
mod endpoints_test;
Loading…
Cancel
Save