๐Ÿ“ฆ Turbo87 / segelflug-classifieds

๐Ÿ“„ main.rs ยท 122 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122#[cfg(test)]
#[macro_use]
extern crate insta;
#[macro_use]
extern crate tracing;
#[macro_use]
extern crate lazy_static;

use crate::app::App;
use crate::classifieds::ClassifiedsApi;
use crate::telegram::TelegramApi;
use anyhow::Result;
use clap::{CommandFactory, Parser};
use tokio::time::Duration;
use tracing::Level;
use tracing_subscriber::filter;
use tracing_subscriber::prelude::*;

mod app;
mod classifieds;
mod guids;
mod telegram;

const SOARING_DE_FEED_URL: &str = "https://soaring.de/osclass/index.php?page=search&sFeed=rss";
const SEGELFLUG_DE_FEED_URL: &str = "https://www.segelflug.de/index.php/de/?option=com_djclassifieds&view=items&format=feed&type=rss";

#[derive(clap::Parser, Debug)]
struct Opts {
    /// Run continuously and poll the server in random intervals
    #[arg(short, long)]
    watch: bool,

    /// Minimum time to wait between server requests (in minutes)
    #[arg(long, default_value = "10")]
    min_time: f32,

    /// Maximum time to wait between server requests (in minutes)
    #[arg(long, default_value = "30")]
    max_time: f32,

    /// Telegram chat ID
    #[arg(
        long,
        env = "TELEGRAM_CHAT_ID",
        default_value = "@segelflug_classifieds"
    )]
    telegram_chat_id: String,

    /// Telegram bot token
    #[arg(long, env = "TELEGRAM_TOKEN", hide_env_values = true)]
    telegram_token: Option<String>,
}

#[tokio::main]
async fn main() -> Result<()> {
    let log_filter = std::env::var("RUST_LOG")
        .unwrap_or_default()
        .parse::<filter::Targets>()
        .expect("Invalid RUST_LOG value");

    let log_layer = tracing_subscriber::fmt::layer()
        .pretty()
        .with_filter(log_filter);

    let sentry_filter = filter::Targets::new().with_default(Level::INFO);
    let sentry_layer = sentry::integrations::tracing::layer().with_filter(sentry_filter);

    tracing_subscriber::registry()
        .with(log_layer)
        .with(sentry_layer)
        .init();

    let sha = env!("VERGEN_GIT_SHA");
    event!(Level::INFO, sha = sha);

    let sentry_dsn = std::env::var("SENTRY_DSN");
    let _guard = sentry_dsn.map(|dsn| {
        let options = sentry::ClientOptions {
            release: Some(sha.into()),
            ..Default::default()
        };
        sentry::init((dsn, options))
    });

    let opts: Opts = Opts::parse();
    event!(Level::DEBUG, opts = ?opts);
    if opts.min_time > opts.max_time {
        Opts::command()
            .error(
                clap::error::ErrorKind::ValueValidation,
                "--min-time must not be larger than --max-time",
            )
            .exit();
    }

    let client = reqwest::Client::builder()
        .timeout(Duration::from_secs(10))
        .build()?;

    let feed_urls = vec![SOARING_DE_FEED_URL, SEGELFLUG_DE_FEED_URL];
    let classifieds = ClassifiedsApi::new(feed_urls, client.clone());

    let cwd = std::env::current_dir()?;
    event!(Level::INFO, cwd = ?cwd);

    let guids_path = cwd.join("last-guids.json");

    let telegram = opts
        .telegram_token
        .as_ref()
        .map(|token| TelegramApi::new(token, &opts.telegram_chat_id, client));

    let app = App::new(classifieds, guids_path, telegram);
    if opts.watch {
        app.watch(opts.min_time, opts.max_time).await;
    } else {
        app.run().await?;
    }

    Ok(())
}