initial commit

This commit is contained in:
yggverse 2025-02-11 18:37:02 +02:00
parent 6f41fdc38d
commit 4280f9cb96
6 changed files with 167 additions and 0 deletions

27
.github/workflows/build.yml vendored Normal file
View 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
View file

@ -0,0 +1,3 @@
/public
/target
Cargo.lock

16
Cargo.toml Normal file
View 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"

View file

@ -1,2 +1,25 @@
# pulsarss # pulsarss
![Linux](https://github.com/YGGverse/pulsarss/actions/workflows/linux.yml/badge.svg)
[![Dependencies](https://deps.rs/repo/github/YGGverse/pulsarss/status.svg)](https://deps.rs/repo/github/YGGverse/pulsarss)
[![crates.io](https://img.shields.io/crates/v/pulsarss.svg)](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
View 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
View 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}"))
}