mirror of
https://github.com/nelsbrock/disbahn.git
synced 2026-08-14 22:26:49 +02:00
Compare commits
11
Commits
500a44ee99
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1842d8d92
|
||
|
|
ec0840bf53
|
||
|
|
4256aaad1d
|
||
|
|
3e31f4d1f9
|
||
|
|
b5a058420e
|
||
|
|
dbae6cedf9
|
||
|
|
72b8ef06d0
|
||
|
|
2967e385e5
|
||
|
|
ec707ec4a3
|
||
|
|
574f3764fb
|
||
|
|
fe9f00c014
|
Generated
+1020
-972
File diff suppressed because it is too large
Load Diff
+3
-2
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "disbahn"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rss = "2.0.12"
|
||||
reqwest = {version = "0.12.22", default-features = false, features = ["rustls-tls"]}
|
||||
reqwest = "0.13.4"
|
||||
tokio = {version = "1.47.0", features = ["rt-multi-thread", "signal"]}
|
||||
lazy-regex = "3.4.1"
|
||||
chrono = "0.4.41"
|
||||
@@ -19,6 +19,7 @@ dotenvy = "0.15.7"
|
||||
diesel = { version = "2.2.12", features = ["sqlite", "chrono"] }
|
||||
diesel_migrations = "2.2.0"
|
||||
getset = "0.1.6"
|
||||
itertools = "0.15.0"
|
||||
|
||||
[dependencies.serenity]
|
||||
version = "0.12.4"
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE post_versions;
|
||||
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE post_versions (
|
||||
announcement_id TEXT NOT NULL,
|
||||
publish_date TIMESTAMP NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
PRIMARY KEY (announcement_id, publish_date, title)
|
||||
);
|
||||
@@ -49,3 +49,24 @@ impl<'a> NewPost<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Queryable, Insertable, Getters)]
|
||||
#[diesel(table_name = post_versions)]
|
||||
pub struct PostVersion {
|
||||
#[getset(get = "pub")]
|
||||
announcement_id: String,
|
||||
#[getset(get = "pub")]
|
||||
publish_date: NaiveDateTime,
|
||||
#[getset(get = "pub")]
|
||||
title: String,
|
||||
}
|
||||
|
||||
impl PostVersion {
|
||||
pub fn new(announcement_id: String, publish_date: NaiveDateTime, title: String) -> Self {
|
||||
Self {
|
||||
announcement_id,
|
||||
publish_date,
|
||||
title,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
post_versions (announcement_id, publish_date, title) {
|
||||
announcement_id -> Text,
|
||||
publish_date -> Timestamp,
|
||||
title -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
posts (announcement_id, webhook_id) {
|
||||
announcement_id -> Text,
|
||||
@@ -8,3 +16,5 @@ diesel::table! {
|
||||
last_updated -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(post_versions, posts,);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
-# **Hinweis:** Die aus dem RSS-Feed von zuginfo.nrw stammenden Beginn- und Endzeiten scheinen oft inakkurat zu sein und sind daher mit Vorsicht zu genießen.
|
||||
+212
-124
@@ -1,18 +1,185 @@
|
||||
pub mod database;
|
||||
|
||||
use crate::database::models::{NewPost, Post};
|
||||
use crate::database::schema::posts::dsl::posts;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use crate::database::Database;
|
||||
use anyhow::{anyhow, Context};
|
||||
use chrono::{DateTime, NaiveDateTime, TimeZone};
|
||||
use crate::database::models::{NewPost, Post, PostVersion};
|
||||
use crate::database::schema::post_versions;
|
||||
use anyhow::{Context, Ok, anyhow};
|
||||
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
|
||||
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
||||
use lazy_regex::{regex, Lazy, Regex};
|
||||
use itertools::Itertools;
|
||||
use lazy_regex::{Lazy, Regex, regex};
|
||||
use log::{debug, error, info};
|
||||
use reqwest::IntoUrl;
|
||||
use serenity::all::{CreateEmbed, CreateEmbedFooter, EditWebhookMessage, ExecuteWebhook};
|
||||
use serenity::all::{
|
||||
CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter, EditWebhookMessage, ExecuteWebhook,
|
||||
};
|
||||
use serenity::http::Http;
|
||||
use serenity::model::webhook::Webhook;
|
||||
|
||||
struct Item<'a> {
|
||||
guid: &'a str,
|
||||
title: &'a str,
|
||||
link: &'a str,
|
||||
validity_begin: DateTime<Utc>,
|
||||
validity_end: DateTime<Utc>,
|
||||
icon: &'a str,
|
||||
description: &'a str,
|
||||
pub_datetime: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Item<'_> {
|
||||
fn parse_validity_time(input: &str) -> anyhow::Result<DateTime<Utc>> {
|
||||
let naive = NaiveDateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S")?;
|
||||
let datetime = chrono_tz::Europe::Berlin
|
||||
.from_local_datetime(&naive)
|
||||
.unwrap()
|
||||
.to_utc();
|
||||
Ok(datetime)
|
||||
}
|
||||
|
||||
fn html_to_discord_markdown(input: &str) -> String {
|
||||
static RE_TIMES: &Lazy<Regex> = regex!(r#"^.*?<br\s*/>\s*<br\s*/>"#i);
|
||||
static RE_BOLD: &Lazy<Regex> = regex!(r#"<b\s*>((.|\n)*?)</b\s*>"#i);
|
||||
static RE_ITALIC: &Lazy<Regex> = regex!(r#"<i\s*>((.|\n)*?)</i\s*>"#i);
|
||||
static RE_STRIKETHROUGH: &Lazy<Regex> = regex!(r#"<s\s*>((.|\n)*?)</s\s*>"#i);
|
||||
static RE_NEWLINE: &Lazy<Regex> = regex!(r#"<br\s*/?>"#i);
|
||||
|
||||
let input = RE_TIMES.replace(input, "");
|
||||
let input = RE_BOLD.replace_all(&input, "**$1**");
|
||||
let input = RE_ITALIC.replace_all(&input, "*$1*");
|
||||
let input = RE_STRIKETHROUGH.replace_all(&input, "~~$1~~");
|
||||
let input = RE_NEWLINE.replace_all(&input, "\n");
|
||||
input.to_string()
|
||||
}
|
||||
|
||||
fn tag(&self) -> Cow<'static, str> {
|
||||
match self.icon {
|
||||
"HIM0" => "\u{2139}\u{FE0F} Information".into(),
|
||||
"HIM1" => "\u{1F6A7} Bauarbeiten".into(),
|
||||
"HIM2" => "\u{26A0}\u{FE0F} Störung".into(),
|
||||
"HIM3" => "\u{270A} Streik".into(),
|
||||
other => format!("\u{1F937} Unbekannte Kategorie {other:?}").into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn colour(&self) -> u32 {
|
||||
match self.icon {
|
||||
"HIM0" => 0x154889,
|
||||
"HIM1" => 0xf5c211,
|
||||
"HIM2" => 0xc1121c,
|
||||
"HIM3" => 0xff6a00,
|
||||
_ => 0xffffff,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_embed(&self, mut previous_versions: &[PostVersion]) -> anyhow::Result<CreateEmbed> {
|
||||
const FOOTER_ICON_URL: &str = "https://www.zuginfo.nrw/img/customer/apple-touch-icon.png";
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.title(self.title)
|
||||
.url(self.link)
|
||||
.author(CreateEmbedAuthor::new(self.tag()))
|
||||
.colour(self.colour())
|
||||
.description(Self::html_to_discord_markdown(self.description))
|
||||
.field(
|
||||
"Beginn:",
|
||||
format!("<t:{}:f>", self.validity_begin.timestamp()),
|
||||
true,
|
||||
)
|
||||
.field(
|
||||
"Ende:",
|
||||
format!("<t:{}:f>", self.validity_end.timestamp()),
|
||||
true,
|
||||
)
|
||||
.timestamp(self.pub_datetime)
|
||||
.footer(CreateEmbedFooter::new("zuginfo.nrw").icon_url(FOOTER_ICON_URL));
|
||||
|
||||
// Remove trailing titles matching the current title
|
||||
while let Some(last) = previous_versions.last()
|
||||
&& last.title() == self.title
|
||||
{
|
||||
previous_versions = &previous_versions[..previous_versions.len() - 1]
|
||||
}
|
||||
|
||||
let mut previous_titles_str = String::new();
|
||||
for version in previous_versions
|
||||
.iter()
|
||||
.dedup_by(|&x, &y| x.title() == y.title())
|
||||
{
|
||||
writeln!(
|
||||
&mut previous_titles_str,
|
||||
"-# * <t:{}:s> {}",
|
||||
version.publish_date().and_utc().timestamp(),
|
||||
version.title()
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if !previous_titles_str.is_empty() {
|
||||
embed = embed.field("Vorherige Titel:", previous_titles_str, false);
|
||||
}
|
||||
|
||||
Ok(embed)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a rss::Item> for Item<'a> {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(rss_item: &'a rss::Item) -> Result<Self, Self::Error> {
|
||||
let categories = rss_item.categories();
|
||||
|
||||
let guid = rss_item.guid().ok_or(anyhow!("Missing GUID"))?.value();
|
||||
|
||||
let title = rss_item.title().ok_or(anyhow!("Missing title"))?;
|
||||
let link = rss_item.link().ok_or(anyhow!("Missing link"))?;
|
||||
|
||||
let validity_begin = &categories
|
||||
.iter()
|
||||
.find(|c| c.domain() == Some("validityBegin"))
|
||||
.ok_or(anyhow!("Missing validityBegin category"))?
|
||||
.name;
|
||||
let validity_begin = Self::parse_validity_time(validity_begin)?;
|
||||
|
||||
let validity_end = &categories
|
||||
.iter()
|
||||
.find(|c| c.domain() == Some("validityEnd"))
|
||||
.ok_or(anyhow!("Missing validityEnd category"))?
|
||||
.name;
|
||||
let validity_end = Self::parse_validity_time(validity_end)?;
|
||||
|
||||
let icon = categories
|
||||
.iter()
|
||||
.find(|c| c.domain() == Some("icon"))
|
||||
.map_or("", |c| c.name());
|
||||
|
||||
let description = rss_item
|
||||
.description()
|
||||
.ok_or(anyhow!("Missing description"))?;
|
||||
|
||||
let pub_date_str = rss_item
|
||||
.pub_date()
|
||||
.ok_or(anyhow!("Missing publication date"))?;
|
||||
let pub_datetime = DateTime::parse_from_rfc2822(pub_date_str)
|
||||
.with_context(|| format!("Unable to parse publication date string {pub_date_str:?}"))?
|
||||
.naive_utc()
|
||||
.and_utc();
|
||||
|
||||
Ok(Self {
|
||||
guid,
|
||||
title,
|
||||
link,
|
||||
validity_begin,
|
||||
validity_end,
|
||||
icon,
|
||||
description,
|
||||
pub_datetime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisbahnClient {
|
||||
database: Database,
|
||||
webhook: Webhook,
|
||||
@@ -36,103 +203,16 @@ impl DisbahnClient {
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
fn validity_time_to_timestamp(input: &str) -> anyhow::Result<i64> {
|
||||
let naive = NaiveDateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S")?;
|
||||
let timestamp = chrono_tz::Europe::Berlin
|
||||
.from_local_datetime(&naive)
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
Ok(timestamp)
|
||||
}
|
||||
|
||||
fn html_to_discord_markdown(input: &str) -> String {
|
||||
static RE_TIMES: &Lazy<Regex> = regex!(r#"^.*<br\s*/>\s*<br\s*/>"#i);
|
||||
static RE_BOLD: &Lazy<Regex> = regex!(r#"<b\s*>((.|\n)*?)</b\s*>"#i);
|
||||
static RE_ITALIC: &Lazy<Regex> = regex!(r#"<i\s*>((.|\n)*?)</i\s*>"#i);
|
||||
static RE_STRIKETHROUGH: &Lazy<Regex> = regex!(r#"<s\s*>((.|\n)*?)</s\s*>"#i);
|
||||
static RE_NEWLINE: &Lazy<Regex> = regex!(r#"<br\s*/?>"#i);
|
||||
|
||||
let input = RE_TIMES.replace(input, "");
|
||||
let input = RE_BOLD.replace_all(&input, "**$1**");
|
||||
let input = RE_ITALIC.replace_all(&input, "*$1*");
|
||||
let input = RE_STRIKETHROUGH.replace_all(&input, "~~$1~~");
|
||||
let input = RE_NEWLINE.replace_all(&input, "\n");
|
||||
input.to_string()
|
||||
}
|
||||
|
||||
fn icon_name_to_url(name: &str) -> &str {
|
||||
match name {
|
||||
"HIM1" => "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a9/Zeichen_123_-_Arbeitsstelle%2C_StVO_2013.svg/273px-Zeichen_123_-_Arbeitsstelle%2C_StVO_2013.svg.png",
|
||||
"HIM2" => "https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/Zeichen_101_-_Gefahrstelle%2C_StVO_1970.svg/273px-Zeichen_101_-_Gefahrstelle%2C_StVO_1970.svg.png",
|
||||
"HIM3" => "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6c/Strike_worker.svg/314px-Strike_worker.svg.png",
|
||||
_ => "https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Zeichen_365-61_-_Informationsstelle%2C_StVO_2013.svg/240px-Zeichen_365-61_-_Informationsstelle%2C_StVO_2013.svg.png",
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_name_to_colour(name: &str) -> u32 {
|
||||
match name {
|
||||
"HIM1" => 0xf5c211,
|
||||
"HIM2" | "HIM3" => 0xc1121c,
|
||||
_ => 0x154889,
|
||||
}
|
||||
}
|
||||
|
||||
fn item_to_embed(item: &rss::Item) -> anyhow::Result<CreateEmbed> {
|
||||
const FOOTER_ICON_URL: &str = "https://www.zuginfo.nrw/img/customer/apple-touch-icon.png";
|
||||
|
||||
let categories = item.categories();
|
||||
|
||||
let title = item.title().ok_or(anyhow!("Missing title"))?;
|
||||
let link = item.link().ok_or(anyhow!("Missing link"))?;
|
||||
|
||||
let validity_begin = &categories
|
||||
.iter()
|
||||
.find(|c| c.domain() == Some("validityBegin"))
|
||||
.ok_or(anyhow!("Missing validityBegin category"))?
|
||||
.name;
|
||||
let validity_begin = Self::validity_time_to_timestamp(validity_begin)?;
|
||||
|
||||
let validity_end = &categories
|
||||
.iter()
|
||||
.find(|c| c.domain() == Some("validityEnd"))
|
||||
.ok_or(anyhow!("Missing validityEnd category"))?
|
||||
.name;
|
||||
let validity_end = Self::validity_time_to_timestamp(validity_end)?;
|
||||
|
||||
let icon = categories
|
||||
.iter()
|
||||
.find(|c| c.domain() == Some("icon"))
|
||||
.map_or("", |c| c.name());
|
||||
let icon_url = Self::icon_name_to_url(icon);
|
||||
|
||||
let description = Self::html_to_discord_markdown(
|
||||
item.description().ok_or(anyhow!("Missing description"))?,
|
||||
);
|
||||
|
||||
let pub_date_str = item.pub_date().ok_or(anyhow!("Missing publication date"))?;
|
||||
let pub_datetime = DateTime::parse_from_rfc2822(pub_date_str)
|
||||
.with_context(|| format!("Unable to parse publication date string {pub_date_str:?}"))?
|
||||
.naive_utc()
|
||||
.and_utc();
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(title)
|
||||
.url(link)
|
||||
.thumbnail(icon_url)
|
||||
.colour(Self::icon_name_to_colour(icon))
|
||||
.description(description)
|
||||
.field("Beginn:", format!("<t:{validity_begin}:F>"), true)
|
||||
.field("Ende:", format!("<t:{validity_end}:F>"), true)
|
||||
.field("", include_str!("hint.txt"), false)
|
||||
.timestamp(pub_datetime)
|
||||
.footer(
|
||||
CreateEmbedFooter::new(
|
||||
"Quelle: https://zuginfo.nrw/ \u{2013} Alle Angaben ohne Gewehr \u{1F52B}",
|
||||
)
|
||||
.icon_url(FOOTER_ICON_URL),
|
||||
);
|
||||
|
||||
Ok(embed)
|
||||
async fn add_post_version_entry(&mut self, item: &Item<'_>) -> anyhow::Result<()> {
|
||||
diesel::insert_into(post_versions::table)
|
||||
.values(PostVersion::new(
|
||||
item.guid.to_string(),
|
||||
item.pub_datetime.naive_utc(),
|
||||
item.title.to_string(),
|
||||
))
|
||||
.execute(self.database.conn())
|
||||
.with_context(|| "Error inserting new post version into database")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn refresh(&mut self) -> anyhow::Result<()> {
|
||||
@@ -154,27 +234,31 @@ impl DisbahnClient {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn refresh_item(&mut self, item: &rss::Item) -> anyhow::Result<()> {
|
||||
use crate::database::schema::posts::{self, dsl};
|
||||
async fn refresh_item(&mut self, rss_item: &rss::Item) -> anyhow::Result<()> {
|
||||
use crate::database::schema::{post_versions, posts};
|
||||
|
||||
let guid = item.guid().ok_or(anyhow!("Missing GUID"))?.value();
|
||||
let pub_date_str = item.pub_date().ok_or(anyhow!("Missing publication date"))?;
|
||||
let pub_datetime = DateTime::parse_from_rfc2822(pub_date_str)
|
||||
.with_context(|| format!("Unable to parse publication date string {pub_date_str:?}"))?
|
||||
.naive_utc()
|
||||
.and_utc();
|
||||
let item: Item = rss_item.try_into()?;
|
||||
|
||||
let existing_post: Option<Post> = posts
|
||||
.filter(dsl::webhook_id.eq(i64::from_le_bytes(self.webhook.id.get().to_le_bytes())))
|
||||
.filter(dsl::announcement_id.eq(guid))
|
||||
let existing_post: Option<Post> = posts::dsl::posts
|
||||
.filter(
|
||||
posts::dsl::webhook_id.eq(i64::from_le_bytes(self.webhook.id.get().to_le_bytes())),
|
||||
)
|
||||
.filter(posts::dsl::announcement_id.eq(item.guid))
|
||||
.first(self.database.conn())
|
||||
.optional()
|
||||
.with_context(|| "Error loading posts from database")?;
|
||||
|
||||
if let Some(existing_post) = existing_post {
|
||||
if existing_post.last_updated().and_utc() < pub_datetime {
|
||||
info!("Updated item: {guid}");
|
||||
let embed = Self::item_to_embed(item)?;
|
||||
if existing_post.last_updated().and_utc() < item.pub_datetime {
|
||||
info!("Updated item with id {}", item.guid);
|
||||
|
||||
let previous_posts = post_versions::dsl::post_versions
|
||||
.filter(post_versions::dsl::announcement_id.eq(item.guid))
|
||||
.order_by(post_versions::publish_date)
|
||||
.load::<PostVersion>(self.database.conn())
|
||||
.with_context(|| "Error loading previous posts from database")?;
|
||||
|
||||
let embed = item.to_embed(&previous_posts)?;
|
||||
self.webhook
|
||||
.edit_message(
|
||||
&self.http,
|
||||
@@ -184,17 +268,19 @@ impl DisbahnClient {
|
||||
.await
|
||||
.with_context(|| "Failed to edit message")?;
|
||||
|
||||
diesel::update(posts.find((
|
||||
guid,
|
||||
diesel::update(posts::dsl::posts.find((
|
||||
item.guid,
|
||||
i64::from_le_bytes(self.webhook.id.get().to_le_bytes()),
|
||||
)))
|
||||
.set(dsl::last_updated.eq(pub_datetime.naive_utc()))
|
||||
.set(posts::dsl::last_updated.eq(item.pub_datetime.naive_utc()))
|
||||
.execute(self.database.conn())
|
||||
.with_context(|| "Error updating post in database")?;
|
||||
|
||||
self.add_post_version_entry(&item).await?;
|
||||
}
|
||||
} else {
|
||||
info!("New item: {guid}");
|
||||
let embed = Self::item_to_embed(item)?;
|
||||
info!("New item with id {}", item.guid);
|
||||
let embed = item.to_embed(&[])?;
|
||||
let message = self
|
||||
.webhook
|
||||
.execute(&self.http, true, ExecuteWebhook::new().embed(embed))
|
||||
@@ -204,13 +290,15 @@ impl DisbahnClient {
|
||||
|
||||
diesel::insert_into(posts::table)
|
||||
.values(NewPost::new(
|
||||
guid,
|
||||
item.guid,
|
||||
self.webhook.id,
|
||||
message.id,
|
||||
pub_datetime.naive_utc(),
|
||||
item.pub_datetime.naive_utc(),
|
||||
))
|
||||
.execute(self.database.conn())
|
||||
.with_context(|| "Error inserting new post into database")?;
|
||||
|
||||
self.add_post_version_entry(&item).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use anyhow::{anyhow, Context};
|
||||
use disbahn::database::Database;
|
||||
use anyhow::{Context, anyhow};
|
||||
use disbahn::DisbahnClient;
|
||||
use disbahn::database::Database;
|
||||
use log::error;
|
||||
use serenity::http::Http;
|
||||
use std::env;
|
||||
@@ -15,11 +15,11 @@ fn env_var(name: &str) -> anyhow::Result<String> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
if let Err(err) = dotenvy::dotenv() {
|
||||
if !err.not_found() {
|
||||
if let Err(err) = dotenvy::dotenv()
|
||||
&& !err.not_found()
|
||||
{
|
||||
return Err(err).context("Unable to load .env file");
|
||||
}
|
||||
}
|
||||
|
||||
env_logger::builder()
|
||||
.filter_module(module_path!(), log::LevelFilter::Debug)
|
||||
@@ -31,7 +31,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
Some(s) => {
|
||||
return Err(anyhow!(format!(
|
||||
"invalid argument `{s}`; the only allowed argument is `daemon`"
|
||||
)))
|
||||
)));
|
||||
}
|
||||
None => false,
|
||||
};
|
||||
Reference in New Issue
Block a user