diff --git a/Cargo.lock b/Cargo.lock index cb443e1..90f03d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2728,6 +2728,18 @@ dependencies = [ "serde", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "2.3.0" @@ -3483,6 +3495,15 @@ dependencies = [ "foldhash", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "headers" version = "0.3.9" @@ -4260,6 +4281,17 @@ dependencies = [ "redox_syscall 0.7.0", ] +[[package]] +name = "libsqlite3-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libxdo" version = "0.6.0" @@ -4430,6 +4462,7 @@ dependencies = [ "futures", "mize", "reqwest 0.13.2", + "rusqlite", "rustyscript", "serde", "serde_json", @@ -6036,6 +6069,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rusqlite" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" +dependencies = [ + "bitflags 2.11.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-hash" version = "1.1.0" diff --git a/packages/marts/Cargo.toml b/packages/marts/Cargo.toml index c8a15fc..9815466 100644 --- a/packages/marts/Cargo.toml +++ b/packages/marts/Cargo.toml @@ -17,10 +17,11 @@ chrono = "0.4.44" reqwest = { version = "0.13.2", features = ["blocking", "json"] } serde = "1.0.228" serde_json = "1.0.149" +rusqlite = { version = "0.31", features = ["bundled"], optional = true } [features] default = ["target-os"] -target-os = [ "clap", "mize/target-os", "deno_core", "rustyscript" ] +target-os = [ "clap", "mize/target-os", "deno_core", "rustyscript", "rusqlite" ] target-wasm = [ "mize/target-wasm" ] clap = ["dep:clap"] diff --git a/packages/marts/c2vi/mod.rs b/packages/marts/c2vi/mod.rs index 28ea813..7f15a00 100644 --- a/packages/marts/c2vi/mod.rs +++ b/packages/marts/c2vi/mod.rs @@ -1,125 +1,66 @@ -use chrono::{Datelike, Timelike, Utc}; +use std::sync::Mutex; + +use chrono::{Timelike, Utc}; use clap::{Arg, Command}; -use rusqlite::{params, Connection, Result}; -use serde::{Deserialize, Serialize}; -use std::env; -use std::fs; -use std::path::PathBuf; -use std::process::Command as StdCommand; +use rusqlite::{params, Connection}; +use serde_json::{json, Value}; -// Assuming PPC and other modules are structured like this. -// This might need adjustment based on the actual definitions in other files. -use crate::habitica::Habitica; // Assuming this exists +use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizeResult}; +use crate::cli::CliPart; +use crate::habitica::Habitica; + +#[mize_part] +#[derive(Default)] pub struct C2vi { - pub db: Connection, + mize: Mize, + db: Mutex>, } -impl C2vi { - pub fn new(local_storage_path: &str) -> Result { - let db_path = PathBuf::from(local_storage_path).join("data.db"); - let conn = Connection::open(db_path)?; - Ok(C2vi { db: conn }) +impl MizePart for C2vi { + fn opts(&self, mize: &mut Mize) { + mize.new_opt("c2vi.local_storage_path"); } } -pub fn extend_cli_c2vi(mut cli: Command) -> Command { - cli.subcommand( - Command::new("bed") - .about("Command to run when c2vi goes to bed") - .action(async || { - // Placeholder for actual PPC context - // await save_habitica_dailies(ppc); - // await save_habitica_logs(ppc); - }), - ) - .subcommand( - Command::new("clearTodos") - .about("Clear all todos in Habitica") - .alias("clt") - .action(async || { - // Placeholder - }), - ) - .subcommand( - Command::new("listTodos") - .about("List all todos in Habitica") - .alias("dut") - .action(async || { - // Placeholder - }), - ) - .subcommand( - Command::new("buyHealthPotion") - .about("Buy a health potion in Habitica") - .alias("buyh") - .action(async || { - // Placeholder - }), - ) - .subcommand( - Command::new("addActionItems") - .about("Add action items from a temp file") - .alias("aal") - .action(async || { - // add_action_items(); - }), - ) - .subcommand( - Command::new("skip") - .about("Skip a Habitica todo") - .alias("sk") - .arg(Arg::new("num").default_value("1")) - .action(async || { - // let num = matches.value_of("num").unwrap().parse::().unwrap(); - // skip_habitica_todo(ppc, num); - }), - ) - .subcommand( - Command::new("dumpLog") - .about("Dump the complete Habitica log") - .action(async || { - // dump_habitica_logs(ppc); - }), - ) - .subcommand( - Command::new("listDailies") - .about("List all dailies from the local db") - .action(async || { - // Placeholder - }), - ) - .subcommand( - Command::new("printTask") - .about("Print a specific todo from Habitica") - .arg(Arg::new("id").required(true)) - .action(async || { - // let id = matches.value_of("id").unwrap(); - // Placeholder - }), - ) +impl C2vi { + fn with_db(&self, f: F) -> MizeResult + where + F: FnOnce(&Connection) -> MizeResult, + { + let guard = self + .db + .lock() + .map_err(|e| mize_err!("DB lock poisoned: {}", e))?; + let conn = guard + .as_ref() + .ok_or_else(|| mize_err!("DB not initialized"))?; + f(conn) + } } -fn format_date(date: chrono::DateTime) -> String { - date.format("%Y-%m-%d").to_string() +pub fn c2vi(mize: &mut Mize) -> MizeResult<()> { + let storage_path = mize.get_config("c2vi.local_storage_path")?.to_string(); + let db_path = std::path::PathBuf::from(&storage_path).join("data.db"); + let conn = Connection::open(&db_path) + .map_err(|e| mize_err!("Failed to open C2vi database at {:?}: {}", db_path, e))?; + + setup_tables(&conn)?; + + let c2vi_part = C2vi { + mize: mize.clone(), + db: Mutex::new(Some(conn)), + }; + mize.register_part(Box::new(c2vi_part))?; + + // Register CLI subcommands + let mut cli = mize.get_part_native::("cli")?; + register_subcommands(&mut cli); + + Ok(()) } -#[derive(Serialize, Deserialize)] -struct HabiticaDaily { - id: String, - text: String, - notes: String, - priority: Option, - frequency: String, -} - -#[derive(Serialize, Deserialize)] -struct HabiticaLog { - date: String, - data: String, // JSON string -} - -fn setup_tables(conn: &Connection) -> Result<()> { +fn setup_tables(conn: &Connection) -> MizeResult<()> { conn.execute( "CREATE TABLE IF NOT EXISTS habitica_dailies ( id TEXT PRIMARY KEY, @@ -129,97 +70,525 @@ fn setup_tables(conn: &Connection) -> Result<()> { frequency TEXT )", [], - )?; + ) + .map_err(|e| mize_err!("DB error creating habitica_dailies: {}", e))?; conn.execute( "CREATE TABLE IF NOT EXISTS habitica_log ( date TEXT PRIMARY KEY, data TEXT NOT NULL )", [], - )?; + ) + .map_err(|e| mize_err!("DB error creating habitica_log: {}", e))?; Ok(()) } -// In-progress translation. Many functions will need the PPC context passed in. +fn register_subcommands(cli: &mut CliPart) { + cli.subcommand( + Command::new("bed").about("Command to run when c2vi goes to bed"), + |_matches, mut mize| { + save_habitica_dailies(&mut mize)?; + save_habitica_logs(&mut mize)?; + Ok(()) + }, + ); -/* -async fn save_habitica_dailies(ppc: &PPC) -> Result<()> { - println!("######### cli bed cmd #########"); - setup_tables(&ppc.c2vi.db)?; + cli.subcommand( + Command::new("clearTodos") + .about("Clear all Habitica todos") + .alias("clt"), + |_matches, mut mize| { + let todos = { + let mut hab = mize.get_part_native::("habitica")?; + hab.get_tasks("todos")? + }; + let mut hab = mize.get_part_native::("habitica")?; + if let Some(arr) = todos.as_array() { + for todo in arr { + if let Some(id) = todo["id"].as_str() { + hab.delete_task(id)?; + } + } + } + Ok(()) + }, + ); - // Assuming ppc.habitica.get_tasks returns a Result - let dailies = ppc.habitica.get_tasks("dailys").await?; - write_dailies_to_database(&dailies, &ppc.c2vi.db).await?; - Ok(()) + cli.subcommand( + Command::new("listTodos") + .about("List all Habitica todos") + .alias("dut"), + |_matches, mut mize| { + let mut hab = mize.get_part_native::("habitica")?; + let todos = hab.get_tasks("todos")?; + if let Some(arr) = todos.as_array() { + for todo in arr { + if let Some(text) = todo["text"].as_str() { + println!("{}", text); + } + } + } + Ok(()) + }, + ); + + cli.subcommand( + Command::new("buyHealthPotion") + .about("Buy a health potion") + .alias("buyh"), + |_matches, mut mize| { + let mut hab = mize.get_part_native::("habitica")?; + hab.api_request( + reqwest::Method::POST, + "user/buy-health-potion".to_string(), + json!({}), + )?; + Ok(()) + }, + ); + + cli.subcommand( + Command::new("addActionItems") + .about("Add action items from temp file") + .alias("aal"), + |_matches, mut mize| add_action_items(&mut mize), + ); + + cli.subcommand( + Command::new("skip") + .about("Skip a Habitica todo") + .alias("sk") + .arg(Arg::new("num").default_value("1")), + |matches, mut mize| { + let num: usize = matches + .get_one::("num") + .unwrap() + .parse() + .unwrap_or(1); + skip_habitica_todo(&mut mize, num) + }, + ); + + cli.subcommand( + Command::new("dumpLog").about("Dump the complete Habitica log"), + |_matches, mut mize| dump_habitica_logs(&mut mize), + ); + + cli.subcommand( + Command::new("listDailies").about("List all dailies from local DB"), + |_matches, mut mize| { + let c2vi = mize.get_part_native::("c2vi")?; + c2vi.with_db(|conn| { + let mut stmt = conn + .prepare("SELECT id, text FROM habitica_dailies") + .map_err(|e| mize_err!("DB error: {}", e))?; + let rows = stmt + .query_map([], |row| { + Ok(( + row.get::<_, String>(0)?, + row.get::<_, String>(1)?, + )) + }) + .map_err(|e| mize_err!("DB error: {}", e))?; + for row in rows { + let (id, text) = row.map_err(|e| mize_err!("DB error: {}", e))?; + println!("{}: {}", id, text); + } + Ok(()) + }) + }, + ); + + cli.subcommand( + Command::new("printTask") + .about("Print a specific task from Habitica") + .arg(Arg::new("id").required(true)), + |matches, mut mize| { + let id = matches.get_one::("id").unwrap(); + let mut hab = mize.get_part_native::("habitica")?; + let data = hab.api_request( + reqwest::Method::GET, + format!("tasks/{}", id), + json!({}), + )?; + if let Some(history) = data.get("history") { + println!("{}", serde_json::to_string_pretty(history).unwrap_or_default()); + } else { + println!("{}", serde_json::to_string_pretty(&data).unwrap_or_default()); + } + Ok(()) + }, + ); } -async fn write_dailies_to_database(dailies: &[HabiticaTask], db: &Connection) -> Result<()> { - println!("Syncing {} dailies to database...", dailies.len()); - let tx = db.transaction()?; - for daily in dailies { - tx.execute( - "INSERT INTO habitica_dailies (id, text, notes, priority, frequency) - VALUES (?1, ?2, ?3, ?4, ?5) - ON CONFLICT(id) DO UPDATE SET - text=excluded.text, - notes=excluded.notes, - priority=excluded.priority, - frequency=excluded.frequency", - params![ - daily.id, - daily.text, - daily.notes.as_deref().unwrap_or(""), - daily.priority.map(|p| p.to_string()), - daily.frequency.as_deref().unwrap_or("daily"), - ], - )?; +// ============= Business Logic ============= + +fn format_date(dt: chrono::DateTime) -> String { + dt.format("%Y-%m-%d").to_string() +} + +/// Get the target date, adjusted for "save for yesterday" logic (before noon = yesterday) +fn get_target_date() -> (String, bool) { + let now = Utc::now(); + let save_for_yesterday = now.hour() < 12; + let date = if save_for_yesterday { + now - chrono::Duration::days(1) + } else { + now + }; + if save_for_yesterday { + println!( + "Saving logs for yesterday because it is {} hours of the day", + now.hour() + ); } - tx.commit() + (format_date(date), save_for_yesterday) } -async fn add_action_items(ppc: &PPC) -> Result<()> { - let initial_content = " +/// Load existing log entry from DB, or return a default empty one +fn get_or_create_log(conn: &Connection, date: &str) -> MizeResult { + let mut stmt = conn + .prepare("SELECT data FROM habitica_log WHERE date = ?1") + .map_err(|e| mize_err!("DB error: {}", e))?; + let result: Option = stmt + .query_row(params![date], |row| row.get(0)) + .ok(); + match result { + Some(data_str) => { + serde_json::from_str(&data_str).map_err(|e| mize_err!("JSON parse error: {}", e)) + } + None => Ok(json!({ + "dailies_done": [], + "dailies_skipped": [], + "todos_done": [], + "todos_skipped": [] + })), + } +} + +/// Upsert a log entry into the database +fn upsert_log(conn: &Connection, date: &str, data: &Value) -> MizeResult<()> { + let data_str = + serde_json::to_string(data).map_err(|e| mize_err!("JSON serialize error: {}", e))?; + conn.execute( + "INSERT INTO habitica_log (date, data) VALUES (?1, ?2) + ON CONFLICT(date) DO UPDATE SET data=excluded.data", + params![date, data_str], + ) + .map_err(|e| mize_err!("DB error upserting log: {}", e))?; + Ok(()) +} + +fn save_habitica_dailies(mize: &mut Mize) -> MizeResult<()> { + println!("######### cli bed cmd #########"); + + // Fetch dailies from Habitica + let dailies = { + let mut hab = mize.get_part_native::("habitica")?; + hab.get_tasks("dailys")? + }; + + // Write to database + let c2vi = mize.get_part_native::("c2vi")?; + c2vi.with_db(|conn| { + if let Some(arr) = dailies.as_array() { + println!("Syncing {} dailies to database...", arr.len()); + for daily in arr { + let id = daily["id"].as_str().unwrap_or(""); + let text = daily["text"].as_str().unwrap_or(""); + let notes = daily["notes"].as_str().unwrap_or(""); + let priority = daily["priority"].to_string(); + let frequency = daily["frequency"].as_str().unwrap_or("daily"); + + conn.execute( + "INSERT INTO habitica_dailies (id, text, notes, priority, frequency) + VALUES (?1, ?2, ?3, ?4, ?5) + ON CONFLICT(id) DO UPDATE SET + text=excluded.text, + notes=excluded.notes, + priority=excluded.priority, + frequency=excluded.frequency", + params![id, text, notes, priority, frequency], + ) + .map_err(|e| mize_err!("DB error writing daily: {}", e))?; + } + } + Ok(()) + }) +} + +fn save_habitica_logs(mize: &mut Mize) -> MizeResult<()> { + let (date_str, save_for_yesterday) = get_target_date(); + + // Fetch dailies and completed todos from Habitica + let (dailies, completed_todos) = { + let mut hab = mize.get_part_native::("habitica")?; + let dailies = hab.get_tasks("dailys")?; + let completed_todos = hab.get_tasks("completedTodos")?; + (dailies, completed_todos) + }; + + let c2vi = mize.get_part_native::("c2vi")?; + c2vi.with_db(|conn| { + let mut data = get_or_create_log(conn, &date_str)?; + + // Process dailies + if let Some(arr) = dailies.as_array() { + for daily in arr { + let is_due = if save_for_yesterday { + // Look at history for yesterday's status + daily["history"] + .as_array() + .and_then(|h| h.iter().max_by_key(|e| e["date"].as_i64().unwrap_or(0))) + .map(|e| e["isDue"].as_bool().unwrap_or(false)) + .unwrap_or(false) + } else { + daily["isDue"].as_bool().unwrap_or(false) + }; + + if !is_due { + continue; + } + + let completed = if save_for_yesterday { + daily["history"] + .as_array() + .and_then(|h| h.iter().max_by_key(|e| e["date"].as_i64().unwrap_or(0))) + .map(|e| e["completed"].as_bool().unwrap_or(false)) + .unwrap_or(false) + } else { + daily["completed"].as_bool().unwrap_or(false) + }; + + let daily_id = daily["id"].as_str().unwrap_or(""); + let daily_text = daily["text"].as_str().unwrap_or(""); + + if completed { + data["dailies_done"] + .as_array_mut() + .unwrap() + .push(json!(daily_id)); + println!("daily-done: {}", daily_text); + } else { + data["dailies_skipped"] + .as_array_mut() + .unwrap() + .push(json!(daily_id)); + println!("daily-skipped: {}", daily_text); + } + } + } + + // Process completed todos + if let Some(arr) = completed_todos.as_array() { + let today_str = format_date(Utc::now()); + for todo in arr { + let date_completed_str = todo["dateCompleted"].as_str().unwrap_or(""); + + // Parse the completion date (ISO 8601 format from Habitica) + let date_completed = chrono::DateTime::parse_from_rfc3339(date_completed_str) + .map(|d| format_date(d.with_timezone(&Utc))) + .unwrap_or_default(); + + // Check if already in the log for this date + if save_for_yesterday { + let existing_todos = data["todos_done"].as_array().unwrap(); + let todo_id = todo["id"].as_str().unwrap_or(""); + if existing_todos.iter().any(|t| t["id"].as_str() == Some(todo_id)) { + continue; + } + } + + let todo_entry = json!({ + "id": todo["id"], + "text": todo["text"], + "notes": todo.get("notes").unwrap_or(&json!("")), + "checklist": todo.get("checklist").unwrap_or(&json!([])), + "tags": todo.get("tags").unwrap_or(&json!([])), + }); + + // Save if completed on the target date + if date_completed == date_str { + println!("todo-done: {}", todo["text"].as_str().unwrap_or("")); + data["todos_done"] + .as_array_mut() + .unwrap() + .push(todo_entry.clone()); + } + + // If saving for yesterday but todo was completed today, also record it + if save_for_yesterday && date_completed == today_str { + println!("todo-done: {}", todo["text"].as_str().unwrap_or("")); + data["todos_done"] + .as_array_mut() + .unwrap() + .push(todo_entry); + } + } + } + + upsert_log(conn, &date_str, &data)?; + Ok(()) + }) +} + +fn skip_habitica_todo(mize: &mut Mize, num: usize) -> MizeResult<()> { + let (date_str, _) = get_target_date(); + + // Fetch todos from Habitica + let todos = { + let mut hab = mize.get_part_native::("habitica")?; + hab.get_tasks("todos")? + }; + + let arr = todos.as_array().ok_or_else(|| mize_err!("Expected array of todos"))?; + + // Delete and log each skipped todo + for i in 0..num { + let todo = match arr.get(i) { + Some(t) => t, + None => continue, + }; + + let todo_id = todo["id"].as_str().unwrap_or(""); + + // Delete the todo and score the skip habit + { + let mut hab = mize.get_part_native::("habitica")?; + hab.delete_task(todo_id)?; + } + increment_habit(mize)?; + + // Record in log + let c2vi = mize.get_part_native::("c2vi")?; + c2vi.with_db(|conn| { + let mut data = get_or_create_log(conn, &date_str)?; + data["todos_skipped"].as_array_mut().unwrap().push(json!({ + "id": todo["id"], + "text": todo["text"], + "notes": todo.get("notes").unwrap_or(&json!("")), + "checklist": todo.get("checklist").unwrap_or(&json!([])), + "tags": todo.get("tags").unwrap_or(&json!([])), + })); + upsert_log(conn, &date_str, &data)?; + Ok(()) + })?; + } + + Ok(()) +} + +fn increment_habit(mize: &mut Mize) -> MizeResult<()> { + let mut hab = mize.get_part_native::("habitica")?; + let habits = hab.get_tasks("habits")?; + if let Some(arr) = habits.as_array() { + for habit in arr { + if habit["text"].as_str() == Some("skipped a planned task") { + let id = habit["id"].as_str().unwrap_or(""); + hab.api_request( + reqwest::Method::POST, + format!("tasks/{}/score/down", id), + json!({}), + )?; + return Ok(()); + } + } + } + Ok(()) +} + +fn add_action_items(mize: &mut Mize) -> MizeResult<()> { + let initial_content = "\ gu 10min TIMER uw 15min FROG b coffee/tee/kakau uw FROG b breakfast, brush teeth, lüften uw FROG - + lunch - + uw 15min TIMER of next days FROG fw dt 30min TIMER - + fun 20:00 (smth that can be stopped easily) se 21:40 read -bed 22:10 -"; - let tmp_file = env::temp_dir().join(format!("habitica-tasks-{}.md", Utc::now().timestamp())); - fs::write(&tmp_file, initial_content.trim())?; +bed 22:10"; - StdCommand::new("nvim").arg(&tmp_file).status()?; + let tmp_file = std::env::temp_dir().join(format!( + "habitica-tasks-{}.md", + Utc::now().timestamp() + )); + std::fs::write(&tmp_file, initial_content) + .map_err(|e| mize_err!("Failed to write temp file: {}", e))?; - let list_str = fs::read_to_string(&tmp_file)?; - fs::remove_file(&tmp_file)?; + // Open in nvim for editing + std::process::Command::new("nvim") + .arg(&tmp_file) + .status() + .map_err(|e| mize_err!("Failed to run nvim: {}", e))?; - let tasks: Vec<&str> = list_str.lines().map(|t| t.trim()).filter(|t| !t.is_empty()).collect(); - let mut task_ids = vec![]; + // Read back and parse + let list_str = + std::fs::read_to_string(&tmp_file).map_err(|e| mize_err!("Failed to read temp file: {}", e))?; + let _ = std::fs::remove_file(&tmp_file); - for todo_text in tasks { - // Assuming api_request exists and works like this - let data = ppc.habitica.api_request("POST", "tasks/user", Some(serde_json::json!({ "text": todo_text, "type": "todo" }))).await?; - let task_id = data["id"].as_str().unwrap().to_string(); - println!("Added to-do: '{}' with id '{}'", todo_text, task_id); - task_ids.push(task_id); - } + let tasks: Vec<&str> = list_str + .lines() + .map(|l| l.trim()) + .filter(|l| !l.is_empty()) + .collect(); - for task_id in task_ids { - ppc.habitica.api_request("POST", &format!("tasks/{}/move/to/-1", task_id), None).await?; + // Add tasks to Habitica + let mut task_ids = Vec::new(); + { + let mut hab = mize.get_part_native::("habitica")?; + for todo_text in &tasks { + let data = hab.api_request( + reqwest::Method::POST, + "tasks/user".to_string(), + json!({ "text": todo_text, "type": "todo" }), + )?; + let id = data["id"].as_str().unwrap_or("").to_string(); + println!("Added to-do: '{}' with id '{}'", todo_text, id); + task_ids.push(id); + } + + // Move all tasks to bottom + for task_id in &task_ids { + hab.api_request( + reqwest::Method::POST, + format!("tasks/{}/move/to/-1", task_id), + json!({}), + )?; + } } Ok(()) } -*/ + +fn dump_habitica_logs(mize: &mut Mize) -> MizeResult<()> { + let c2vi = mize.get_part_native::("c2vi")?; + c2vi.with_db(|conn| { + let mut stmt = conn + .prepare("SELECT date, data FROM habitica_log ORDER BY date") + .map_err(|e| mize_err!("DB error: {}", e))?; + let rows = stmt + .query_map([], |row| { + Ok(( + row.get::<_, String>(0)?, + row.get::<_, String>(1)?, + )) + }) + .map_err(|e| mize_err!("DB error: {}", e))?; + for row in rows { + let (date, data_str) = row.map_err(|e| mize_err!("DB error: {}", e))?; + let data: Value = + serde_json::from_str(&data_str).unwrap_or(json!({"parse_error": data_str})); + println!("{}: {}", date, serde_json::to_string_pretty(&data).unwrap_or_default()); + } + Ok(()) + }) +} diff --git a/packages/marts/habitica.rs b/packages/marts/habitica.rs index c890261..a999fde 100644 --- a/packages/marts/habitica.rs +++ b/packages/marts/habitica.rs @@ -20,10 +20,17 @@ pub fn habitica(mize: &mut Mize) -> MizeResult<()> { })) } -impl MizePart for Habitica {} +impl MizePart for Habitica { + fn opts(&self, mize: &mut Mize) { + mize.new_opt("habitica.api_url"); + mize.new_opt("habitica.user_id"); + mize.new_opt("habitica.api_token"); + mize.new_opt("habitica.client_name"); + } +} impl Habitica { - fn api_request(&mut self, method: Method, path: String, data: Value) -> MizeResult { + pub fn api_request(&mut self, method: Method, path: String, data: Value) -> MizeResult { let api_url = self.mize.get_config("habitica.api_url")?.to_string(); let user_id = self.mize.get_config("habitica.user_id")?.to_string(); let api_token = self.mize.get_config("habitica.api_token")?.to_string(); @@ -65,6 +72,18 @@ impl Habitica { let json_response: Value = response.json()?; Ok(json_response.get("data").cloned().unwrap_or(Value::Null)) } + + pub fn get_tasks(&mut self, task_type: &str) -> MizeResult { + self.api_request( + Method::GET, + format!("tasks/user?type={}", task_type), + json!({}), + ) + } + + pub fn delete_task(&mut self, id: &str) -> MizeResult { + self.api_request(Method::DELETE, format!("tasks/{}", id), json!({})) + } } fn handle_rate_limit(response: &Response) { diff --git a/packages/marts/lib.rs b/packages/marts/lib.rs index 7b947fd..3d4fd8d 100644 --- a/packages/marts/lib.rs +++ b/packages/marts/lib.rs @@ -10,5 +10,10 @@ pub mod habitica; #[cfg(feature = "target-os")] pub use habitica::*; +#[cfg(feature = "target-os")] +pub mod c2vi; +#[cfg(feature = "target-os")] +pub use c2vi::*; + pub mod js; pub use js::*; diff --git a/packages/ppc/platform/os/main.rs b/packages/ppc/platform/os/main.rs index b7e20de..b96176e 100644 --- a/packages/ppc/platform/os/main.rs +++ b/packages/ppc/platform/os/main.rs @@ -18,6 +18,7 @@ fn os_main(mize: &mut Mize) -> MizeResult<()> { marts::cli(mize)?; //marts::js(mize)?; marts::habitica(mize)?; + marts::c2vi(mize)?; let mut cli = mize.get_part_native::("cli")?;