rdzobot 0.1.0

Modular, but monolithic Matrix bot
Documentation
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Wojtek Porczyk <woju@hackerspace.pl>

use matrix_sdk::attachment::AttachmentConfig;

use serde_json;

use crate::prelude::*;
use crate::utils;
use crate::utils::Location;

async fn get_parcel_locker_location(bot: &Rdzobot, r#ref: &str) -> anyhow::Result<Location> {
    let data = bot
        .client
        .http_client()
        .get(format!("https://api-pl-points.easypack24.net/v1/points/{ref}"))
        .send()
        .await?
        .json::<serde_json::Value>()
        .await?;
    Location::try_from(&data["location"])
}

pub const RE_PARCEL_INPOST_1: &str = "\
    Cześć! Z kodem (?<code>[0-9]{6}) odbierzesz .*? \
    na numer telefonu (?<phone>[0-9]{9}).*? \
    w urządzeniu P[aą]czkomat (?<ref>[A-Za-z0-9]*)";

struct Parcel {
    room: String,
    event_orig: String,
    event_qr: String,
    code: String,
    phone: String,
    r#ref: String,
}

pub async fn on_regex_inpost(
    re: regex::Regex,
    body: String,
    event: OriginalSyncRoomMessageEvent,
    _client: Client,
    room: Room,
    bot: Rdzobot,
) -> anyhow::Result<()> {
    for captures in re.captures_iter(body.as_str()) {
        let image =
            utils::create_qrcode_png(&format!("P|{}|{}", &captures["phone"], &captures["code"]))?;

        let response = room
            .send_attachment(
                "qrcode.png",
                &mime::IMAGE_PNG,
                image,
                AttachmentConfig::new().caption(Some(format!(
                    "inpost, {}, telefon: {}, kod odbioru: {}",
                    &captures["ref"], &captures["phone"], &captures["code"],
                ))),
            )
            .await?;

        let mut location: Option<Location> = None;
        match get_parcel_locker_location(&bot, &captures["ref"]).await {
            Ok(l) => {
                location = Some(l);
            }
            Err(e) => {
                tracing::info!("error while resolving parcel locker location: {e}");
            }
        }

        let parcel = Parcel {
            room: room.room_id().to_string(),
            event_orig: event.event_id.to_string(),
            event_qr: response.event_id.to_string(),
            code: captures["code"].to_string(),
            phone: captures["phone"].to_string(),
            r#ref: captures["ref"].to_string(),
        };

        let bot = bot.clone();
        let _ = tokio::task::spawn_blocking(move || -> anyhow::Result<()> {
            let sqlite = bot.sqlite();

            if let Some(l) = location {
                sqlite
                    .prepare(
                        "INSERT OR REPLACE INTO parcel_points (
                            operator, ref, lat, lon
                        ) VALUES (
                            'inpost', :ref, :lat, :lon
                        );",
                    )?
                    .execute(rusqlite::named_params! {
                        ":ref": parcel.r#ref,
                        ":lat": l.lat,
                        ":lon": l.lon,
                    })?;
            }

            sqlite
                .prepare(
                    "INSERT INTO parcels (
                        room, event_orig, event_qr, operator, code, phone, ref, time_end
                    ) VALUES (
                        :room, :event_orig, :event_qr, 'inpost', :code, :phone, :ref, datetime('now', '+2 days')
                    );",
                )?
                .execute(rusqlite::named_params! {
                    ":room": parcel.room,
                    ":event_orig": parcel.event_orig,
                    ":event_qr": parcel.event_qr,
                    ":code": parcel.code,
                    ":phone": parcel.phone,
                    ":ref": parcel.r#ref,
                })?;

            Ok(())
        })
        .await?;
    }

    Ok(())
}