this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(relationship): support emoji and description metadata

+129 -26
+21
src/commands/user/relationship/db.rs
··· 10 10 id INTEGER PRIMARY KEY AUTOINCREMENT, 11 11 guild_id INTEGER NOT NULL, 12 12 relationship_type TEXT NOT NULL, 13 + emoji TEXT, 14 + description TEXT, 13 15 status TEXT NOT NULL CHECK(status IN ('active', 'ended')) DEFAULT 'active', 14 16 created_by INTEGER NOT NULL, 15 17 created_at INTEGER NOT NULL, ··· 75 77 ) 76 78 .expect("Failed to create invites relationship index"); 77 79 80 + ensure_relationship_schema(&conn).expect("Failed to migrate relationship schema"); 81 + 78 82 Mutex::new(conn) 79 83 }); 84 + 85 + fn ensure_relationship_schema(conn: &Connection) -> rusqlite::Result<()> { 86 + let mut stmt = conn.prepare("PRAGMA table_info(relationships)")?; 87 + let column_names = stmt 88 + .query_map([], |row| row.get::<_, String>(1))? 89 + .collect::<Result<Vec<_>, _>>()?; 90 + 91 + if !column_names.iter().any(|name| name == "emoji") { 92 + conn.execute("ALTER TABLE relationships ADD COLUMN emoji TEXT", [])?; 93 + } 94 + 95 + if !column_names.iter().any(|name| name == "description") { 96 + conn.execute("ALTER TABLE relationships ADD COLUMN description TEXT", [])?; 97 + } 98 + 99 + Ok(()) 100 + }
+39 -12
src/commands/user/relationship/logic.rs
··· 11 11 pub struct InviteInboxItem { 12 12 pub relationship_id: i64, 13 13 pub relationship_type: String, 14 + pub emoji: Option<String>, 15 + pub description: Option<String>, 14 16 pub inviter_id: u64, 15 17 pub created_at: i64, 16 18 } ··· 19 21 pub struct GroupSummary { 20 22 pub relationship_id: i64, 21 23 pub relationship_type: String, 24 + pub emoji: Option<String>, 25 + pub description: Option<String>, 22 26 pub member_ids: Vec<u64>, 23 27 } 24 28 ··· 55 59 caller_id: u64, 56 60 relationship_type: &str, 57 61 explicit_relationship_id: Option<i64>, 62 + emoji: Option<&str>, 63 + description: Option<&str>, 58 64 ) -> Result<std::result::Result<MakeResolution, String>> { 59 65 if let Some(explicit_id) = explicit_relationship_id { 60 66 let exists_and_allowed = conn ··· 113 119 })); 114 120 } 115 121 116 - let new_id = create_relationship(conn, guild_id, caller_id, relationship_type)?; 122 + let new_id = create_relationship( 123 + conn, 124 + guild_id, 125 + caller_id, 126 + relationship_type, 127 + emoji, 128 + description, 129 + )?; 117 130 add_active_member(conn, new_id, caller_id)?; 118 131 119 132 Ok(Ok(MakeResolution { ··· 292 305 conn: &Connection, 293 306 guild_id: u64, 294 307 user_id: u64, 295 - ) -> Result<Vec<(i64, String)>> { 308 + ) -> Result<Vec<(i64, String, Option<String>, Option<String>)>> { 296 309 let mut stmt = conn.prepare( 297 - "SELECT r.id, r.relationship_type 310 + "SELECT r.id, r.relationship_type, r.emoji, r.description 298 311 FROM relationships r 299 312 INNER JOIN relationship_members m ON m.relationship_id = r.id 300 313 WHERE r.guild_id = ? ··· 309 322 |row| { 310 323 let relationship_id: i64 = row.get(0)?; 311 324 let relationship_type: String = row.get(1)?; 312 - Ok((relationship_id, relationship_type)) 325 + let emoji: Option<String> = row.get(2)?; 326 + let description: Option<String> = row.get(3)?; 327 + Ok((relationship_id, relationship_type, emoji, description)) 313 328 }, 314 329 )?; 315 330 ··· 327 342 user_id: u64, 328 343 ) -> Result<Vec<InviteInboxItem>> { 329 344 let mut stmt = conn.prepare( 330 - "SELECT i.relationship_id, r.relationship_type, i.inviter_id, i.created_at 345 + "SELECT i.relationship_id, r.relationship_type, r.emoji, r.description, i.inviter_id, i.created_at 331 346 FROM relationship_invites i 332 347 INNER JOIN relationships r ON r.id = i.relationship_id 333 348 WHERE r.guild_id = ? ··· 342 357 |row| { 343 358 let relationship_id: i64 = row.get(0)?; 344 359 let relationship_type: String = row.get(1)?; 345 - let inviter_id: i64 = row.get(2)?; 346 - let created_at: i64 = row.get(3)?; 360 + let emoji: Option<String> = row.get(2)?; 361 + let description: Option<String> = row.get(3)?; 362 + let inviter_id: i64 = row.get(4)?; 363 + let created_at: i64 = row.get(5)?; 347 364 Ok(InviteInboxItem { 348 365 relationship_id, 349 366 relationship_type, 367 + emoji, 368 + description, 350 369 inviter_id: inviter_id.cast_unsigned(), 351 370 created_at, 352 371 }) ··· 366 385 guild_id: u64, 367 386 ) -> Result<Vec<GroupSummary>> { 368 387 let mut stmt = conn.prepare( 369 - "SELECT id, relationship_type 388 + "SELECT id, relationship_type, emoji, description 370 389 FROM relationships 371 390 WHERE guild_id = ? 372 391 AND status = 'active' ··· 376 395 let rows = stmt.query_map([guild_id.cast_signed()], |row| { 377 396 let relationship_id: i64 = row.get(0)?; 378 397 let relationship_type: String = row.get(1)?; 379 - Ok((relationship_id, relationship_type)) 398 + let emoji: Option<String> = row.get(2)?; 399 + let description: Option<String> = row.get(3)?; 400 + Ok((relationship_id, relationship_type, emoji, description)) 380 401 })?; 381 402 382 403 let mut groups = Vec::new(); 383 404 for row in rows { 384 - let (relationship_id, relationship_type) = row?; 405 + let (relationship_id, relationship_type, emoji, description) = row?; 385 406 let member_ids = active_member_ids(conn, relationship_id)?; 386 407 groups.push(GroupSummary { 387 408 relationship_id, 388 409 relationship_type, 410 + emoji, 411 + description, 389 412 member_ids, 390 413 }); 391 414 } ··· 423 446 guild_id: u64, 424 447 created_by: u64, 425 448 relationship_type: &str, 449 + emoji: Option<&str>, 450 + description: Option<&str>, 426 451 ) -> Result<i64> { 427 452 conn.execute( 428 - "INSERT INTO relationships (guild_id, relationship_type, status, created_by, created_at) 429 - VALUES (?, ?, 'active', ?, ?)", 453 + "INSERT INTO relationships (guild_id, relationship_type, emoji, description, status, created_by, created_at) 454 + VALUES (?, ?, ?, ?, 'active', ?, ?)", 430 455 params![ 431 456 guild_id.cast_signed(), 432 457 relationship_type, 458 + emoji, 459 + description, 433 460 created_by.cast_signed(), 434 461 now_ts(), 435 462 ],
+69 -14
src/commands/user/relationship/mod.rs
··· 16 16 }; 17 17 use reply::safe_reply; 18 18 19 + fn trim_optional_text(input: Option<String>) -> Option<String> { 20 + input.and_then(|value| { 21 + let trimmed = value.trim().to_string(); 22 + if trimmed.is_empty() { 23 + None 24 + } else { 25 + Some(trimmed) 26 + } 27 + }) 28 + } 29 + 30 + fn format_group_header( 31 + relationship_type: &str, 32 + emoji: Option<&str>, 33 + description: Option<&str>, 34 + ) -> String { 35 + let mut base = String::new(); 36 + if let Some(emoji) = emoji { 37 + base.push_str(emoji); 38 + base.push(' '); 39 + } 40 + base.push('`'); 41 + base.push_str(relationship_type); 42 + base.push('`'); 43 + if let Some(description) = description { 44 + base.push_str(" - "); 45 + base.push_str(description); 46 + } 47 + base 48 + } 49 + 19 50 #[poise::command( 20 51 slash_command, 21 52 guild_only, ··· 36 67 "**How work**", 37 68 "- Relationship groups are identified by an ID (like `#12`).", 38 69 "- Types are free-form like `marriage`, `friend`, `adopted-sibling`.", 70 + "- Groups can have optional emoji + description metadata.", 39 71 "- `make` sends an invite. The other user must `accept`.", 40 72 "", 41 73 "**How to use**", ··· 44 76 "3. Accept with `/relationship accept relationship_id:<id>`", 45 77 "", 46 78 "**Commands**", 47 - "- `/relationship make <type> <user> [relationship_id]`: create/invite.", 79 + "- `/relationship make <type> <user> [relationship_id] [emoji] [description]`: create/invite.", 48 80 "- `/relationship inbox`: show your pending invites.", 49 81 "- `/relationship accept <relationship_id>`: join invited group.", 50 82 "- `/relationship decline <relationship_id>`: decline invite.", ··· 73 105 #[description = "Existing relationship ID to invite into (optional)"] relationship_id: Option< 74 106 i64, 75 107 >, 108 + #[description = "Optional emoji for new group (e.g. 💍)"] emoji: Option<String>, 109 + #[description = "Optional description for new group"] description: Option<String>, 76 110 ) -> Result<()> { 77 111 let Some(guild_id) = ctx.guild_id() else { 78 112 ctx.send( ··· 116 150 } 117 151 }; 118 152 153 + let emoji = trim_optional_text(emoji); 154 + let description = trim_optional_text(description); 155 + 119 156 let relationship_result = { 120 157 let conn = RELATIONSHIP_DB.lock().unwrap(); 121 158 resolve_relationship_for_make( ··· 124 161 caller_id, 125 162 &normalized_type, 126 163 relationship_id, 164 + emoji.as_deref(), 165 + description.as_deref(), 127 166 )? 128 167 }; 129 168 ··· 440 479 return Ok(()); 441 480 } 442 481 443 - let mut grouped: BTreeMap<String, Vec<i64>> = BTreeMap::new(); 444 - for (relationship_id, relationship_type) in &relationships { 445 - grouped 446 - .entry(relationship_type.clone()) 447 - .or_default() 448 - .push(*relationship_id); 482 + let mut grouped: BTreeMap<String, Vec<(i64, Option<String>, Option<String>)>> = BTreeMap::new(); 483 + for (relationship_id, relationship_type, emoji, description) in &relationships { 484 + grouped.entry(relationship_type.clone()).or_default().push(( 485 + *relationship_id, 486 + emoji.clone(), 487 + description.clone(), 488 + )); 449 489 } 450 490 451 491 let mut lines = vec![format!("**Active relationships for <@{target_id}>**")]; 452 492 453 493 { 454 494 let conn = RELATIONSHIP_DB.lock().unwrap(); 455 - for (relationship_type, ids) in grouped { 495 + for (relationship_type, rows) in grouped { 456 496 lines.push(format!("\n`{relationship_type}`")); 457 - for relationship_id in ids { 497 + for (relationship_id, emoji, description) in rows { 458 498 let members = active_member_ids(&conn, relationship_id)?; 459 499 let member_mentions = members 460 500 .iter() ··· 462 502 .collect::<Vec<_>>() 463 503 .join(", "); 464 504 465 - lines.push(format!("- #{relationship_id}: {member_mentions}")); 505 + let header = format_group_header( 506 + &relationship_type, 507 + emoji.as_deref(), 508 + description.as_deref(), 509 + ); 510 + lines.push(format!("- #{relationship_id} {header}: {member_mentions}")); 466 511 } 467 512 } 468 513 } ··· 502 547 503 548 let mut lines = vec!["**Your pending relationship invites**".to_string()]; 504 549 for invite in invites { 550 + let header = format_group_header( 551 + &invite.relationship_type, 552 + invite.emoji.as_deref(), 553 + invite.description.as_deref(), 554 + ); 505 555 lines.push(format!( 506 - "- `#{}` `{}` from <@{}> (created <t:{}:R>)\n Accept: `/relationship accept relationship_id:{}` | Decline: `/relationship decline relationship_id:{}`", 556 + "- `#{}` {} from <@{}> (created <t:{}:R>)\n Accept: `/relationship accept relationship_id:{}` | Decline: `/relationship decline relationship_id:{}`", 507 557 invite.relationship_id, 508 - invite.relationship_type, 558 + header, 509 559 invite.inviter_id, 510 560 invite.created_at, 511 561 invite.relationship_id, ··· 551 601 .collect::<Vec<_>>() 552 602 .join(", "); 553 603 604 + let header = format_group_header( 605 + &group.relationship_type, 606 + group.emoji.as_deref(), 607 + group.description.as_deref(), 608 + ); 554 609 lines.push(format!( 555 - "- `#{}` `{}`: {}", 556 - group.relationship_id, group.relationship_type, members 610 + "- `#{}` {}: {}", 611 + group.relationship_id, header, members 557 612 )); 558 613 } 559 614