From 743bed67f3d3436b4a64fb8bc80874e08ffcc7c5 Mon Sep 17 00:00:00 2001 From: latte Date: Mon, 12 Jan 2026 20:30:59 +0100 Subject: [PATCH] quick fix --- .env.example | 30 +++++----- docker-compose.yml | 57 ++++++++++--------- src/daemon_boyfriend/models/base.py | 18 ++++-- src/daemon_boyfriend/models/conversation.py | 10 ++-- src/daemon_boyfriend/models/guild.py | 17 ++++-- src/daemon_boyfriend/models/living_ai.py | 35 ++++++++---- src/daemon_boyfriend/models/user.py | 23 ++++++-- .../services/association_service.py | 4 +- .../services/fact_extraction_service.py | 4 +- src/daemon_boyfriend/services/mood_service.py | 10 ++-- .../services/opinion_service.py | 4 +- .../services/persistent_conversation.py | 6 +- .../services/proactive_service.py | 12 ++-- .../services/relationship_service.py | 6 +- .../services/self_awareness_service.py | 6 +- src/daemon_boyfriend/services/user_service.py | 6 +- 16 files changed, 146 insertions(+), 102 deletions(-) diff --git a/.env.example b/.env.example index 61026d6..7d17ead 100644 --- a/.env.example +++ b/.env.example @@ -29,16 +29,16 @@ AI_TEMPERATURE=0.7 # Bot Identity & Personality # =========================================== # The bot's name, used in the system prompt to tell the AI who it is -BOT_NAME=My Bot +BOT_NAME="My Bot" # Personality traits that define how the bot responds (used in system prompt) -BOT_PERSONALITY=helpful and friendly +BOT_PERSONALITY="helpful and friendly" # Message shown when someone mentions the bot without saying anything -BOT_DESCRIPTION=I'm an AI assistant here to help you. +BOT_DESCRIPTION="I'm an AI assistant here to help you." # Status message shown in Discord (displays as "Watching ") -BOT_STATUS=for mentions +BOT_STATUS="for mentions" # Optional: Override the entire system prompt (leave commented to use auto-generated) # SYSTEM_PROMPT=You are a custom assistant... @@ -104,7 +104,7 @@ FACT_EXTRACTION_RATE=0.3 PROACTIVE_ENABLED=true # Enable cross-user associations (privacy-sensitive - shows shared interests) -CROSS_USER_ENABLED=false +CROSS_USER_ENABLED=true # Enable bot opinion formation (bot develops topic preferences) OPINION_FORMATION_ENABLED=true @@ -119,18 +119,18 @@ MOOD_DECAY_RATE=0.1 # Command Toggles # =========================================== # Master switch for all commands (when false, bot handles via conversation) -COMMANDS_ENABLED=true +COMMANDS_ENABLED=false # Individual command toggles -CMD_RELATIONSHIP_ENABLED=true -CMD_MOOD_ENABLED=true -CMD_BOTSTATS_ENABLED=true -CMD_OURHISTORY_ENABLED=true -CMD_BIRTHDAY_ENABLED=true -CMD_REMEMBER_ENABLED=true -CMD_SETNAME_ENABLED=true -CMD_WHATDOYOUKNOW_ENABLED=true -CMD_FORGETME_ENABLED=true +# CMD_RELATIONSHIP_ENABLED=true +# CMD_MOOD_ENABLED=true +# CMD_BOTSTATS_ENABLED=true +# CMD_OURHISTORY_ENABLED=true +# CMD_BIRTHDAY_ENABLED=true +# CMD_REMEMBER_ENABLED=true +# CMD_SETNAME_ENABLED=true +# CMD_WHATDOYOUKNOW_ENABLED=true +# CMD_FORGETME_ENABLED=true # =========================================== # Logging & Monitoring diff --git a/docker-compose.yml b/docker-compose.yml index 80318ae..58a5712 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,32 +1,33 @@ services: - daemon-boyfriend: - build: . - container_name: daemon-boyfriend - restart: unless-stopped - env_file: - - .env - environment: - - PYTHONUNBUFFERED=1 - - DATABASE_URL=postgresql+asyncpg://daemon:${POSTGRES_PASSWORD:-daemon}@postgres:5432/daemon_boyfriend - depends_on: - postgres: - condition: service_healthy + daemon-boyfriend: + build: . + container_name: daemon-boyfriend + restart: unless-stopped + env_file: + - .env + environment: + - PYTHONUNBUFFERED=1 + - DATABASE_URL=postgresql+asyncpg://daemon:${POSTGRES_PASSWORD:-daemon}@postgres:5432/daemon_boyfriend + depends_on: + postgres: + condition: service_healthy - postgres: - image: postgres:16-alpine - container_name: daemon-boyfriend-postgres - restart: unless-stopped - environment: - POSTGRES_USER: daemon - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-daemon} - POSTGRES_DB: daemon_boyfriend - volumes: - - postgres_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U daemon -d daemon_boyfriend"] - interval: 10s - timeout: 5s - retries: 5 + postgres: + image: postgres:16-alpine + container_name: daemon-boyfriend-postgres + restart: unless-stopped + environment: + POSTGRES_USER: daemon + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-daemon} + POSTGRES_DB: daemon_boyfriend + volumes: + - postgres_data:/var/lib/postgresql/data + - ./schema.sql:/docker-entrypoint-initdb.d/schema.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U daemon -d daemon_boyfriend"] + interval: 10s + timeout: 5s + retries: 5 volumes: - postgres_data: + postgres_data: diff --git a/src/daemon_boyfriend/models/base.py b/src/daemon_boyfriend/models/base.py index 407ce98..cf19f41 100644 --- a/src/daemon_boyfriend/models/base.py +++ b/src/daemon_boyfriend/models/base.py @@ -1,11 +1,17 @@ """SQLAlchemy base model and metadata configuration.""" -from datetime import datetime +from datetime import datetime, timezone -from sqlalchemy import MetaData +from sqlalchemy import DateTime, MetaData from sqlalchemy.ext.asyncio import AsyncAttrs from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column + +def utc_now() -> datetime: + """Return current UTC time as timezone-aware datetime.""" + return datetime.now(timezone.utc) + + # Naming convention for constraints (helps with migrations) convention = { "ix": "ix_%(column_0_label)s", @@ -23,6 +29,8 @@ class Base(AsyncAttrs, DeclarativeBase): metadata = metadata - # Common timestamp columns - created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) - updated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, onupdate=datetime.utcnow) + # Common timestamp columns - use timezone-aware datetimes + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=utc_now, onupdate=utc_now + ) diff --git a/src/daemon_boyfriend/models/conversation.py b/src/daemon_boyfriend/models/conversation.py index 5b0b0f7..4c23c4e 100644 --- a/src/daemon_boyfriend/models/conversation.py +++ b/src/daemon_boyfriend/models/conversation.py @@ -3,10 +3,10 @@ from datetime import datetime from typing import TYPE_CHECKING -from sqlalchemy import ARRAY, BigInteger, Boolean, ForeignKey, Integer, String, Text +from sqlalchemy import ARRAY, BigInteger, Boolean, DateTime, ForeignKey, Integer, String, Text from sqlalchemy.orm import Mapped, mapped_column, relationship -from .base import Base +from .base import Base, utc_now if TYPE_CHECKING: from .user import User @@ -21,8 +21,10 @@ class Conversation(Base): user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) guild_id: Mapped[int | None] = mapped_column(BigInteger) channel_id: Mapped[int | None] = mapped_column(BigInteger, index=True) - started_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) - last_message_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, index=True) + started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) + last_message_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=utc_now, index=True + ) message_count: Mapped[int] = mapped_column(Integer, default=0) is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True) diff --git a/src/daemon_boyfriend/models/guild.py b/src/daemon_boyfriend/models/guild.py index df42f52..84539c4 100644 --- a/src/daemon_boyfriend/models/guild.py +++ b/src/daemon_boyfriend/models/guild.py @@ -3,11 +3,20 @@ from datetime import datetime from typing import TYPE_CHECKING -from sqlalchemy import ARRAY, BigInteger, Boolean, ForeignKey, String, Text, UniqueConstraint +from sqlalchemy import ( + ARRAY, + BigInteger, + Boolean, + DateTime, + ForeignKey, + String, + Text, + UniqueConstraint, +) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship -from .base import Base +from .base import Base, utc_now if TYPE_CHECKING: from .user import User @@ -21,7 +30,7 @@ class Guild(Base): id: Mapped[int] = mapped_column(primary_key=True) discord_id: Mapped[int] = mapped_column(BigInteger, unique=True, index=True) name: Mapped[str] = mapped_column(String(255)) - joined_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) + joined_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) is_active: Mapped[bool] = mapped_column(Boolean, default=True) settings: Mapped[dict] = mapped_column(JSONB, default=dict) @@ -41,7 +50,7 @@ class GuildMember(Base): user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), index=True) guild_nickname: Mapped[str | None] = mapped_column(String(255)) roles: Mapped[list[str] | None] = mapped_column(ARRAY(Text), default=None) - joined_guild_at: Mapped[datetime | None] = mapped_column(default=None) + joined_guild_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) # Relationships guild: Mapped["Guild"] = relationship(back_populates="members") diff --git a/src/daemon_boyfriend/models/living_ai.py b/src/daemon_boyfriend/models/living_ai.py index ac96a39..7203011 100644 --- a/src/daemon_boyfriend/models/living_ai.py +++ b/src/daemon_boyfriend/models/living_ai.py @@ -3,11 +3,20 @@ from datetime import datetime from typing import TYPE_CHECKING -from sqlalchemy import BigInteger, Boolean, Float, ForeignKey, String, Text, UniqueConstraint +from sqlalchemy import ( + BigInteger, + Boolean, + DateTime, + Float, + ForeignKey, + String, + Text, + UniqueConstraint, +) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import Mapped, mapped_column, relationship -from .base import Base +from .base import Base, utc_now if TYPE_CHECKING: from .user import User, UserFact @@ -24,13 +33,13 @@ class BotState(Base): # Current mood state (valence-arousal model) mood_valence: Mapped[float] = mapped_column(Float, default=0.0) # -1.0 (sad) to 1.0 (happy) mood_arousal: Mapped[float] = mapped_column(Float, default=0.0) # -1.0 (calm) to 1.0 (excited) - mood_updated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) + mood_updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) # Bot statistics total_messages_sent: Mapped[int] = mapped_column(default=0) total_facts_learned: Mapped[int] = mapped_column(default=0) total_users_known: Mapped[int] = mapped_column(default=0) - first_activated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) + first_activated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) # Bot preferences (evolved over time) preferences: Mapped[dict] = mapped_column(JSONB, default=dict) @@ -50,8 +59,8 @@ class BotOpinion(Base): discussion_count: Mapped[int] = mapped_column(default=0) reasoning: Mapped[str | None] = mapped_column(Text) - formed_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) - last_reinforced_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) + formed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) + last_reinforced_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) __table_args__ = (UniqueConstraint("guild_id", "topic"),) @@ -81,8 +90,8 @@ class UserRelationship(Base): # Inside jokes / shared references shared_references: Mapped[dict] = mapped_column(JSONB, default=dict) - first_interaction_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) - last_interaction_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) + first_interaction_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) + last_interaction_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) # Relationships user: Mapped["User"] = relationship(back_populates="relationships") @@ -128,7 +137,7 @@ class ScheduledEvent(Base): channel_id: Mapped[int | None] = mapped_column(BigInteger) event_type: Mapped[str] = mapped_column(String(50), index=True) - trigger_at: Mapped[datetime] = mapped_column(index=True) + trigger_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True) title: Mapped[str] = mapped_column(String(255)) context: Mapped[dict] = mapped_column(JSONB, default=dict) @@ -137,7 +146,7 @@ class ScheduledEvent(Base): recurrence_rule: Mapped[str | None] = mapped_column(String(100)) status: Mapped[str] = mapped_column(String(20), default="pending", index=True) - triggered_at: Mapped[datetime | None] = mapped_column(default=None) + triggered_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), default=None) # Relationships user: Mapped["User"] = relationship(back_populates="scheduled_events") @@ -159,7 +168,7 @@ class FactAssociation(Base): association_type: Mapped[str] = mapped_column(String(50)) strength: Mapped[float] = mapped_column(Float, default=0.5) - discovered_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) + discovered_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) # Relationships fact_1: Mapped["UserFact"] = relationship(foreign_keys=[fact_id_1]) @@ -183,4 +192,6 @@ class MoodHistory(Base): trigger_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL")) trigger_description: Mapped[str | None] = mapped_column(Text) - recorded_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, index=True) + recorded_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=utc_now, index=True + ) diff --git a/src/daemon_boyfriend/models/user.py b/src/daemon_boyfriend/models/user.py index f7b540d..987b25d 100644 --- a/src/daemon_boyfriend/models/user.py +++ b/src/daemon_boyfriend/models/user.py @@ -3,10 +3,19 @@ from datetime import datetime from typing import TYPE_CHECKING -from sqlalchemy import BigInteger, Boolean, Float, ForeignKey, String, Text, UniqueConstraint +from sqlalchemy import ( + BigInteger, + Boolean, + DateTime, + Float, + ForeignKey, + String, + Text, + UniqueConstraint, +) from sqlalchemy.orm import Mapped, mapped_column, relationship -from .base import Base +from .base import Base, utc_now if TYPE_CHECKING: from .conversation import Conversation, Message @@ -24,8 +33,8 @@ class User(Base): discord_username: Mapped[str] = mapped_column(String(255)) discord_display_name: Mapped[str | None] = mapped_column(String(255)) custom_name: Mapped[str | None] = mapped_column(String(255)) - first_seen_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) - last_seen_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) + first_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) + last_seen_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) is_active: Mapped[bool] = mapped_column(Boolean, default=True) # Relationships @@ -88,8 +97,10 @@ class UserFact(Base): confidence: Mapped[float] = mapped_column(Float, default=1.0) source: Mapped[str] = mapped_column(String(50), default="conversation") is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True) - learned_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) - last_referenced_at: Mapped[datetime | None] = mapped_column(default=None) + learned_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=utc_now) + last_referenced_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), default=None + ) # Relationships user: Mapped["User"] = relationship(back_populates="facts") diff --git a/src/daemon_boyfriend/services/association_service.py b/src/daemon_boyfriend/services/association_service.py index d4908c7..e846a12 100644 --- a/src/daemon_boyfriend/services/association_service.py +++ b/src/daemon_boyfriend/services/association_service.py @@ -1,7 +1,7 @@ """Association Service - discovers and manages cross-user fact associations.""" import logging -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession @@ -97,7 +97,7 @@ class AssociationService: fact_id_2=fact_2.id, association_type=association_type, strength=strength, - discovered_at=datetime.utcnow(), + discovered_at=datetime.now(timezone.utc), ) self._session.add(assoc) await self._session.flush() diff --git a/src/daemon_boyfriend/services/fact_extraction_service.py b/src/daemon_boyfriend/services/fact_extraction_service.py index 9404ef4..4d10fcb 100644 --- a/src/daemon_boyfriend/services/fact_extraction_service.py +++ b/src/daemon_boyfriend/services/fact_extraction_service.py @@ -3,7 +3,7 @@ import json import logging import random -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -311,7 +311,7 @@ Return ONLY the JSON array, no other text.""" confidence=fact_data.get("confidence", 0.8), source="auto_extraction", is_active=True, - learned_at=datetime.utcnow(), + learned_at=datetime.now(timezone.utc), # New fields from Living AI category=fact_data["type"], importance=fact_data.get("importance", 0.5), diff --git a/src/daemon_boyfriend/services/mood_service.py b/src/daemon_boyfriend/services/mood_service.py index 9b4a526..622342e 100644 --- a/src/daemon_boyfriend/services/mood_service.py +++ b/src/daemon_boyfriend/services/mood_service.py @@ -2,7 +2,7 @@ import logging from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from sqlalchemy import select @@ -60,7 +60,9 @@ class MoodService: bot_state = await self.get_or_create_bot_state(guild_id) # Apply time decay toward neutral - hours_since_update = (datetime.utcnow() - bot_state.mood_updated_at).total_seconds() / 3600 + hours_since_update = ( + datetime.now(timezone.utc) - bot_state.mood_updated_at + ).total_seconds() / 3600 decay_factor = max(0, 1 - (settings.mood_decay_rate * hours_since_update)) valence = bot_state.mood_valence * decay_factor @@ -105,7 +107,7 @@ class MoodService: bot_state = await self.get_or_create_bot_state(guild_id) bot_state.mood_valence = new_valence bot_state.mood_arousal = new_arousal - bot_state.mood_updated_at = datetime.utcnow() + bot_state.mood_updated_at = datetime.now(timezone.utc) # Record history await self._record_mood_history( @@ -140,7 +142,7 @@ class MoodService: async def get_stats(self, guild_id: int | None = None) -> dict: """Get bot statistics.""" bot_state = await self.get_or_create_bot_state(guild_id) - age_delta = datetime.utcnow() - bot_state.first_activated_at + age_delta = datetime.now(timezone.utc) - bot_state.first_activated_at return { "age_days": age_delta.days, diff --git a/src/daemon_boyfriend/services/opinion_service.py b/src/daemon_boyfriend/services/opinion_service.py index d1db440..115186e 100644 --- a/src/daemon_boyfriend/services/opinion_service.py +++ b/src/daemon_boyfriend/services/opinion_service.py @@ -1,7 +1,7 @@ """Opinion Service - manages bot opinion formation on topics.""" import logging -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -77,7 +77,7 @@ class OpinionService: ) opinion.interest_level = max(0.0, min(1.0, opinion.interest_level)) - opinion.last_reinforced_at = datetime.utcnow() + opinion.last_reinforced_at = datetime.now(timezone.utc) logger.debug( f"Updated opinion on '{topic}': sentiment={opinion.sentiment:.2f}, " diff --git a/src/daemon_boyfriend/services/persistent_conversation.py b/src/daemon_boyfriend/services/persistent_conversation.py index 3c46d15..84771bd 100644 --- a/src/daemon_boyfriend/services/persistent_conversation.py +++ b/src/daemon_boyfriend/services/persistent_conversation.py @@ -1,7 +1,7 @@ """Persistent conversation management using PostgreSQL.""" import logging -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -38,7 +38,7 @@ class PersistentConversationManager: Conversation model instance """ # Look for recent active conversation in this channel - cutoff = datetime.utcnow() - self._timeout + cutoff = datetime.now(timezone.utc) - self._timeout stmt = select(Conversation).where( Conversation.user_id == user.id, @@ -133,7 +133,7 @@ class PersistentConversationManager: self._session.add(message) # Update conversation stats - conversation.last_message_at = datetime.utcnow() + conversation.last_message_at = datetime.now(timezone.utc) conversation.message_count += 1 await self._session.flush() diff --git a/src/daemon_boyfriend/services/proactive_service.py b/src/daemon_boyfriend/services/proactive_service.py index d0b4cae..c7e6e0a 100644 --- a/src/daemon_boyfriend/services/proactive_service.py +++ b/src/daemon_boyfriend/services/proactive_service.py @@ -3,7 +3,7 @@ import json import logging import re -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -71,7 +71,7 @@ Examples: if result and result.get("has_event"): days_until = result.get("days_until", 1) or 1 # Schedule follow-up for 1 day after the event - trigger_at = datetime.utcnow() + timedelta(days=days_until + 1) + trigger_at = datetime.now(timezone.utc) + timedelta(days=days_until + 1) event = ScheduledEvent( user_id=user.id, @@ -159,7 +159,7 @@ Examples: break # Create the event - trigger_at = datetime.utcnow() + timedelta(days=days_until + 1) + trigger_at = datetime.now(timezone.utc) + timedelta(days=days_until + 1) event = ScheduledEvent( user_id=user.id, @@ -300,7 +300,7 @@ Examples: def _next_birthday(self, birthday: datetime) -> datetime: """Calculate the next occurrence of a birthday.""" - today = datetime.utcnow().date() + today = datetime.now(timezone.utc).date() this_year = birthday.replace(year=today.year) if this_year.date() < today: @@ -322,7 +322,7 @@ Examples: async def get_pending_events(self, before: datetime | None = None) -> list[ScheduledEvent]: """Get events that should be triggered.""" - cutoff = before or datetime.utcnow() + cutoff = before or datetime.now(timezone.utc) stmt = ( select(ScheduledEvent) .where( @@ -390,7 +390,7 @@ Examples: async def mark_event_triggered(self, event: ScheduledEvent) -> None: """Mark an event as triggered and handle recurrence.""" event.status = "triggered" - event.triggered_at = datetime.utcnow() + event.triggered_at = datetime.now(timezone.utc) # Handle recurring events if event.is_recurring and event.recurrence_rule: diff --git a/src/daemon_boyfriend/services/relationship_service.py b/src/daemon_boyfriend/services/relationship_service.py index 7083406..a0339e2 100644 --- a/src/daemon_boyfriend/services/relationship_service.py +++ b/src/daemon_boyfriend/services/relationship_service.py @@ -1,7 +1,7 @@ """Relationship Service - manages relationship tracking with users.""" import logging -from datetime import datetime +from datetime import datetime, timezone from enum import Enum from sqlalchemy import select @@ -69,7 +69,7 @@ class RelationshipService: rel = await self.get_or_create_relationship(user, guild_id) rel.total_interactions += 1 - rel.last_interaction_at = datetime.utcnow() + rel.last_interaction_at = datetime.now(timezone.utc) # Track sentiment if sentiment > 0.2: @@ -211,7 +211,7 @@ class RelationshipService: level = self.get_level(rel.relationship_score) # Calculate time since first interaction - time_known = datetime.utcnow() - rel.first_interaction_at + time_known = datetime.now(timezone.utc) - rel.first_interaction_at days_known = time_known.days return { diff --git a/src/daemon_boyfriend/services/self_awareness_service.py b/src/daemon_boyfriend/services/self_awareness_service.py index 927ddda..4ce4bf4 100644 --- a/src/daemon_boyfriend/services/self_awareness_service.py +++ b/src/daemon_boyfriend/services/self_awareness_service.py @@ -1,7 +1,7 @@ """Self Awareness Service - provides bot self-reflection and statistics.""" import logging -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession @@ -36,7 +36,7 @@ class SelfAwarenessService: await self._session.flush() # Calculate age - age_delta = datetime.utcnow() - bot_state.first_activated_at + age_delta = datetime.now(timezone.utc) - bot_state.first_activated_at # Count users (from database) user_count = await self._count_users() @@ -76,7 +76,7 @@ class SelfAwarenessService: facts_count = facts_result.scalar() or 0 if rel: - days_known = (datetime.utcnow() - rel.first_interaction_at).days + days_known = (datetime.now(timezone.utc) - rel.first_interaction_at).days return { "first_met": rel.first_interaction_at, "days_known": days_known, diff --git a/src/daemon_boyfriend/services/user_service.py b/src/daemon_boyfriend/services/user_service.py index d06c98b..b16900b 100644 --- a/src/daemon_boyfriend/services/user_service.py +++ b/src/daemon_boyfriend/services/user_service.py @@ -1,7 +1,7 @@ """User management service.""" import logging -from datetime import datetime +from datetime import datetime, timezone from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -40,7 +40,7 @@ class UserService: if user: # Update last seen and current name - user.last_seen_at = datetime.utcnow() + user.last_seen_at = datetime.now(timezone.utc) user.discord_username = username if display_name: user.discord_display_name = display_name @@ -232,7 +232,7 @@ class UserService: for fact in facts[:20]: # Limit to 20 most recent facts lines.append(f"- [{fact.fact_type}] {fact.fact_content}") # Mark as referenced - fact.last_referenced_at = datetime.utcnow() + fact.last_referenced_at = datetime.now(timezone.utc) return "\n".join(lines)