no
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user