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(())
}