Compare commits
2 Commits
f140f8f75c
...
028bdfa4f9
| Author | SHA1 | Date | |
|---|---|---|---|
| 028bdfa4f9 | |||
| a395f221cc |
@@ -1,12 +1,11 @@
|
|||||||
"""
|
|
||||||
Medications command handler - bot-side hooks for medication management
|
|
||||||
"""
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from bot.command_registry import register_module
|
from bot.command_registry import register_module
|
||||||
import ai.parser as ai_parser
|
import ai.parser as ai_parser
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
|
||||||
def _get_nearest_scheduled_time(times, user_tz=None):
|
def _get_nearest_scheduled_time(times, user_tz=None):
|
||||||
@@ -14,7 +13,7 @@ def _get_nearest_scheduled_time(times, user_tz=None):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
times: List of time strings like ["08:00", "20:00"]
|
times: List of time strings like ["08:00", "20:00"]
|
||||||
user_tz: pytz timezone object for the user
|
user_tz: pytz timezone object or offset in minutes
|
||||||
|
|
||||||
Returns the time as HH:MM string, or None if no times provided.
|
Returns the time as HH:MM string, or None if no times provided.
|
||||||
"""
|
"""
|
||||||
@@ -25,6 +24,13 @@ def _get_nearest_scheduled_time(times, user_tz=None):
|
|||||||
if user_tz is None:
|
if user_tz is None:
|
||||||
# Default to UTC if no timezone provided
|
# Default to UTC if no timezone provided
|
||||||
user_tz = timezone.utc
|
user_tz = timezone.utc
|
||||||
|
elif isinstance(user_tz, int):
|
||||||
|
# If user_tz is an offset in minutes, convert to timezone object
|
||||||
|
# Positive offset means behind UTC, so we need to use Etc/GMT+N
|
||||||
|
# where N = -offset_minutes/60 (because Etc/GMT+5 means 5 hours behind UTC)
|
||||||
|
offset_hours = -user_tz // 60
|
||||||
|
user_tz = pytz.timezone(f"Etc/GMT+{offset_hours}")
|
||||||
|
|
||||||
now = datetime.now(user_tz)
|
now = datetime.now(user_tz)
|
||||||
now_minutes = now.hour * 60 + now.minute
|
now_minutes = now.hour * 60 + now.minute
|
||||||
|
|
||||||
@@ -51,10 +57,6 @@ def _get_nearest_scheduled_time(times, user_tz=None):
|
|||||||
|
|
||||||
# If no time within window, use the most recent past time
|
# If no time within window, use the most recent past time
|
||||||
if best_time is None:
|
if best_time is None:
|
||||||
# Get current time in user's timezone again for reference
|
|
||||||
now = datetime.now(user_tz)
|
|
||||||
now_minutes = now.hour * 60 + now.minute
|
|
||||||
|
|
||||||
# Find the most recent past time
|
# Find the most recent past time
|
||||||
past_times = []
|
past_times = []
|
||||||
for time_str in times:
|
for time_str in times:
|
||||||
@@ -76,56 +78,15 @@ def _get_nearest_scheduled_time(times, user_tz=None):
|
|||||||
past_times.sort()
|
past_times.sort()
|
||||||
best_time = past_times[-1][1]
|
best_time = past_times[-1][1]
|
||||||
|
|
||||||
return best_time
|
# Fallback: if still no time found, use the first scheduled time
|
||||||
if not best_time:
|
|
||||||
for time_str in times:
|
|
||||||
try:
|
|
||||||
hour, minute = map(int, time_str.split(":"))
|
|
||||||
time_minutes = hour * 60 + minute
|
|
||||||
|
|
||||||
# If this time was earlier today, it's a candidate
|
|
||||||
if time_minutes <= now_minutes:
|
|
||||||
diff = now_minutes - time_minutes
|
|
||||||
if diff < best_diff:
|
|
||||||
best_diff = diff
|
|
||||||
best_time = time_str
|
|
||||||
except (ValueError, AttributeError):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If still no time found, use the first scheduled time
|
|
||||||
if not best_time and times:
|
if not best_time and times:
|
||||||
best_time = times[0]
|
best_time = times[0]
|
||||||
|
|
||||||
return best_time
|
return best_time
|
||||||
|
|
||||||
|
|
||||||
async def _get_user_timezone(bot, user_id):
|
async def _get_user_timezone(message, session, token):
|
||||||
"""Check if user has timezone set, ask for it if not.
|
"""Get user's timezone offset. Returns offset_minutes or None if not set."""
|
||||||
|
|
||||||
Returns the timezone string or None if user cancels.
|
|
||||||
"""
|
|
||||||
# Check if user has timezone set
|
|
||||||
user_data = await bot.api.get_user_data(user_id)
|
|
||||||
if user_data and user_data.get('timezone'):
|
|
||||||
return user_data['timezone']
|
|
||||||
|
|
||||||
# Ask user for their timezone
|
|
||||||
await bot.send_dm(user_id, "🕐 I don't have your timezone set yet. Could you please tell me your timezone?\n\n" +
|
|
||||||
"You can provide it in various formats:\n" +
|
|
||||||
"- Timezone name (e.g., 'America/New_York', 'Europe/London')\n" +
|
|
||||||
"- UTC offset (e.g., 'UTC+2', '-05:00')\n" +
|
|
||||||
"- Common abbreviations (e.g., 'EST', 'PST')\n\n" +
|
|
||||||
"Please reply with your timezone and I'll set it up for you!")
|
|
||||||
|
|
||||||
# Wait for user response (simplified - in real implementation this would be more complex)
|
|
||||||
# For now, we'll just return None to indicate we need to handle this differently
|
|
||||||
return None
|
|
||||||
await bot.send_dm(user_id, "I didn't understand that timezone format. Please say something like:\n"
|
|
||||||
'- "UTC-8" or "-8" for Pacific Time\n'
|
|
||||||
'- "UTC+1" or "+1" for Central European Time\n'
|
|
||||||
'- "PST", "EST", "CST", "MST" for US timezones')
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Check if user has timezone set in preferences
|
# Check if user has timezone set in preferences
|
||||||
resp, status = api_request("get", "/api/preferences", token)
|
resp, status = api_request("get", "/api/preferences", token)
|
||||||
if status == 200 and resp:
|
if status == 200 and resp:
|
||||||
@@ -133,7 +94,7 @@ async def _get_user_timezone(bot, user_id):
|
|||||||
if offset is not None:
|
if offset is not None:
|
||||||
return offset
|
return offset
|
||||||
|
|
||||||
# No timezone set - need to ask user
|
# No timezone set
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -251,10 +212,74 @@ async def _get_scheduled_time_from_context(message, med_name):
|
|||||||
|
|
||||||
|
|
||||||
async def handle_medication(message, session, parsed):
|
async def handle_medication(message, session, parsed):
|
||||||
action = parsed.get("action", "unknown")
|
|
||||||
token = session["token"]
|
token = session["token"]
|
||||||
user_uuid = session["user_uuid"]
|
user_uuid = session["user_uuid"]
|
||||||
|
|
||||||
|
# --- PENDING CONFIRMATION HANDLER ---
|
||||||
|
# Check if we are waiting for a response (e.g., timezone, yes/no confirmation)
|
||||||
|
pending = session.get("pending_confirmations", {})
|
||||||
|
|
||||||
|
# 1. Handle Pending Timezone
|
||||||
|
if "timezone" in pending:
|
||||||
|
user_response = message.content.strip()
|
||||||
|
offset = _parse_timezone(user_response)
|
||||||
|
|
||||||
|
if offset is not None:
|
||||||
|
# Save to API
|
||||||
|
resp, status = api_request(
|
||||||
|
"put", "/api/preferences", token, {"timezone_offset": offset}
|
||||||
|
)
|
||||||
|
if status == 200:
|
||||||
|
# Retrieve the stored action context
|
||||||
|
prev_context = pending["timezone"]
|
||||||
|
del session["pending_confirmations"]["timezone"]
|
||||||
|
|
||||||
|
await message.channel.send(f"✅ Timezone set successfully.")
|
||||||
|
|
||||||
|
# Restore the previous action so we can resume it
|
||||||
|
# We merge the context into 'parsed' so the logic below continues correctly
|
||||||
|
parsed = prev_context
|
||||||
|
else:
|
||||||
|
await message.channel.send("Error saving timezone. Please try again.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
await message.channel.send(
|
||||||
|
"I didn't understand that timezone format. Please say something like:\n"
|
||||||
|
'- "UTC-8" or "-8" for Pacific Time\n'
|
||||||
|
'- "UTC+1" or "+1" for Central European Time\n'
|
||||||
|
'- "PST", "EST", "CST", "MST" for US timezones'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2. Handle Generic Yes/No Confirmations (Add/Delete meds)
|
||||||
|
# Find keys that look like confirmations (excluding timezone)
|
||||||
|
confirm_keys = [k for k in pending if k.startswith("med_")]
|
||||||
|
if confirm_keys:
|
||||||
|
text = message.content.strip().lower()
|
||||||
|
key = confirm_keys[0] # Handle one confirmation at a time
|
||||||
|
stored_action = pending[key]
|
||||||
|
|
||||||
|
if text in ["yes", "y", "confirm"]:
|
||||||
|
del session["pending_confirmations"][key]
|
||||||
|
# Resume the action with confirmation bypassed
|
||||||
|
parsed = stored_action
|
||||||
|
# Force needs_confirmation to False just in case
|
||||||
|
parsed["needs_confirmation"] = False
|
||||||
|
|
||||||
|
elif text in ["no", "n", "cancel"]:
|
||||||
|
del session["pending_confirmations"][key]
|
||||||
|
await message.channel.send("Okay, cancelled.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
# User said something else, remind them
|
||||||
|
await message.channel.send(
|
||||||
|
"I'm waiting for a confirmation. Please reply **yes** to proceed or **no** to cancel."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# --- MAIN ACTION HANDLER ---
|
||||||
|
action = parsed.get("action", "unknown")
|
||||||
|
|
||||||
if action == "list":
|
if action == "list":
|
||||||
resp, status = api_request("get", "/api/medications", token)
|
resp, status = api_request("get", "/api/medications", token)
|
||||||
if status == 200:
|
if status == 200:
|
||||||
@@ -751,4 +776,4 @@ def validate_medication_json(data):
|
|||||||
|
|
||||||
|
|
||||||
register_module("medication", handle_medication)
|
register_module("medication", handle_medication)
|
||||||
ai_parser.register_validator("medication", validate_medication_json)
|
ai_parser.register_validator("medication", validate_medication_json)
|
||||||
Reference in New Issue
Block a user