mirror of
https://github.com/YGGverse/pulsarss.git
synced 2026-03-31 17:15:29 +00:00
initial commit
This commit is contained in:
parent
6f41fdc38d
commit
4280f9cb96
6 changed files with 167 additions and 0 deletions
27
.github/workflows/build.yml
vendored
Normal file
27
.github/workflows/build.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTFLAGS: -Dwarnings
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Run rustfmt
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
|
- name: Run clippy
|
||||||
|
run: cargo clippy --all-targets
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --verbose
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --verbose
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
/public
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "pulsarss"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
description = "RSS Aggregator for Gemini Protocol "
|
||||||
|
keywords = ["gemini", "gemini-protocol", "gemtext", "rss", "aggregator"]
|
||||||
|
categories = ["network-programming", "parsing"]
|
||||||
|
repository = "https://github.com/YGGverse/pulsarss"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = "0.4.39"
|
||||||
|
clap = { version = "4.5.28", features = ["derive"] }
|
||||||
|
reqwest = { version = "0.12.12", features = ["blocking"] }
|
||||||
|
rss = "2.0.11"
|
||||||
23
README.md
23
README.md
|
|
@ -1,2 +1,25 @@
|
||||||
# pulsarss
|
# pulsarss
|
||||||
|
|
||||||
|

|
||||||
|
[](https://deps.rs/repo/github/YGGverse/pulsarss)
|
||||||
|
[](https://crates.io/crates/pulsarss)
|
||||||
|
|
||||||
RSS Aggregator for Gemini Protocol
|
RSS Aggregator for Gemini Protocol
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
cargo install pulsarss
|
||||||
|
```
|
||||||
|
|
||||||
|
## Launch
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
pulsarss --source https://path/to/feed.rss
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
* `source` - RSS feed source (required)
|
||||||
|
* `target` - Destination directory (`public` by default)
|
||||||
|
* `update` - Update timeout in seconds (60 by default)
|
||||||
18
src/argument.rs
Normal file
18
src/argument.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct Argument {
|
||||||
|
/// RSS feed source (required)
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub source: String,
|
||||||
|
|
||||||
|
/// Destination directory (`public` by default)
|
||||||
|
#[arg(short, long, default_value_t = String::from("public"))]
|
||||||
|
pub target: String,
|
||||||
|
|
||||||
|
/// Update timeout in seconds (60 by default)
|
||||||
|
#[arg(short, long, default_value_t = 60)]
|
||||||
|
pub update: u64,
|
||||||
|
}
|
||||||
80
src/main.rs
Normal file
80
src/main.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
mod argument;
|
||||||
|
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
use argument::Argument;
|
||||||
|
use clap::Parser;
|
||||||
|
use std::{thread::sleep, time::Duration};
|
||||||
|
|
||||||
|
let argument = Argument::parse();
|
||||||
|
|
||||||
|
let mut index: Vec<usize> = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
crawl(&mut index, &argument.source, &argument.target)?;
|
||||||
|
sleep(Duration::from_secs(argument.update));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn crawl(index: &mut Vec<usize>, source: &str, target: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
use reqwest::blocking::get;
|
||||||
|
use rss::Channel;
|
||||||
|
use std::{
|
||||||
|
fs::{metadata, File},
|
||||||
|
io::Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
for item in Channel::read_from(&get(source)?.bytes()?[..])?.items() {
|
||||||
|
let id = index.len() + 1;
|
||||||
|
|
||||||
|
let path = &path(id, target, item.pub_date().unwrap(), true)?;
|
||||||
|
|
||||||
|
if metadata(path).is_ok() {
|
||||||
|
continue; // skip existing records
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = File::create(path)?;
|
||||||
|
|
||||||
|
file.write_all(
|
||||||
|
format!(
|
||||||
|
"# {}\n\n{}\n\n=> {}",
|
||||||
|
item.pub_date().unwrap(),
|
||||||
|
item.description().unwrap(),
|
||||||
|
item.link().unwrap()
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
index.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(_id: usize, base: &str, pub_date: &str, mkdir: bool) -> Result<String, Box<dyn Error>> {
|
||||||
|
use chrono::{DateTime, Datelike, Timelike};
|
||||||
|
use std::{fs::create_dir_all, path::MAIN_SEPARATOR};
|
||||||
|
|
||||||
|
let date_time = DateTime::parse_from_rfc2822(pub_date)?;
|
||||||
|
|
||||||
|
let dir = format!(
|
||||||
|
"{base}{MAIN_SEPARATOR}{:02}{MAIN_SEPARATOR}{:02}{MAIN_SEPARATOR}{:02}",
|
||||||
|
date_time.year(),
|
||||||
|
date_time.month(),
|
||||||
|
date_time.day()
|
||||||
|
);
|
||||||
|
|
||||||
|
let file = format!(
|
||||||
|
"{:02}-{:02}-{:02}.gmi",
|
||||||
|
date_time.hour(),
|
||||||
|
date_time.minute(),
|
||||||
|
date_time.second()
|
||||||
|
);
|
||||||
|
|
||||||
|
if mkdir {
|
||||||
|
create_dir_all(&dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(format!("{dir}{MAIN_SEPARATOR}{file}"))
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue