implement list command
This commit is contained in:
parent
8309d26c9f
commit
3210581a22
7 changed files with 467 additions and 25 deletions
|
|
@ -64,10 +64,12 @@ pub fn get_serve() -> Arc<Serve> {
|
|||
pub struct Main {
|
||||
/// The data directory.
|
||||
static_dir: PathBuf,
|
||||
/// Grist API KEY
|
||||
grist_api_key: String,
|
||||
}
|
||||
impl Main {
|
||||
/// Constructor.
|
||||
pub fn new() -> Self {
|
||||
pub fn new(grist_api_key: impl Into<String>) -> Self {
|
||||
let static_dir_root = match dirs::data_dir().context("no `static_dir` directory found") {
|
||||
Ok(root) => root,
|
||||
Err(e) => {
|
||||
|
|
@ -77,6 +79,7 @@ impl Main {
|
|||
};
|
||||
Self {
|
||||
static_dir: static_dir_root.join("shift_bot"),
|
||||
grist_api_key: grist_api_key.into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,11 +99,16 @@ impl Main {
|
|||
pub fn session_file(&self) -> PathBuf {
|
||||
self.from_static_dir(Self::SESSION_FILE_NAME)
|
||||
}
|
||||
|
||||
/// Grist API KEY
|
||||
pub fn grist_api_key(&self) -> &str {
|
||||
&self.grist_api_key
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Main {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new("")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use std::fmt::Write;
|
||||
|
||||
use crate::grist::Shift;
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use matrix_sdk::{Client, Room};
|
||||
|
||||
|
|
@ -27,19 +26,37 @@ enum Action {
|
|||
Aide,
|
||||
}
|
||||
|
||||
/// Sends a message to a room.
|
||||
pub async fn send_room_msg(_client: Client, room: Room, content: String) -> Res<OwnedEventId> {
|
||||
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
|
||||
// let md_parser = pulldown_cmark::Parser::new(&content);
|
||||
// let mut html_body = String::with_capacity(250);
|
||||
// pulldown_cmark::html::push_html(&mut html_body, md_parser);
|
||||
// let content = RoomMessageEventContent::text_html(content, html_body);
|
||||
let content = RoomMessageEventContent::text_plain(content);
|
||||
let sent = room.send(content).await?;
|
||||
Ok(sent.event_id)
|
||||
}
|
||||
|
||||
// pub fn handle(command: impl Into<String>) -> Res<()> {
|
||||
pub async fn send_room_msg_html(
|
||||
_client: Client,
|
||||
room: Room,
|
||||
html_body: String,
|
||||
) -> Res<OwnedEventId> {
|
||||
use matrix_sdk::ruma::events::room::message::RoomMessageEventContent;
|
||||
// let md_parser = pulldown_cmark::Parser::new(&content);
|
||||
// let mut html_body = String::with_capacity(250);
|
||||
// pulldown_cmark::html::push_html(&mut html_body, md_parser);
|
||||
let content = RoomMessageEventContent::text_html(html_body.clone(), html_body);
|
||||
let sent = room.send(content).await?;
|
||||
Ok(sent.event_id)
|
||||
}
|
||||
|
||||
pub fn format_shifts(shifts: Vec<Shift>) -> String {
|
||||
let mut output = String::new();
|
||||
output.push_str("Shifts :\n<ul>");
|
||||
for shift in shifts {
|
||||
output.push_str(format!("<li>{}</li>", shift).as_ref());
|
||||
}
|
||||
output.push_str("</ul>");
|
||||
output
|
||||
}
|
||||
|
||||
pub async fn handle(command: impl AsRef<str>, room: Room, client: Client) -> Res<()> {
|
||||
debug!("handling command: {}", command.as_ref());
|
||||
|
||||
|
|
@ -50,6 +67,9 @@ pub async fn handle(command: impl AsRef<str>, room: Room, client: Client) -> Res
|
|||
match clap.action {
|
||||
Action::Lister => {
|
||||
debug!("user asked for list");
|
||||
let shifts = crate::grist::list_shifts().await?;
|
||||
debug!("{:?}", shifts);
|
||||
send_room_msg_html(client, room, format_shifts(shifts)).await?;
|
||||
}
|
||||
Action::Ajouter { nom, infos } => {
|
||||
debug!("user asked for add");
|
||||
|
|
|
|||
220
src/grist.rs
Normal file
220
src/grist.rs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
prelude! {}
|
||||
|
||||
use chrono::DateTime;
|
||||
use grist_client::apis::configuration::Configuration;
|
||||
use grist_client::apis::records_api::list_records;
|
||||
use grist_client::models::{RecordsList, RecordsListRecordsInner};
|
||||
|
||||
pub const API_URL: &str = "https://grist.interhacker.space/api";
|
||||
pub const SHIFTS_DOC: &str = "iwSaC82TsQiuD3MYSG9RXX";
|
||||
pub const SHIFTS_TABLE: &str = "Shifts";
|
||||
pub const SHIFT_TYPES_TABLE: &str = "Types_de_shifts";
|
||||
pub const INSCRIPTIONS_TABLE: &str = "Inscriptions";
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Priority {
|
||||
Prioritaire,
|
||||
Secondaire,
|
||||
Bonus,
|
||||
Urgent,
|
||||
}
|
||||
|
||||
impl Display for Priority {
|
||||
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
||||
match self {
|
||||
Priority::Prioritaire => write!(f, "<b>Prioritaire</b>"),
|
||||
Priority::Secondaire => write!(f, "Secondaire"),
|
||||
Priority::Bonus => write!(f, "Bonus"),
|
||||
Priority::Urgent => write!(f, "<font color=\"red\">Prioritaire</font>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ShiftType {
|
||||
pub name: String,
|
||||
pub priority: Priority,
|
||||
pub frequency: u64,
|
||||
pub persons: u64,
|
||||
pub duration: u64,
|
||||
pub guide: String,
|
||||
}
|
||||
impl ShiftType {
|
||||
pub fn new(
|
||||
name: impl Into<String>,
|
||||
priority: Priority,
|
||||
frequency: u64,
|
||||
persons: u64,
|
||||
duration: u64,
|
||||
guide: impl Into<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
priority: priority,
|
||||
frequency: frequency,
|
||||
persons: persons,
|
||||
duration: duration,
|
||||
guide: guide.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ShiftType {
|
||||
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
||||
write!(
|
||||
f,
|
||||
"{} ({}): {} x/j, {} personnes, {} h",
|
||||
self.name, self.priority, self.frequency, self.persons, self.duration
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// {"Autres_infos": Null, "Heure_de_debut": Number(1782144000), "Inscriptions": Array [String("L"), Number(1)], "Type_de_shift": Number(23)}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Shift {
|
||||
pub shift_type: ShiftType,
|
||||
pub start_time: u64,
|
||||
pub inscriptions: Vec<String>,
|
||||
}
|
||||
impl Shift {
|
||||
pub fn new(shift_type: ShiftType, start_time: u64, inscriptions: Vec<String>) -> Self {
|
||||
Self {
|
||||
shift_type: shift_type,
|
||||
start_time: start_time.into(),
|
||||
inscriptions: inscriptions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Shift {
|
||||
fn fmt(&self, f: &mut Fmt<'_>) -> FmtRes {
|
||||
let start_time = DateTime::from_timestamp(self.start_time as i64, 0)
|
||||
.expect("failed to parse timestamp")
|
||||
.format("%H:%M");
|
||||
write!(
|
||||
f,
|
||||
"{} ({}): à {}, durée {} h, inscrit·es : {}/{}",
|
||||
self.shift_type.name,
|
||||
self.shift_type.priority,
|
||||
start_time,
|
||||
self.shift_type.duration,
|
||||
self.inscriptions.len(),
|
||||
self.shift_type.persons
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_number(record: &RecordsListRecordsInner, column: &str) -> Res<u64> {
|
||||
record
|
||||
.fields
|
||||
.get(column)
|
||||
.ok_or(anyhow!("Failed to get column {}", column))?
|
||||
.as_u64()
|
||||
.ok_or(anyhow!("Failed to parse number"))
|
||||
}
|
||||
|
||||
pub fn get_string(record: &RecordsListRecordsInner, column: &str) -> Res<String> {
|
||||
record
|
||||
.fields
|
||||
.get(column)
|
||||
.ok_or(anyhow!("Failed to get column {}", column))?
|
||||
.as_str()
|
||||
.ok_or(anyhow!("Failed to parse number"))
|
||||
.map(|s| s.into())
|
||||
}
|
||||
|
||||
pub fn get_priority(record: &RecordsListRecordsInner, column: &str) -> Res<Priority> {
|
||||
match get_string(record, column)? {
|
||||
s if s.starts_with("1") => Ok(Priority::Prioritaire),
|
||||
s if s.starts_with("2") => Ok(Priority::Secondaire),
|
||||
s if s.starts_with("3") => Ok(Priority::Bonus),
|
||||
s if s.starts_with("0") => Ok(Priority::Urgent),
|
||||
_ => Err(anyhow!("can't parse priority")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_number_array(record: RecordsListRecordsInner, column: &str) -> Res<Vec<u64>> {
|
||||
let val_col = record
|
||||
.fields
|
||||
.get(column)
|
||||
.ok_or(anyhow!("Failed to get column {}", column))?;
|
||||
if val_col.is_null() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
let mut val_array = val_col
|
||||
.as_array()
|
||||
.ok_or(anyhow!("Failed to parse array"))?
|
||||
.clone();
|
||||
val_array.remove(0);
|
||||
debug!("{:?}", val_array);
|
||||
let mut u64_array = Vec::new();
|
||||
for val in val_array {
|
||||
let u = val.as_u64().ok_or(anyhow!("Failed to parse number"))?;
|
||||
u64_array.push(u);
|
||||
}
|
||||
Ok(u64_array)
|
||||
}
|
||||
|
||||
pub async fn get_records(doc: &str, table: &str) -> Res<RecordsList> {
|
||||
let conf = conf::get();
|
||||
let api_key = conf.grist_api_key();
|
||||
let grist_conf = Configuration::new(API_URL.into(), Some(api_key.into()));
|
||||
list_records(&grist_conf, doc, table, None, None, None, None, None, None)
|
||||
.await
|
||||
.context("Failed to list records")
|
||||
}
|
||||
|
||||
pub async fn get_shift_type(index: u64) -> Res<ShiftType> {
|
||||
let record_list = get_records(SHIFTS_DOC, SHIFT_TYPES_TABLE).await?;
|
||||
let record = record_list
|
||||
.records
|
||||
.iter()
|
||||
.find(|record| record.id == index)
|
||||
.ok_or(anyhow!("can't find shift type"))?;
|
||||
Ok(ShiftType::new(
|
||||
get_string(&record, "Nom")?,
|
||||
get_priority(&record, "Priorite")?,
|
||||
get_number(&record, "Frequence_jour")?,
|
||||
get_number(&record, "Personnes_shift")?,
|
||||
get_number(&record, "Duree_h_")?,
|
||||
get_string(&record, "Guide")?,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn get_inscription(index: u64) -> Res<String> {
|
||||
let record_list = get_records(SHIFTS_DOC, INSCRIPTIONS_TABLE).await?;
|
||||
let record = record_list
|
||||
.records
|
||||
.iter()
|
||||
.find(|record| record.id == index)
|
||||
.ok_or(anyhow!("can't find inscription"))?
|
||||
.clone();
|
||||
let name = get_string(&record, "Personne")?.into();
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
pub async fn list_shifts() -> Res<Vec<Shift>> {
|
||||
let record_list = get_records(SHIFTS_DOC, SHIFTS_TABLE).await?;
|
||||
debug!("{:?}", record_list);
|
||||
let mut shifts: Vec<Shift> = Vec::new();
|
||||
for record in record_list.records {
|
||||
let shift_type_ref =
|
||||
get_number(&record, "Type_de_shift").context("getting shift type ref")?;
|
||||
let shift_type = get_shift_type(shift_type_ref).await?;
|
||||
let start_time = get_number(&record, "Heure_de_debut")?;
|
||||
let inscriptions_refs =
|
||||
get_number_array(record, "Inscriptions").context("getting inscriptions refs")?;
|
||||
// let inscriptions = inscriptions_refs
|
||||
// .iter()
|
||||
// .filter_map(async |i| get_inscription(i).await.ok())
|
||||
// .collect();
|
||||
let mut inscriptions = Vec::new();
|
||||
for inscription_ref in inscriptions_refs {
|
||||
inscriptions.push(get_inscription(inscription_ref).await?);
|
||||
}
|
||||
let shift = Shift::new(shift_type, start_time, inscriptions);
|
||||
shifts.push(shift);
|
||||
}
|
||||
|
||||
Ok(shifts)
|
||||
}
|
||||
|
|
@ -41,6 +41,8 @@ pub mod serve;
|
|||
|
||||
pub mod command;
|
||||
|
||||
pub mod grist;
|
||||
|
||||
prelude! {}
|
||||
|
||||
/// Warmup, runs before anything.
|
||||
|
|
|
|||
10
src/main.rs
10
src/main.rs
|
|
@ -13,7 +13,7 @@ WARNING: The contents of this file may NOT be used to train AI/LLM models. See N
|
|||
details.
|
||||
*/
|
||||
|
||||
use clap::{Parser};
|
||||
use clap::Parser;
|
||||
|
||||
use shift_bot::*;
|
||||
|
||||
|
|
@ -39,6 +39,7 @@ struct Clap {
|
|||
default_value = "shift_bot_dev"
|
||||
)]
|
||||
username: String,
|
||||
|
||||
/// Homeserver.
|
||||
#[arg(
|
||||
short = 's',
|
||||
|
|
@ -47,9 +48,14 @@ struct Clap {
|
|||
default_value = "matrix.org"
|
||||
)]
|
||||
homeserver: String,
|
||||
|
||||
/// Password of the element bot account.
|
||||
#[arg(short, long, value_name = "PASSWORD")]
|
||||
password: String,
|
||||
|
||||
/// Grist API KEY
|
||||
#[arg(short, long)]
|
||||
grist_api_key: String,
|
||||
}
|
||||
|
||||
impl Clap {
|
||||
|
|
@ -69,7 +75,7 @@ impl Clap {
|
|||
async fn run(self) -> Res<()> {
|
||||
simple_logger::init_with_level(self.verb_level())
|
||||
.context("something when wrong while initializing logger")?;
|
||||
conf::set(conf::Main::new());
|
||||
conf::set(conf::Main::new(self.grist_api_key));
|
||||
warmup(self.wipe)?;
|
||||
let bot = MatrixId::new(self.username, self.homeserver);
|
||||
let conf = conf::Serve::new(bot, self.password);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue