feat(scraper): add scraper CLI

This commit is contained in:
Matthew Esposito 2024-10-21 20:54:05 -04:00
parent 49ef59e000
commit f3d2f0cc59
4 changed files with 114 additions and 3 deletions

73
src/scraper/main.rs Normal file
View file

@ -0,0 +1,73 @@
use std::{fmt::Display, io::Write};
use clap::{Parser, ValueEnum};
use redlib::utils::Post;
#[derive(Parser)]
#[command(name = "my_cli")]
#[command(about = "A simple CLI example", long_about = None)]
struct Cli {
#[arg(short = 's', long = "sub")]
sub: String,
#[arg(short = 'c', long = "count")]
count: usize,
#[arg(long = "sort")]
sort: SortOrder,
#[arg(short = 'f', long = "format", value_enum)]
format: Format,
}
#[derive(Debug, Clone, ValueEnum)]
enum SortOrder {
Hot,
Rising,
New,
Top,
Controversial,
}
impl Display for SortOrder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SortOrder::Hot => write!(f, "hot"),
SortOrder::Rising => write!(f, "rising"),
SortOrder::New => write!(f, "new"),
SortOrder::Top => write!(f, "top"),
SortOrder::Controversial => write!(f, "controversial"),
}
}
}
#[derive(Debug, Clone, ValueEnum)]
enum Format {
Json,
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let (sub, final_count, sort, format) = (cli.sub, cli.count, cli.sort, cli.format);
let initial = format!("/r/{sub}/{sort}.json?&raw_json=1");
let (mut posts, mut after) = Post::fetch(&initial, false).await.unwrap();
while posts.len() < final_count {
print!("\r");
let path = format!("/r/{sub}/{sort}.json?sort={sort}&t=&after={after}&raw_json=1");
let (new_posts, new_after) = Post::fetch(&path, false).await.unwrap();
posts.extend(new_posts);
after = new_after;
// Print number of posts fetched
print!("Fetched {} posts", posts.len());
std::io::stdout().flush().unwrap();
}
match format {
Format::Json => {
let filename: String = format!("{sub}.json");
let json = serde_json::to_string(&posts).unwrap();
std::fs::write(filename, json).unwrap();
}
}
}

View file

@ -11,6 +11,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use rinja::Template;
use rust_embed::RustEmbed;
use serde::Serialize;
use serde_json::Value;
use serde_json_path::{JsonPath, JsonPathExt};
use std::collections::{HashMap, HashSet};
@ -46,6 +47,7 @@ pub enum ResourceType {
}
// Post flair with content, background color and foreground color
#[derive(Serialize)]
pub struct Flair {
pub flair_parts: Vec<FlairPart>,
pub text: String,
@ -54,7 +56,7 @@ pub struct Flair {
}
// Part of flair, either emoji or text
#[derive(Clone)]
#[derive(Clone, Serialize)]
pub struct FlairPart {
pub flair_part_type: String,
pub value: String,
@ -96,12 +98,14 @@ impl FlairPart {
}
}
#[derive(Serialize)]
pub struct Author {
pub name: String,
pub flair: Flair,
pub distinguished: String,
}
#[derive(Serialize)]
pub struct Poll {
pub poll_options: Vec<PollOption>,
pub voting_end_timestamp: (String, String),
@ -129,6 +133,7 @@ impl Poll {
}
}
#[derive(Serialize)]
pub struct PollOption {
pub id: u64,
pub text: String,
@ -158,13 +163,14 @@ impl PollOption {
}
// Post flags with nsfw and stickied
#[derive(Serialize)]
pub struct Flags {
pub spoiler: bool,
pub nsfw: bool,
pub stickied: bool,
}
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub struct Media {
pub url: String,
pub alt_url: String,
@ -264,6 +270,7 @@ impl Media {
}
}
#[derive(Serialize)]
pub struct GalleryMedia {
pub url: String,
pub width: i64,
@ -304,6 +311,7 @@ impl GalleryMedia {
}
// Post containing content, metadata and media
#[derive(Serialize)]
pub struct Post {
pub id: String,
pub title: String,
@ -470,7 +478,7 @@ pub struct Comment {
pub prefs: Preferences,
}
#[derive(Default, Clone)]
#[derive(Default, Clone, Serialize)]
pub struct Award {
pub name: String,
pub icon_url: String,
@ -484,6 +492,7 @@ impl std::fmt::Display for Award {
}
}
#[derive(Serialize)]
pub struct Awards(pub Vec<Award>);
impl std::ops::Deref for Awards {