mirror of
https://github.com/nelsbrock/disbahn.git
synced 2026-08-14 14:22:07 +02:00
add list for previous titles
This commit is contained in:
Generated
+10
@@ -632,6 +632,7 @@ dependencies = [
|
|||||||
"dotenvy",
|
"dotenvy",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"getset",
|
"getset",
|
||||||
|
"itertools",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
"log",
|
"log",
|
||||||
"reqwest 0.13.4",
|
"reqwest 0.13.4",
|
||||||
@@ -1227,6 +1228,15 @@ version = "1.70.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
|||||||
+2
-1
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "disbahn"
|
name = "disbahn"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ dotenvy = "0.15.7"
|
|||||||
diesel = { version = "2.2.12", features = ["sqlite", "chrono"] }
|
diesel = { version = "2.2.12", features = ["sqlite", "chrono"] }
|
||||||
diesel_migrations = "2.2.0"
|
diesel_migrations = "2.2.0"
|
||||||
getset = "0.1.6"
|
getset = "0.1.6"
|
||||||
|
itertools = "0.15.0"
|
||||||
|
|
||||||
[dependencies.serenity]
|
[dependencies.serenity]
|
||||||
version = "0.12.4"
|
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)
|
||||||
|
);
|
||||||
+7
-7
@@ -1,6 +1,6 @@
|
|||||||
use anyhow::{anyhow, Context};
|
use anyhow::{Context, anyhow};
|
||||||
use disbahn::database::Database;
|
|
||||||
use disbahn::DisbahnClient;
|
use disbahn::DisbahnClient;
|
||||||
|
use disbahn::database::Database;
|
||||||
use log::error;
|
use log::error;
|
||||||
use serenity::http::Http;
|
use serenity::http::Http;
|
||||||
use std::env;
|
use std::env;
|
||||||
@@ -15,10 +15,10 @@ fn env_var(name: &str) -> anyhow::Result<String> {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
if let Err(err) = dotenvy::dotenv() {
|
if let Err(err) = dotenvy::dotenv()
|
||||||
if !err.not_found() {
|
&& !err.not_found()
|
||||||
return Err(err).context("Unable to load .env file");
|
{
|
||||||
}
|
return Err(err).context("Unable to load .env file");
|
||||||
}
|
}
|
||||||
|
|
||||||
env_logger::builder()
|
env_logger::builder()
|
||||||
@@ -31,7 +31,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
Some(s) => {
|
Some(s) => {
|
||||||
return Err(anyhow!(format!(
|
return Err(anyhow!(format!(
|
||||||
"invalid argument `{s}`; the only allowed argument is `daemon`"
|
"invalid argument `{s}`; the only allowed argument is `daemon`"
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
None => false,
|
None => false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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.
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
post_versions (announcement_id, publish_date, title) {
|
||||||
|
announcement_id -> Text,
|
||||||
|
publish_date -> Timestamp,
|
||||||
|
title -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
posts (announcement_id, webhook_id) {
|
posts (announcement_id, webhook_id) {
|
||||||
announcement_id -> Text,
|
announcement_id -> Text,
|
||||||
@@ -8,3 +16,5 @@ diesel::table! {
|
|||||||
last_updated -> Timestamp,
|
last_updated -> Timestamp,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diesel::allow_tables_to_appear_in_same_query!(post_versions, posts,);
|
||||||
|
|||||||
+204
-117
@@ -1,14 +1,16 @@
|
|||||||
pub mod database;
|
pub mod database;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
use crate::database::models::{NewPost, Post};
|
|
||||||
use crate::database::schema::posts::dsl::posts;
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use anyhow::{anyhow, Context};
|
use crate::database::models::{NewPost, Post, PostVersion};
|
||||||
use chrono::{DateTime, NaiveDateTime, TimeZone};
|
use crate::database::schema::post_versions;
|
||||||
|
use anyhow::{Context, Ok, anyhow};
|
||||||
|
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
|
||||||
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl};
|
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 log::{debug, error, info};
|
||||||
use reqwest::IntoUrl;
|
use reqwest::IntoUrl;
|
||||||
use serenity::all::{
|
use serenity::all::{
|
||||||
@@ -17,6 +19,164 @@ use serenity::all::{
|
|||||||
use serenity::http::Http;
|
use serenity::http::Http;
|
||||||
use serenity::model::webhook::Webhook;
|
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 {
|
||||||
|
"HIM1" => "\u{1F6A7} Bauarbeiten".into(),
|
||||||
|
"HIM2" => "\u{26A0}\u{FE0F} Störung".into(),
|
||||||
|
"HIM3" => "\u{270A} Streik".into(),
|
||||||
|
other => format!("\u{2139}\u{FE0F} Information ({other})").into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn colour(&self) -> u32 {
|
||||||
|
match self.icon {
|
||||||
|
"HIM1" => 0xf5c211,
|
||||||
|
"HIM2" | "HIM3" => 0xc1121c,
|
||||||
|
_ => 0x154889,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
pub struct DisbahnClient {
|
||||||
database: Database,
|
database: Database,
|
||||||
webhook: Webhook,
|
webhook: Webhook,
|
||||||
@@ -40,97 +200,16 @@ impl DisbahnClient {
|
|||||||
Ok(channel)
|
Ok(channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validity_time_to_timestamp(input: &str) -> anyhow::Result<i64> {
|
async fn add_post_version_entry(&mut self, item: &Item<'_>) -> anyhow::Result<()> {
|
||||||
let naive = NaiveDateTime::parse_from_str(input, "%Y-%m-%d %H:%M:%S")?;
|
diesel::insert_into(post_versions::table)
|
||||||
let timestamp = chrono_tz::Europe::Berlin
|
.values(PostVersion::new(
|
||||||
.from_local_datetime(&naive)
|
item.guid.to_string(),
|
||||||
.unwrap()
|
item.pub_datetime.naive_utc(),
|
||||||
.timestamp();
|
item.title.to_string(),
|
||||||
Ok(timestamp)
|
))
|
||||||
}
|
.execute(self.database.conn())
|
||||||
|
.with_context(|| "Error inserting new post version into database")?;
|
||||||
fn html_to_discord_markdown(input: &str) -> String {
|
Ok(())
|
||||||
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_tag(name: &str) -> Cow<'static, str> {
|
|
||||||
match name {
|
|
||||||
"HIM1" => "\u{1F6A7} Bauarbeiten".into(),
|
|
||||||
"HIM2" => "\u{26A0}\u{FE0F} Störung".into(),
|
|
||||||
"HIM3" => "\u{270A} Streik".into(),
|
|
||||||
other => format!("\u{2139}\u{FE0F} Information ({other})").into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 tag = Self::icon_name_to_tag(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)
|
|
||||||
.author(CreateEmbedAuthor::new(tag))
|
|
||||||
.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)
|
|
||||||
.timestamp(pub_datetime)
|
|
||||||
.footer(CreateEmbedFooter::new("zuginfo.nrw").icon_url(FOOTER_ICON_URL));
|
|
||||||
|
|
||||||
Ok(embed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn refresh(&mut self) -> anyhow::Result<()> {
|
pub async fn refresh(&mut self) -> anyhow::Result<()> {
|
||||||
@@ -152,27 +231,31 @@ impl DisbahnClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn refresh_item(&mut self, item: &rss::Item) -> anyhow::Result<()> {
|
async fn refresh_item(&mut self, rss_item: &rss::Item) -> anyhow::Result<()> {
|
||||||
use crate::database::schema::posts::{self, dsl};
|
use crate::database::schema::{post_versions, posts};
|
||||||
|
|
||||||
let guid = item.guid().ok_or(anyhow!("Missing GUID"))?.value();
|
let item: Item = rss_item.try_into()?;
|
||||||
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 existing_post: Option<Post> = posts
|
let existing_post: Option<Post> = posts::dsl::posts
|
||||||
.filter(dsl::webhook_id.eq(i64::from_le_bytes(self.webhook.id.get().to_le_bytes())))
|
.filter(
|
||||||
.filter(dsl::announcement_id.eq(guid))
|
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())
|
.first(self.database.conn())
|
||||||
.optional()
|
.optional()
|
||||||
.with_context(|| "Error loading posts from database")?;
|
.with_context(|| "Error loading posts from database")?;
|
||||||
|
|
||||||
if let Some(existing_post) = existing_post {
|
if let Some(existing_post) = existing_post {
|
||||||
if existing_post.last_updated().and_utc() < pub_datetime {
|
if existing_post.last_updated().and_utc() < item.pub_datetime {
|
||||||
info!("Updated item: {guid}");
|
info!("Updated item with id {}", item.guid);
|
||||||
let embed = Self::item_to_embed(item)?;
|
|
||||||
|
let previous_posts = post_versions::dsl::post_versions
|
||||||
|
.filter(post_versions::dsl::announcement_id.eq(item.guid))
|
||||||
|
.order_by(post_versions::publish_date.desc())
|
||||||
|
.load::<PostVersion>(self.database.conn())
|
||||||
|
.with_context(|| "Error loading previous posts from database")?;
|
||||||
|
|
||||||
|
let embed = item.to_embed(&previous_posts)?;
|
||||||
self.webhook
|
self.webhook
|
||||||
.edit_message(
|
.edit_message(
|
||||||
&self.http,
|
&self.http,
|
||||||
@@ -182,17 +265,19 @@ impl DisbahnClient {
|
|||||||
.await
|
.await
|
||||||
.with_context(|| "Failed to edit message")?;
|
.with_context(|| "Failed to edit message")?;
|
||||||
|
|
||||||
diesel::update(posts.find((
|
diesel::update(posts::dsl::posts.find((
|
||||||
guid,
|
item.guid,
|
||||||
i64::from_le_bytes(self.webhook.id.get().to_le_bytes()),
|
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())
|
.execute(self.database.conn())
|
||||||
.with_context(|| "Error updating post in database")?;
|
.with_context(|| "Error updating post in database")?;
|
||||||
|
|
||||||
|
self.add_post_version_entry(&item).await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("New item: {guid}");
|
info!("New item with id {}", item.guid);
|
||||||
let embed = Self::item_to_embed(item)?;
|
let embed = item.to_embed(&[])?;
|
||||||
let message = self
|
let message = self
|
||||||
.webhook
|
.webhook
|
||||||
.execute(&self.http, true, ExecuteWebhook::new().embed(embed))
|
.execute(&self.http, true, ExecuteWebhook::new().embed(embed))
|
||||||
@@ -202,13 +287,15 @@ impl DisbahnClient {
|
|||||||
|
|
||||||
diesel::insert_into(posts::table)
|
diesel::insert_into(posts::table)
|
||||||
.values(NewPost::new(
|
.values(NewPost::new(
|
||||||
guid,
|
item.guid,
|
||||||
self.webhook.id,
|
self.webhook.id,
|
||||||
message.id,
|
message.id,
|
||||||
pub_datetime.naive_utc(),
|
item.pub_datetime.naive_utc(),
|
||||||
))
|
))
|
||||||
.execute(self.database.conn())
|
.execute(self.database.conn())
|
||||||
.with_context(|| "Error inserting new post into database")?;
|
.with_context(|| "Error inserting new post into database")?;
|
||||||
|
|
||||||
|
self.add_post_version_entry(&item).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user