This commit is contained in:
2026-02-16 13:16:18 -06:00
parent d262d80199
commit f140f8f75c
2 changed files with 274 additions and 210 deletions

View File

@@ -4,10 +4,190 @@ Medications command handler - bot-side hooks for medication management
import asyncio
import re
from datetime import datetime, timedelta, timezone
from bot.command_registry import register_module
import ai.parser as ai_parser
def _get_nearest_scheduled_time(times, user_tz=None):
"""Find the nearest scheduled time in user's timezone.
Args:
times: List of time strings like ["08:00", "20:00"]
user_tz: pytz timezone object for the user
Returns the time as HH:MM string, or None if no times provided.
"""
if not times:
return None
# Get current time in user's timezone
if user_tz is None:
# Default to UTC if no timezone provided
user_tz = timezone.utc
now = datetime.now(user_tz)
now_minutes = now.hour * 60 + now.minute
# Find the time closest to now (within ±4 hours window)
best_time = None
best_diff = float("inf")
window_minutes = 4 * 60 # 4 hour window
for time_str in times:
try:
# Parse time string (e.g., "12:00")
hour, minute = map(int, time_str.split(":"))
time_minutes = hour * 60 + minute
# Calculate difference
diff = abs(time_minutes - now_minutes)
# Only consider times within the window
if diff <= window_minutes and diff < best_diff:
best_diff = diff
best_time = time_str
except (ValueError, AttributeError):
continue
# If no time within window, use the most recent past time
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
past_times = []
for time_str in times:
try:
hour, minute = map(int, time_str.split(":"))
time_minutes = hour * 60 + minute
# If time is in the past today, calculate minutes since then
if time_minutes <= now_minutes:
past_times.append((time_minutes, time_str))
else:
# If time is in the future, consider it as yesterday's time
past_times.append((time_minutes - 24 * 60, time_str))
except (ValueError, AttributeError):
continue
if past_times:
# Find the time closest to now (most recent past)
past_times.sort()
best_time = past_times[-1][1]
return best_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:
best_time = times[0]
return best_time
async def _get_user_timezone(bot, user_id):
"""Check if user has timezone set, ask for it if not.
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
resp, status = api_request("get", "/api/preferences", token)
if status == 200 and resp:
offset = resp.get("timezone_offset")
if offset is not None:
return offset
# No timezone set - need to ask user
return None
def _parse_timezone(user_input):
"""Parse timezone string to offset in minutes.
Examples:
"UTC-8" -> 480 (8 hours behind UTC)
"-8" -> 480
"PST" -> 480
"EST" -> 300
"+5:30" -> -330
Returns offset in minutes (positive = behind UTC) or None if invalid.
"""
user_input = user_input.strip().upper()
# Common timezone abbreviations
tz_map = {
"PST": 480,
"PDT": 420,
"MST": 420,
"MDT": 360,
"CST": 360,
"CDT": 300,
"EST": 300,
"EDT": 240,
"GMT": 0,
"UTC": 0,
}
if user_input in tz_map:
return tz_map[user_input]
# Try to parse offset format
# Remove UTC prefix if present
if user_input.startswith("UTC"):
user_input = user_input[3:]
# Match patterns like -8, +5, -5:30, +5:30
match = re.match(r"^([+-])?(\d+)(?::(\d+))?$", user_input)
if match:
sign = -1 if match.group(1) == "-" else 1
hours = int(match.group(2))
minutes = int(match.group(3)) if match.group(3) else 0
# Convert to offset minutes (positive = behind UTC)
# If user says UTC-8, they're 8 hours BEHIND UTC, so offset is +480
offset_minutes = (hours * 60 + minutes) * sign * -1
return offset_minutes
return None
async def _get_scheduled_time_from_context(message, med_name):
"""Fetch recent messages and extract scheduled time from medication reminder.
@@ -164,22 +344,48 @@ async def handle_medication(message, session, parsed):
await message.channel.send("Which medication did you take?")
return
# Check if we have user's timezone
timezone_offset = await _get_user_timezone(message, session, token)
if timezone_offset is None:
# Need to ask for timezone first
if "pending_confirmations" not in session:
session["pending_confirmations"] = {}
# Store that we're waiting for timezone, along with the med info
session["pending_confirmations"]["timezone"] = {
"action": "take",
"medication_id": med_id,
"name": name,
}
await message.channel.send(
"📍 I need to know your timezone to track medications correctly.\n\n"
'What timezone are you in? (e.g., "UTC-8", "PST", "EST", "+1")'
)
return
# Try to get scheduled time from recent reminder context
scheduled_time = await _get_scheduled_time_from_context(message, name)
# If not found in context, calculate from medication schedule
if not scheduled_time:
# Get medication details to find scheduled times
med_resp, med_status = api_request(
"get", f"/api/medications/{med_id}", token
)
if med_status == 200 and med_resp:
times = med_resp.get("times", [])
scheduled_time = _get_nearest_scheduled_time(times, timezone_offset)
# Build request body with scheduled_time if found
request_body = {}
if scheduled_time:
request_body["scheduled_time"] = scheduled_time
print(
f"[DEBUG] About to call API: POST /api/medications/{med_id}/take with body: {request_body}",
flush=True,
)
resp, status = api_request(
"post", f"/api/medications/{med_id}/take", token, request_body
)
print(f"[DEBUG] API response: status={status}, resp={resp}", flush=True)
if status == 201:
if scheduled_time:
await message.channel.send(