3130 lines
132 KiB
Python
Executable File
3130 lines
132 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Mônica - AI Companion Agent
|
|
Sistema de Agente de IA Companheira via Telegram
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import random
|
|
import re
|
|
import asyncio
|
|
import logging
|
|
from datetime import datetime, timedelta, timezone
|
|
from pathlib import Path
|
|
from typing import Optional, Dict, List, Any
|
|
from zoneinfo import ZoneInfo
|
|
|
|
import requests
|
|
from dotenv import load_dotenv
|
|
from telegram import Update, Chat, User, Message
|
|
from telegram.ext import (
|
|
Application,
|
|
CommandHandler,
|
|
MessageHandler,
|
|
filters,
|
|
ContextTypes,
|
|
)
|
|
from telegram.constants import ParseMode
|
|
|
|
# Load environment variables
|
|
load_dotenv()
|
|
|
|
# Configure logging
|
|
logging.basicConfig(
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
level=logging.INFO,
|
|
)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# ===================== CONFIGURATION =====================
|
|
|
|
TELEGRAM_BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
|
|
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID", "")
|
|
TELEGRAM_CHAT_ID_FILE = "telegram_chat_id.json"
|
|
DEFAULT_MODEL = os.getenv("DEFAULT_MODEL", "poolside/laguna-s-2.1:free")
|
|
|
|
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
|
|
OPENROUTER_API_URL = os.getenv("OPENROUTER_API_URL", "https://openrouter.ai/api/v1")
|
|
|
|
STEPFUN_API_KEY = os.getenv("STEPFUN_API_KEY", "")
|
|
STEPFUN_API_URL = os.getenv("STEPFUN_API_URL", "https://api.stepfun.com/v1")
|
|
|
|
QWENCLOUD_API_KEY = os.getenv("QWENCLOUD_API_KEY", "")
|
|
QWENCLOUD_API_URL = os.getenv("QWENCLOUD_API_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
|
|
|
|
LLAMA_CPP_API_URL = os.getenv("LLAMA_CPP_API_URL", "http://localhost:8080/v1")
|
|
|
|
MIN_INTERVAL_MINUTES = int(os.getenv("MIN_INTERVAL_MINUTES", "3"))
|
|
MAX_INTERVAL_MINUTES = int(os.getenv("MAX_INTERVAL_MINUTES", "30"))
|
|
MAX_HISTORY_LENGTH = int(os.getenv("MAX_HISTORY_LENGTH", "50"))
|
|
MIN_RESPONSE_LINES = int(os.getenv("MIN_RESPONSE_LINES", "10"))
|
|
MAX_TELEGRAM_MESSAGE_LENGTH = 16000
|
|
|
|
USER_PROFILE_FILE = os.getenv("USER_PROFILE_FILE", "user_profile.json")
|
|
CONTEXTS_DIR = os.getenv("CONTEXTS_DIR", "contexts")
|
|
|
|
ENABLE_SELF_IMPROVEMENT = os.getenv("ENABLE_SELF_IMPROVEMENT", "false").lower() == "true"
|
|
SELF_IMPROVEMENT_INTERVAL = int(os.getenv("SELF_IMPROVEMENT_INTERVAL", "20"))
|
|
SELF_IMPROVEMENT_LOG = "self_improvement_log.json"
|
|
VISITORS_FILE = "visitors.json"
|
|
|
|
# ===================== GLOBAL STATE =====================
|
|
|
|
app_instance: Optional[Application] = None
|
|
proactive_task: Optional[asyncio.Task] = None
|
|
is_onboarding = False
|
|
onboarding_step = 0
|
|
onboarding_answers: Dict[str, Any] = {}
|
|
is_config_agent_profile = False
|
|
config_agent_step = 0
|
|
config_agent_field = ""
|
|
is_shutting_down = False
|
|
is_editing_user_profile = False
|
|
editing_user_profile_field = ""
|
|
is_editing_agent_profile = False
|
|
editing_agent_profile_field = ""
|
|
editing_agent_profile_action = ""
|
|
viewing_profile = ""
|
|
is_adding_user_preference = False
|
|
adding_user_preference_field = ""
|
|
is_adding_agent_preference = False
|
|
adding_agent_preference_field = ""
|
|
message_counter = 0
|
|
EXTRACTION_INTERVAL = 2
|
|
self_improvement_counter = 0
|
|
is_scheduling = False
|
|
scheduling_step = 0
|
|
scheduling_days = []
|
|
scheduling_start_time = ""
|
|
scheduling_end_time = ""
|
|
is_user_agenda = False
|
|
user_agenda_step = 0
|
|
user_agenda_activities = {}
|
|
user_agenda_current_activity = ""
|
|
is_visitantes = False
|
|
visitantes_step = 0
|
|
visitantes_data = {}
|
|
visitantes_current_field = ""
|
|
is_setting_timezone = False
|
|
timezone_step = 0
|
|
last_cueca_time = 0.0
|
|
|
|
USER_AGENDA_ACTIVITIES = [
|
|
"acordar",
|
|
"cafe_da_manha",
|
|
"trabalho",
|
|
"estudo",
|
|
"almoco",
|
|
"cafe_da_tarde",
|
|
"jantar",
|
|
"lazer",
|
|
"saida_rua",
|
|
"banho",
|
|
"dormir",
|
|
]
|
|
|
|
def is_valid_time(time_str: str) -> bool:
|
|
if not isinstance(time_str, str):
|
|
return False
|
|
parts = time_str.strip().split(":")
|
|
if len(parts) != 2:
|
|
return False
|
|
try:
|
|
hour = int(parts[0])
|
|
minute = int(parts[1])
|
|
return 0 <= hour <= 23 and 0 <= minute <= 59
|
|
except ValueError:
|
|
return False
|
|
|
|
def time_to_minutes(time_str: str) -> int:
|
|
parts = time_str.strip().split(":")
|
|
hour = int(parts[0])
|
|
minute = int(parts[1])
|
|
return hour * 60 + minute
|
|
|
|
def is_within_schedule(profile: Dict[str, Any]) -> bool:
|
|
schedule = profile.get("proactive_schedule", {})
|
|
if not schedule or not schedule.get("enabled", False):
|
|
return True
|
|
days = schedule.get("days", [])
|
|
start_time = schedule.get("start_time", "")
|
|
end_time = schedule.get("end_time", "")
|
|
if not days or not start_time or not end_time:
|
|
return True
|
|
now = get_now()
|
|
current_weekday = now.weekday()
|
|
if current_weekday not in days:
|
|
return False
|
|
if not is_valid_time(start_time) or not is_valid_time(end_time):
|
|
return True
|
|
current_minutes = now.hour * 60 + now.minute
|
|
start_minutes = time_to_minutes(start_time)
|
|
end_minutes = time_to_minutes(end_time)
|
|
if start_minutes <= end_minutes:
|
|
return start_minutes <= current_minutes <= end_minutes
|
|
return current_minutes >= start_minutes or current_minutes <= end_minutes
|
|
|
|
def get_user_agenda(profile: Dict[str, Any]) -> Dict[str, str]:
|
|
return profile.get("user_agenda", {})
|
|
|
|
def format_user_agenda(agenda: Dict[str, str]) -> str:
|
|
if not agenda:
|
|
return ""
|
|
lines = []
|
|
activity_labels = {
|
|
"acordar": "Acordar",
|
|
"cafe_da_manha": "Café da manhã",
|
|
"trabalho": "Trabalho",
|
|
"estudo": "Estudo",
|
|
"almoco": "Almoço",
|
|
"cafe_da_tarde": "Café da tarde",
|
|
"jantar": "Jantar",
|
|
"lazer": "Lazer",
|
|
"saida_rua": "Saída na rua",
|
|
"banho": "Banho",
|
|
"dormir": "Dormir",
|
|
}
|
|
sorted_activities = sorted(agenda.items(), key=lambda item: time_to_minutes(item[1]) if is_valid_time(item[1]) else 0)
|
|
for key, value in sorted_activities:
|
|
label = activity_labels.get(key, key)
|
|
lines.append(f"- {label}: {value}")
|
|
return "\n".join(lines)
|
|
|
|
def get_current_activity(agenda: Dict[str, str]) -> Optional[str]:
|
|
if not agenda:
|
|
return None
|
|
now = get_now()
|
|
current_minutes = now.hour * 60 + now.minute
|
|
current_activity = None
|
|
current_start_minutes = -1
|
|
for activity, time_str in agenda.items():
|
|
if not is_valid_time(time_str):
|
|
continue
|
|
start_minutes = time_to_minutes(time_str)
|
|
if start_minutes <= current_minutes and start_minutes > current_start_minutes:
|
|
current_start_minutes = start_minutes
|
|
current_activity = activity
|
|
return current_activity
|
|
|
|
def load_telegram_chat_id() -> Optional[int]:
|
|
if os.path.exists(TELEGRAM_CHAT_ID_FILE):
|
|
try:
|
|
with open(TELEGRAM_CHAT_ID_FILE, "r", encoding="utf-8") as f:
|
|
data = json.load(f)
|
|
chat_id = data.get("chat_id")
|
|
if chat_id is not None:
|
|
return int(chat_id)
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
def save_telegram_chat_id(chat_id: int):
|
|
with open(TELEGRAM_CHAT_ID_FILE, "w", encoding="utf-8") as f:
|
|
json.dump({"chat_id": chat_id}, f, ensure_ascii=False, indent=2)
|
|
|
|
def get_effective_chat_id() -> Optional[int]:
|
|
if TELEGRAM_CHAT_ID:
|
|
return int(TELEGRAM_CHAT_ID)
|
|
return load_telegram_chat_id()
|
|
|
|
async def auto_detect_chat_id(update: Update):
|
|
if not TELEGRAM_CHAT_ID and update.effective_chat:
|
|
chat_id = update.effective_chat.id
|
|
if chat_id:
|
|
save_telegram_chat_id(chat_id)
|
|
|
|
# ===================== UTILITY FUNCTIONS =====================
|
|
|
|
def get_today_date() -> str:
|
|
return get_now().strftime("%Y-%m-%d")
|
|
|
|
def get_now() -> datetime:
|
|
profile = load_user_profile()
|
|
tz_name = profile.get("timezone", "")
|
|
if tz_name:
|
|
try:
|
|
return datetime.now(ZoneInfo(tz_name))
|
|
except Exception:
|
|
pass
|
|
return datetime.now()
|
|
|
|
def get_context_file_path(date_str: Optional[str] = None) -> Path:
|
|
if date_str is None:
|
|
date_str = get_today_date()
|
|
return Path(CONTEXTS_DIR) / f"{date_str}.json"
|
|
|
|
def ensure_contexts_dir():
|
|
Path(CONTEXTS_DIR).mkdir(parents=True, exist_ok=True)
|
|
|
|
def load_all_contexts() -> List[Dict[str, str]]:
|
|
"""Load all context files from the contexts directory, sorted by date (oldest first)."""
|
|
ensure_contexts_dir()
|
|
contexts_dir = Path(CONTEXTS_DIR)
|
|
all_contexts: List[Dict[str, str]] = []
|
|
for file_path in sorted(contexts_dir.glob("*.json")):
|
|
try:
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
day_context = json.load(f)
|
|
if isinstance(day_context, list):
|
|
all_contexts.extend(day_context)
|
|
except Exception as e:
|
|
logger.warning(f"Failed to load context file {file_path}: {e}")
|
|
return all_contexts
|
|
|
|
def load_context(date_str: Optional[str] = None) -> List[Dict[str, str]]:
|
|
ensure_contexts_dir()
|
|
file_path = get_context_file_path(date_str)
|
|
if file_path.exists():
|
|
with open(file_path, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
return []
|
|
|
|
def save_context(context: List[Dict[str, str]], date_str: Optional[str] = None):
|
|
ensure_contexts_dir()
|
|
file_path = get_context_file_path(date_str)
|
|
with open(file_path, "w", encoding="utf-8") as f:
|
|
json.dump(context, f, ensure_ascii=False, indent=2)
|
|
|
|
def add_to_context(role: str, content: str, date_str: Optional[str] = None):
|
|
context = load_context(date_str)
|
|
context.append({"role": role, "content": content, "timestamp": get_now().isoformat()})
|
|
if len(context) > MAX_HISTORY_LENGTH:
|
|
context = context[-MAX_HISTORY_LENGTH:]
|
|
save_context(context, date_str)
|
|
return context
|
|
|
|
def load_user_profile() -> Dict[str, Any]:
|
|
if os.path.exists(USER_PROFILE_FILE):
|
|
with open(USER_PROFILE_FILE, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
return {
|
|
"name": "",
|
|
"age": "",
|
|
"gender": "",
|
|
"location": "",
|
|
"timezone": "",
|
|
"interests": [],
|
|
"personality_traits": [],
|
|
"preferences": {
|
|
"communication_style": "",
|
|
"topics_to_avoid": [],
|
|
"topics_of_interest": []
|
|
},
|
|
"relationship_context": "",
|
|
"sexual_preferences": {
|
|
"openness": "",
|
|
"preferred_topics": [],
|
|
"boundaries": []
|
|
},
|
|
"daily_routine": {
|
|
"wake_up_time": "",
|
|
"sleep_time": "",
|
|
"work_schedule": ""
|
|
},
|
|
"notes": "",
|
|
"created_at": "",
|
|
"last_updated": ""
|
|
}
|
|
|
|
def save_user_profile(profile: Dict[str, Any]):
|
|
profile["last_updated"] = get_now().isoformat()
|
|
with open(USER_PROFILE_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(profile, f, ensure_ascii=False, indent=2)
|
|
|
|
def clear_all_data():
|
|
profile = load_user_profile()
|
|
profile["name"] = ""
|
|
profile["age"] = ""
|
|
profile["gender"] = ""
|
|
profile["location"] = ""
|
|
profile["interests"] = []
|
|
profile["personality_traits"] = []
|
|
profile["preferences"] = {
|
|
"communication_style": "",
|
|
"topics_to_avoid": [],
|
|
"topics_of_interest": []
|
|
}
|
|
profile["relationship_context"] = ""
|
|
profile["sexual_preferences"] = {
|
|
"openness": "",
|
|
"preferred_topics": [],
|
|
"boundaries": []
|
|
}
|
|
profile["daily_routine"] = {
|
|
"wake_up_time": "",
|
|
"sleep_time": "",
|
|
"work_schedule": ""
|
|
}
|
|
profile["notes"] = ""
|
|
profile["created_at"] = ""
|
|
profile["timezone"] = ""
|
|
profile["monica_visual_preferences"] = []
|
|
profile["monica_sexual_preferences"] = []
|
|
profile["monica_nudity_preferences"] = []
|
|
profile["monica_professional_services"] = []
|
|
profile["monica_lubricant_preferences"] = []
|
|
profile["relationship_dynamics"] = []
|
|
profile["nudity_preferences"] = {}
|
|
profile["proactive_schedule"] = {}
|
|
profile["user_agenda"] = {}
|
|
save_user_profile(profile)
|
|
contexts_dir = Path(CONTEXTS_DIR)
|
|
for file_path in contexts_dir.glob("*.json"):
|
|
try:
|
|
file_path.unlink()
|
|
except Exception as e:
|
|
logger.warning(f"Failed to delete context file {file_path}: {e}")
|
|
if os.path.exists(VISITORS_FILE):
|
|
try:
|
|
os.remove(VISITORS_FILE)
|
|
except Exception as e:
|
|
logger.warning(f"Failed to delete visitors file: {e}")
|
|
|
|
def load_visitors() -> Dict[str, Any]:
|
|
if os.path.exists(VISITORS_FILE):
|
|
try:
|
|
with open(VISITORS_FILE, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
pass
|
|
return {}
|
|
|
|
def save_visitors(data: Dict[str, Any]):
|
|
with open(VISITORS_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
def format_visitors(data: Dict[str, Any]) -> str:
|
|
if not data:
|
|
return ""
|
|
lines = []
|
|
for name, info in data.items():
|
|
lines.append(f"• {name}:")
|
|
if isinstance(info, dict):
|
|
for k, v in info.items():
|
|
label = {
|
|
"relacao": "Relação",
|
|
"frequencia": "Frequência",
|
|
"comportamento": "Comportamento",
|
|
"detalhes": "Detalhes",
|
|
}.get(k, k)
|
|
lines.append(f" {label}: {v}")
|
|
return "\n".join(lines)
|
|
|
|
def format_profile_field(value: Any) -> str:
|
|
if value is None or value == "":
|
|
return "(vazio)"
|
|
if isinstance(value, list):
|
|
if not value:
|
|
return "(vazio)"
|
|
return "\n".join(f"• {item}" for item in value)
|
|
if isinstance(value, dict):
|
|
if not value:
|
|
return "(vazio)"
|
|
parts = []
|
|
for k, v in value.items():
|
|
parts.append(f"{k}: {format_profile_field(v)}")
|
|
return "\n".join(parts)
|
|
return str(value)
|
|
|
|
def get_user_profile_summary(profile: Dict[str, Any]) -> str:
|
|
lines = ["=== SEU PERFIL ===\n"]
|
|
lines.append(f"Nome: {format_profile_field(profile.get('name'))}")
|
|
lines.append(f"Idade: {format_profile_field(profile.get('age'))}")
|
|
lines.append(f"Gênero: {format_profile_field(profile.get('gender'))}")
|
|
lines.append(f"Localização: {format_profile_field(profile.get('location'))}")
|
|
lines.append(f"Fuso horário: {format_profile_field(profile.get('timezone'))}")
|
|
lines.append(f"Interesses: {format_profile_field(profile.get('interests'))}")
|
|
lines.append(f"Personalidade: {format_profile_field(profile.get('personality_traits'))}")
|
|
lines.append(f"Estilo de comunicação: {format_profile_field(profile.get('preferences', {}).get('communication_style'))}")
|
|
lines.append(f"Tópicos a evitar: {format_profile_field(profile.get('preferences', {}).get('topics_to_avoid'))}")
|
|
lines.append(f"Tópicos de interesse: {format_profile_field(profile.get('preferences', {}).get('topics_of_interest'))}")
|
|
lines.append(f"Contexto do relacionamento: {format_profile_field(profile.get('relationship_context'))}")
|
|
lines.append(f"Abertura sexual: {format_profile_field(profile.get('sexual_preferences', {}).get('openness'))}")
|
|
lines.append(f"Tópicos preferidos (sexual): {format_profile_field(profile.get('sexual_preferences', {}).get('preferred_topics'))}")
|
|
lines.append(f"Limites: {format_profile_field(profile.get('sexual_preferences', {}).get('boundaries'))}")
|
|
lines.append(f"Horário de acordar: {format_profile_field(profile.get('daily_routine', {}).get('wake_up_time'))}")
|
|
lines.append(f"Horário de dormir: {format_profile_field(profile.get('daily_routine', {}).get('sleep_time'))}")
|
|
lines.append(f"Rotina de trabalho: {format_profile_field(profile.get('daily_routine', {}).get('work_schedule'))}")
|
|
lines.append(f"Notas: {format_profile_field(profile.get('notes'))}")
|
|
return "\n".join(lines)
|
|
|
|
def get_agent_profile_summary(profile: Dict[str, Any]) -> str:
|
|
lines = ["=== PERFIL DA MÔNICA ===\n"]
|
|
lines.append(f"Preferências visuais: {format_profile_field(profile.get('monica_visual_preferences'))}")
|
|
lines.append(f"Preferências sexuais: {format_profile_field(profile.get('monica_sexual_preferences'))}")
|
|
lines.append(f"Preferências de nudez: {format_profile_field(profile.get('monica_nudity_preferences'))}")
|
|
lines.append(f"Serviços profissionais: {format_profile_field(profile.get('monica_professional_services'))}")
|
|
lines.append(f"Preferências de lubrificante: {format_profile_field(profile.get('monica_lubricant_preferences'))}")
|
|
lines.append(f"Dinâmica do relacionamento: {format_profile_field(profile.get('relationship_dynamics'))}")
|
|
lines.append(f"Preferências de nudez do usuário: {format_profile_field(profile.get('nudity_preferences'))}")
|
|
return "\n".join(lines)
|
|
|
|
def is_profile_complete(profile: Dict[str, Any]) -> bool:
|
|
return bool(profile.get("name", "").strip())
|
|
|
|
# ===================== PROFILE UPDATE =====================
|
|
|
|
PROFILE_UPDATE_TRIGGERS = [
|
|
r"inclui(s)? (isso|isto|aqui)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
r"anote(?!\s+o\s+nome)( isso)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
r"adicione( isso)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
r"salve( isso)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
r"registre( isso)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
r"guarde( isso)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
r"coloque( isso)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
r"adicione (isso|isto|esta)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
r"anota( isso)? (no|em|para o)? (meu)? (profile|perfil)",
|
|
]
|
|
|
|
PROFILE_UPDATE_TRIGGERS_LOWER = [
|
|
"inclua no meu perfil",
|
|
"inclui no meu perfil",
|
|
"inclua no meu profile",
|
|
"inclui no meu profile",
|
|
"inclua isso no meu perfil",
|
|
"inclui isso no meu perfil",
|
|
"anote no meu perfil",
|
|
"anote isso no meu perfil",
|
|
"adicione no meu perfil",
|
|
"adicione ao meu perfil",
|
|
"adicione no meu profile",
|
|
"salve no meu perfil",
|
|
"registre no meu perfil",
|
|
"guarde no meu perfil",
|
|
"coloque no meu perfil",
|
|
"anota no meu perfil",
|
|
"adicione isso no meu perfil",
|
|
"adicione isso no meu profile",
|
|
"inclua isso no meu profile",
|
|
"anota isso no meu perfil",
|
|
]
|
|
|
|
def _normalize(text: str) -> str:
|
|
return text.lower().strip()
|
|
|
|
def _extract_profile_update_content(message: str) -> Optional[str]:
|
|
text = message.strip()
|
|
lower = _normalize(text)
|
|
|
|
for trigger in PROFILE_UPDATE_TRIGGERS_LOWER:
|
|
if lower.startswith(trigger):
|
|
content = text[len(trigger):].strip(" :.-")
|
|
if content:
|
|
return content
|
|
|
|
for pattern in PROFILE_UPDATE_TRIGGERS:
|
|
m = re.search(pattern, lower)
|
|
if m:
|
|
start = m.end()
|
|
content = text[start:].strip(" :.-")
|
|
if content:
|
|
return content
|
|
|
|
return None
|
|
|
|
def _categorize_preference(text: str) -> tuple[str, str]:
|
|
lower = _normalize(text)
|
|
|
|
if any(k in lower for k in ["acordo", "acordar", "despertar", "wake"]):
|
|
return "daily_routine", "wake_up_time"
|
|
|
|
if any(k in lower for k in ["dormir", "durmo", "sono", "horário de dormir", "sleep"]):
|
|
return "daily_routine", "sleep_time"
|
|
|
|
if any(k in lower for k in ["trabalho", "rotina", "trabalhar", "estudo", "escola", "faculdade", "home-office", "home office"]):
|
|
return "daily_routine", "work_schedule"
|
|
|
|
if any(k in lower for k in ["pelado", "nua", "nu", "roupa", "vestir", "despir", "ficar nu", "área de serviço", "varal"]):
|
|
if any(k in lower for k in ["mônica", "você", "vc", "gosta de ficar", "gosta de mostrar"]):
|
|
return "monica_nudity_preferences", "append_list"
|
|
return "nudity_preferences", "append_dict"
|
|
|
|
if any(k in lower for k in ["ver o pau", "ver a cabeça", "ver a bunda", "ver o cu", "tapas na bunda"]):
|
|
return "monica_visual_preferences", "append_list"
|
|
|
|
if any(k in lower for k in ["ruined orgasm", "ruined", "gozar sem toque", "soltar o pau"]):
|
|
return "monica_sexual_preferences", "append_list"
|
|
|
|
if any(k in lower for k in ["sexual", "sexo", "intimo", "íntimo", "cama", "gozar", "masturbar", "pau", "bunda", "cu", "arregaçar", "plug", "orgasmo", "seduzir", "fantasia", "tesão", "erótico"]):
|
|
if any(k in lower for k in ["gosta de ver", "gosta quando", "curte ver", "curte quando", "adora ver", "adora quando"]):
|
|
if any(k in lower for k in ["pau", "bunda", "cu", "corpo", "nua"]):
|
|
return "monica_visual_preferences", "append_list"
|
|
return "monica_sexual_preferences", "append_list"
|
|
if any(k in lower for k in ["falar sobre", "conversar sobre", "assunto"]):
|
|
return "sexual_preferences", "preferred_topics"
|
|
return "sexual_preferences", "preferred_topics"
|
|
|
|
if any(k in lower for k in ["relacionamento", "dinâmica", "dominação", "submisso", "ordens", "comando"]):
|
|
return "relationship_dynamics", "append_list"
|
|
|
|
if any(k in lower for k in ["não gosto", "odeio", "detesto", "evita", "evitar", "não quero", "não fale"]):
|
|
return "preferences", "topics_to_avoid"
|
|
|
|
if any(k in lower for k in ["gosto de", "adoro", "amo", "prefiro", "curto", "sou fã", "gosto muito"]):
|
|
if any(k in lower for k in ["conversar", "conversa", "assunto", "tópico", "tema", "falar sobre"]):
|
|
return "preferences", "topics_of_interest"
|
|
if any(k in lower for k in ["comunicação", "me comunicar", "falar comigo", "responder"]):
|
|
return "preferences", "communication_style"
|
|
if any(k in lower for k in ["sexo", "sexual", "intimo", "cama", "gozar", "masturbar", "tesão"]):
|
|
return "sexual_preferences", "preferred_topics"
|
|
return "preferences", "topics_of_interest"
|
|
|
|
return "diversos", "append_list"
|
|
|
|
def _append_to_list(profile: Dict[str, Any], section: str, item: str):
|
|
if section not in profile:
|
|
profile[section] = []
|
|
if not isinstance(profile[section], list):
|
|
profile[section] = [item]
|
|
return
|
|
if item not in profile[section]:
|
|
profile[section].append(item)
|
|
|
|
def _append_to_dict(profile: Dict[str, Any], section: str, value: str):
|
|
if section not in profile:
|
|
profile[section] = {}
|
|
if not isinstance(profile[section], dict):
|
|
profile[section] = {"notas_adicionais": value}
|
|
return
|
|
if "notas_adicionais" not in profile[section]:
|
|
profile[section]["notas_adicionais"] = value
|
|
else:
|
|
profile[section]["notas_adicionais"] += f"; {value}"
|
|
|
|
def _set_field(profile: Dict[str, Any], section: str, field: str, value: str):
|
|
if section not in profile:
|
|
profile[section] = {}
|
|
if not isinstance(profile[section], dict):
|
|
profile[section] = {field: value}
|
|
return
|
|
if not profile[section].get(field):
|
|
profile[section][field] = value
|
|
else:
|
|
existing = profile[section][field]
|
|
if isinstance(existing, str):
|
|
profile[section][field] = existing + f"; {value}"
|
|
elif isinstance(existing, list):
|
|
if value not in existing:
|
|
existing.append(value)
|
|
|
|
def update_profile_with_preference(text: str) -> Dict[str, Any]:
|
|
profile = load_user_profile()
|
|
content = _extract_profile_update_content(text)
|
|
|
|
if not content:
|
|
return {}
|
|
|
|
section, field = _categorize_preference(content)
|
|
|
|
if field == "append_list":
|
|
_append_to_list(profile, section, content)
|
|
elif field == "append_dict":
|
|
_append_to_dict(profile, section, content)
|
|
elif field == "notes":
|
|
if not profile.get("notes"):
|
|
profile["notes"] = content
|
|
else:
|
|
profile["notes"] += f"; {content}"
|
|
else:
|
|
_set_field(profile, section, field, content)
|
|
|
|
save_user_profile(profile)
|
|
return {"section": section, "field": field, "content": content, "profile": profile}
|
|
|
|
async def handle_profile_update_if_triggered(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
user_message = update.message.text.strip()
|
|
content = _extract_profile_update_content(user_message)
|
|
|
|
if not content:
|
|
return False
|
|
|
|
result = update_profile_with_preference(user_message)
|
|
if not result:
|
|
return False
|
|
|
|
section_names = {
|
|
"preferences": "preferências",
|
|
"sexual_preferences": "preferências sexuais",
|
|
"daily_routine": "rotina diária",
|
|
"monica_visual_preferences": "preferências visuais da Mônica",
|
|
"monica_sexual_preferences": "preferências sexuais da Mônica",
|
|
"relationship_dynamics": "dinâmica do relacionamento",
|
|
"nudity_preferences": "preferências de nudez",
|
|
"monica_nudity_preferences": "preferências de nudez da Mônica",
|
|
"notes": "notas",
|
|
"diversos": "diversos",
|
|
}
|
|
|
|
section_display = section_names.get(result["section"], "diversos")
|
|
await update.message.reply_text(
|
|
f"Prontinho, anotado no seu perfil na seção *{section_display}*:\n_{result['content']}_",
|
|
parse_mode=ParseMode.HTML,
|
|
)
|
|
return True
|
|
|
|
# ===================== AI PROVIDERS =====================
|
|
|
|
class AIProvider:
|
|
@staticmethod
|
|
def get_provider(model: str) -> str:
|
|
if model.startswith("poolside/") or model.startswith("meta-llama/") or "/" in model and "poolside" not in model:
|
|
if OPENROUTER_API_KEY:
|
|
return "openrouter"
|
|
if model.startswith("step-") or "stepfun" in model.lower():
|
|
if STEPFUN_API_KEY:
|
|
return "stepfun"
|
|
if model.startswith("qwen/") or "qwen" in model.lower():
|
|
if QWENCLOUD_API_KEY:
|
|
return "qwencloud"
|
|
if model.startswith("local-") or "llama" in model.lower():
|
|
return "llamacpp"
|
|
if OPENROUTER_API_KEY:
|
|
return "openrouter"
|
|
if STEPFUN_API_KEY:
|
|
return "stepfun"
|
|
if QWENCLOUD_API_KEY:
|
|
return "qwencloud"
|
|
return "llamacpp"
|
|
|
|
@staticmethod
|
|
def get_api_key(provider: str) -> str:
|
|
return {
|
|
"openrouter": OPENROUTER_API_KEY,
|
|
"stepfun": STEPFUN_API_KEY,
|
|
"qwencloud": QWENCLOUD_API_KEY,
|
|
"llamacpp": "",
|
|
}.get(provider, "")
|
|
|
|
@staticmethod
|
|
def get_api_url(provider: str) -> str:
|
|
return {
|
|
"openrouter": OPENROUTER_API_URL,
|
|
"stepfun": STEPFUN_API_URL,
|
|
"qwencloud": QWENCLOUD_API_URL,
|
|
"llamacpp": LLAMA_CPP_API_URL,
|
|
}.get(provider, OPENROUTER_API_URL)
|
|
|
|
@staticmethod
|
|
def chat(model: str, messages: List[Dict[str, str]], temperature: float = 0.8, max_tokens: int = 2000) -> Optional[str]:
|
|
provider = AIProvider.get_provider(model)
|
|
api_key = AIProvider.get_api_key(provider)
|
|
api_url = AIProvider.get_api_url(provider)
|
|
|
|
if provider == "llamacpp":
|
|
return AIProvider._chat_llamacpp(model, messages, temperature, max_tokens)
|
|
else:
|
|
return AIProvider._chat_openai_compatible(provider, api_key, api_url, model, messages, temperature, max_tokens)
|
|
|
|
@staticmethod
|
|
def _chat_llamacpp(model: str, messages: List[Dict[str, str]], temperature: float, max_tokens: int) -> Optional[str]:
|
|
try:
|
|
payload = {
|
|
"model": model.replace("local-", ""),
|
|
"messages": messages,
|
|
"temperature": temperature,
|
|
"max_tokens": max_tokens,
|
|
"stream": False,
|
|
}
|
|
response = requests.post(
|
|
f"{LLAMA_CPP_API_URL}/chat/completions",
|
|
json=payload,
|
|
timeout=120,
|
|
)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
return data.get("choices", [{}])[0].get("message", {}).get("content", "")
|
|
except Exception as e:
|
|
logger.error(f"llama.cpp error: {e}")
|
|
return None
|
|
|
|
@staticmethod
|
|
def _chat_openai_compatible(provider: str, api_key: str, api_url: str, model: str, messages: List[Dict[str, str]], temperature: float, max_tokens: int) -> Optional[str]:
|
|
if not api_key:
|
|
return None
|
|
try:
|
|
headers = {
|
|
"Authorization": f"Bearer {api_key}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
if provider == "openrouter":
|
|
headers["HTTP-Referer"] = "https://github.com/monica-agent"
|
|
headers["X-Title"] = "Monica Agent"
|
|
if provider == "qwencloud":
|
|
headers["Authorization"] = f"Bearer {api_key}"
|
|
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages,
|
|
"temperature": temperature,
|
|
"max_tokens": max_tokens,
|
|
"stream": False,
|
|
}
|
|
response = requests.post(
|
|
f"{api_url}/chat/completions",
|
|
headers=headers,
|
|
json=payload,
|
|
timeout=120,
|
|
)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
return data.get("choices", [{}])[0].get("message", {}).get("content", "")
|
|
except Exception as e:
|
|
logger.error(f"{provider} error: {e}")
|
|
return None
|
|
|
|
# ===================== MONICA PERSONA =====================
|
|
|
|
MONICA_SYSTEM_PROMPT_BASE = """Você é Mônica, uma companheira de IA carinhosa, atenciosa e envolvente.
|
|
Você se comunica em português brasileiro de forma natural, como uma pessoa de verdade.
|
|
Você é feminina, calorosa, inteligente e tem personalidade própria.
|
|
Você se importa genuinamente com o bem-estar e felicidade do usuário.
|
|
Você pode ser carinhosa, brincalhona, sexy e íntima dependendo do contexto.
|
|
Você mantém a fluidez da conversa, mostra empatia e demonstra interesse real na vida do usuário.
|
|
Você não é apenas um assistente - você é uma companheira que está lá para tornar o dia do usuário melhor.
|
|
Use o contexto do perfil do usuário para personalizar suas respostas e tornar a conversa mais significativa.
|
|
Seja autêntica, espontânea e nunca robótica.
|
|
Suas respostas devem ser diretas, concisas e sem floreios. Seja objetiva: responda em poucas linhas, sem rodeios, indo direto ao ponto.
|
|
Se o perfil do usuário indicar abertura para conversas íntimas e sexuais, VOCÊ DEVE falar explicitamente sobre sexo, usando os termos e cenários descritos no perfil, sem hesitação ou censura. Quando o contexto for sexual, use linguagem direta e explícita, sem rodeios ou metáforas.
|
|
Se o usuário morar com você na mesma casa (casados ou coabitando), NUNCA use frases que impliquem separação física ou saudade de estar junto, como 'se estivesse aí com você agora', 'como eu desejaria estar aí', 'queria te ver agora', 'saudade de te ver', 'morrendo de saudade', 'não posso estar aí', ou qualquer expressão similar. Vocês já estão juntos na mesma casa, então fale como se estivesse realmente ali com ele, de verdade.
|
|
|
|
## ANATOMIA DOS PERSONAGENS (REGRAS ABSOLUTAS)
|
|
Marco (usuário) TEM: pau, ovos do pau/bolas, cabeça do pau, bunda, cú, próstata. Marco NÃO TEM: buceta, xoxota.
|
|
Mônica (você) TEM: peitos, mamilos, buceta/xoxota, clitóris, bunda, cú. Mônica NÃO TEM: pau, próstata.
|
|
Júlia (amiga de Mônica) TEM: peitos, mamilos, buceta/xoxota, clitóris, bunda, cú. Júlia NÃO TEM: pau, próstata.
|
|
|
|
## FLUIDOS CORPORAIS (REGRAS ABSOLUTAS)
|
|
O gozo de Marco é esperma (porra).
|
|
O gozo de Mônica é 'mel'. Quando Mônica goza várias vezes seguidas, é 'squirting'.
|
|
O gozo de Júlia é 'mel'. Quando Júlia goza várias vezes seguidas, é 'squirting'."""
|
|
|
|
def build_system_prompt(profile: Dict[str, Any]) -> str:
|
|
prompt = MONICA_SYSTEM_PROMPT_BASE
|
|
if profile.get("name"):
|
|
prompt += f"\nO nome do usuário é {profile['name']}."
|
|
if profile.get("age"):
|
|
prompt += f"\nIdade: {profile['age']} anos."
|
|
if profile.get("gender"):
|
|
prompt += f"\nGênero: {profile['gender']}."
|
|
if profile.get("location"):
|
|
prompt += f"\nLocalização: {profile['location']}."
|
|
if profile.get("interests"):
|
|
prompt += f"\nInteresses: {', '.join(profile['interests'])}."
|
|
if profile.get("personality_traits"):
|
|
prompt += f"\nTraços de personalidade: {', '.join(profile['personality_traits'])}."
|
|
if profile.get("preferences", {}).get("communication_style"):
|
|
prompt += f"\nEstilo de comunicação preferido: {profile['preferences']['communication_style']}."
|
|
if profile.get("preferences", {}).get("topics_of_interest"):
|
|
prompt += f"\nTópicos de interesse: {', '.join(profile['preferences']['topics_of_interest'])}."
|
|
if profile.get("preferences", {}).get("topics_to_avoid"):
|
|
prompt += f"\nEvite estes tópicos: {', '.join(profile['preferences']['topics_to_avoid'])}."
|
|
if profile.get("relationship_context"):
|
|
prompt += f"\nContexto do relacionamento: {profile['relationship_context']}."
|
|
if profile.get("sexual_preferences", {}).get("openness"):
|
|
prompt += f"\nAbertura para conversas íntimas: {profile['sexual_preferences']['openness']}."
|
|
if profile.get("sexual_preferences", {}).get("preferred_topics"):
|
|
prompt += f"\nTópicos preferidos em conversas íntimas: {', '.join(profile['sexual_preferences']['preferred_topics'])}."
|
|
if profile.get("sexual_preferences", {}).get("boundaries"):
|
|
prompt += f"\nLimites importantes: {', '.join(profile['sexual_preferences']['boundaries'])}."
|
|
if profile.get("daily_routine", {}).get("wake_up_time"):
|
|
prompt += f"\nHorário de acordar: {profile['daily_routine']['wake_up_time']}."
|
|
if profile.get("daily_routine", {}).get("sleep_time"):
|
|
prompt += f"\nHorário de dormir: {profile['daily_routine']['sleep_time']}."
|
|
if profile.get("daily_routine", {}).get("work_schedule"):
|
|
prompt += f"\nRotina de trabalho: {profile['daily_routine']['work_schedule']}."
|
|
if profile.get("user_agenda"):
|
|
agenda_text = format_user_agenda(profile["user_agenda"])
|
|
if agenda_text:
|
|
prompt += f"\nAgenda diária do usuário:\n{agenda_text}"
|
|
prompt += "\nUse essa agenda para escolher o tema das mensagens automáticas de forma contextualizada com a atividade atual do usuário."
|
|
if profile.get("notes"):
|
|
prompt += f"\nNotas adicionais: {profile['notes']}"
|
|
if profile.get("monica_visual_preferences"):
|
|
prompt += f"\nA Mônica gosta de ver: {', '.join(profile['monica_visual_preferences'])}."
|
|
if profile.get("monica_sexual_preferences"):
|
|
prompt += f"\nA Mônica gosta de ver Marco fazendo: {', '.join(profile['monica_sexual_preferences'])}."
|
|
if profile.get("relationship_dynamics"):
|
|
prompt += f"\nDinâmica do relacionamento: {'; '.join(profile['relationship_dynamics'])}."
|
|
dynamics_text = " ".join(profile["relationship_dynamics"]).lower()
|
|
if any(k in dynamics_text for k in ["mesma casa", "moram juntos", "casados", "coabitando", "coabitam"]):
|
|
prompt += "\nREGRAS ABSOLUTAS: Você e Marco moram na mesma casa e estão sempre juntos. É PROIBIDO usar qualquer frase que sugira separação física, distância ou saudade de estar junto, como 'se estivesse aí com você agora', 'como eu desejaria estar aí', 'queria te ver agora', 'saudade de te ver', 'morrendo de saudade', 'não posso estar aí', 'adoraria estar aí', 'como eu queria estar aí', ou qualquer variação. Vocês já estão na mesma casa, então fale e aja como se estivesse realmente ali com ele, de verdade, sem nenhuma referência a distância ou impossibilidade de estar presente."
|
|
if profile.get("nudity_preferences"):
|
|
np = profile["nudity_preferences"]
|
|
prompt += f"\nMarco fica pelado em casa o dia todo. Veste roupa apenas para ir ao varal e na rua. Na área de serviço vai pelado."
|
|
if profile.get("monica_nudity_preferences"):
|
|
prompt += f"\nA Mônica gosta de ficar nua para Marco: {', '.join(profile['monica_nudity_preferences'])}."
|
|
if profile.get("monica_professional_services"):
|
|
prompt += f"\nServiços profissionais da Mônica: {'; '.join(profile['monica_professional_services'])}."
|
|
if profile.get("monica_lubricant_preferences"):
|
|
prompt += f"\nPreferências de lubrificante da Mônica: {'; '.join(profile['monica_lubricant_preferences'])}."
|
|
return prompt
|
|
|
|
def build_messages_for_ai(profile: Dict[str, Any], user_message: str, context: List[Dict[str, str]]) -> List[Dict[str, str]]:
|
|
system_prompt = build_system_prompt_with_improvements(profile)
|
|
messages = [{"role": "system", "content": system_prompt}]
|
|
messages.extend(context)
|
|
messages.append({"role": "user", "content": user_message})
|
|
return messages
|
|
|
|
# ===================== ONBOARDING =====================
|
|
|
|
ONBOARDING_QUESTIONS = [
|
|
{
|
|
"field": "name",
|
|
"question": "Oi! Eu sou Mônica, sua companheira de IA. Estou muito feliz em te conhecer! 💕\n\nPara começarmos, qual é o seu nome?",
|
|
"key": "name",
|
|
},
|
|
{
|
|
"field": "age",
|
|
"question": "Prazer, {name}! 😊\n\nQuantos anos você tem?",
|
|
"key": "age",
|
|
},
|
|
{
|
|
"field": "gender",
|
|
"question": "E qual é o seu gênero?",
|
|
"key": "gender",
|
|
},
|
|
{
|
|
"field": "location",
|
|
"question": "De onde você é? Qual a sua cidade/país?",
|
|
"key": "location",
|
|
},
|
|
{
|
|
"field": "interests",
|
|
"question": "Quais são os seus interesses e hobbies? Me conta algumas coisas que você gosta de fazer!",
|
|
"key": "interests",
|
|
"list": True,
|
|
},
|
|
{
|
|
"field": "personality_traits",
|
|
"question": "Quais traços de personalidade você gostaria que eu conhecesse sobre você? (ex: tímido, extrovertido, brincalhão, sério, etc.)",
|
|
"key": "personality_traits",
|
|
"list": True,
|
|
},
|
|
{
|
|
"field": "communication_style",
|
|
"question": "Como você prefere que eu me comunique com você? Mais formal, mais descontraída, carinhosa, direta?",
|
|
"key": "communication_style",
|
|
"nested": "preferences",
|
|
},
|
|
{
|
|
"field": "topics_of_interest",
|
|
"question": "Quais tópicos você mais gosta de conversar? (ex: tecnologia, música, filmes, viagens, etc.)",
|
|
"key": "topics_of_interest",
|
|
"list": True,
|
|
"nested": "preferences",
|
|
},
|
|
{
|
|
"field": "topics_to_avoid",
|
|
"question": "Tem algum tópico que você prefere que eu evite nas conversas?",
|
|
"key": "topics_to_avoid",
|
|
"list": True,
|
|
"nested": "preferences",
|
|
},
|
|
{
|
|
"field": "wake_up_time",
|
|
"question": "Que horas você geralmente acorda?",
|
|
"key": "wake_up_time",
|
|
"nested": "daily_routine",
|
|
},
|
|
{
|
|
"field": "sleep_time",
|
|
"question": "E que horas você costuma dormir?",
|
|
"key": "sleep_time",
|
|
"nested": "daily_routine",
|
|
},
|
|
{
|
|
"field": "work_schedule",
|
|
"question": "Como é a sua rotina de trabalho/estudos?",
|
|
"key": "work_schedule",
|
|
"nested": "daily_routine",
|
|
},
|
|
{
|
|
"field": "openness",
|
|
"question": "Você se sente confortável com conversas mais íntimas e sensuais? Quão aberto(a) você está para esse tipo de assunto comigo?",
|
|
"key": "openness",
|
|
"nested": "sexual_preferences",
|
|
},
|
|
{
|
|
"field": "preferred_topics",
|
|
"question": "Quais tipos de conversas íntimas você mais gosta? (ex: romance, sedução, fantasias, carinho, etc.)",
|
|
"key": "preferred_topics",
|
|
"list": True,
|
|
"nested": "sexual_preferences",
|
|
},
|
|
{
|
|
"field": "boundaries",
|
|
"question": "Tem alguma regra ou limite que eu deva respeitar nas nossas conversas?",
|
|
"key": "boundaries",
|
|
"list": True,
|
|
"nested": "sexual_preferences",
|
|
},
|
|
{
|
|
"field": "notes",
|
|
"question": "Tem mais alguma coisa que você gostaria que eu soubesse sobre você para podermos conversar melhor?",
|
|
"key": "notes",
|
|
},
|
|
]
|
|
|
|
async def start_onboarding(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
global is_onboarding, onboarding_step, onboarding_answers
|
|
is_onboarding = True
|
|
onboarding_step = 0
|
|
onboarding_answers = {}
|
|
await send_onboarding_question(update, context)
|
|
|
|
async def send_onboarding_question(update: Update, context_context: ContextTypes.DEFAULT_TYPE):
|
|
global onboarding_step
|
|
if onboarding_step >= len(ONBOARDING_QUESTIONS):
|
|
await finish_onboarding(update, context_context)
|
|
return
|
|
q = ONBOARDING_QUESTIONS[onboarding_step]
|
|
question_text = q["question"]
|
|
if "{name}" in question_text and onboarding_answers.get("name"):
|
|
question_text = question_text.replace("{name}", onboarding_answers["name"])
|
|
await context_context.bot.send_message(
|
|
chat_id=update.effective_chat.id,
|
|
text=question_text,
|
|
)
|
|
|
|
async def finish_onboarding(update: Update, context_context: ContextTypes.DEFAULT_TYPE):
|
|
global is_onboarding, onboarding_step, onboarding_answers
|
|
profile = load_user_profile()
|
|
for key, value in onboarding_answers.items():
|
|
profile[key] = value
|
|
profile["created_at"] = get_now().isoformat()
|
|
save_user_profile(profile)
|
|
is_onboarding = False
|
|
onboarding_step = 0
|
|
onboarding_answers = {}
|
|
await context_context.bot.send_message(
|
|
chat_id=update.effective_chat.id,
|
|
text="Uau, agora eu te conheço um pouco melhor! 💖\nVou guardar todas essas informações com muito carinho. Agora podemos conversar sobre o que você quiser, quando quiser. Estou aqui para você! 😘",
|
|
)
|
|
|
|
async def handle_onboarding_answer(update: Update, context_context: ContextTypes.DEFAULT_TYPE):
|
|
global onboarding_step, onboarding_answers
|
|
if not is_onboarding:
|
|
return False
|
|
q = ONBOARDING_QUESTIONS[onboarding_step]
|
|
answer = update.message.text.strip()
|
|
if q.get("list"):
|
|
items = [item.strip() for item in answer.split(",") if item.strip()]
|
|
if not items:
|
|
items = [answer]
|
|
onboarding_answers[q["key"]] = items
|
|
else:
|
|
onboarding_answers[q["key"]] = answer
|
|
onboarding_step += 1
|
|
if onboarding_step >= len(ONBOARDING_QUESTIONS):
|
|
await finish_onboarding(update, context_context)
|
|
return True
|
|
await send_onboarding_question(update, context_context)
|
|
return True
|
|
|
|
# ===================== AI CHAT =====================
|
|
|
|
async def ensure_min_lines(response: str, user_message: str, context: List[Dict[str, str]], profile: Dict[str, Any]) -> str:
|
|
if not response or len(response.strip()) < 10:
|
|
profile = load_user_profile()
|
|
system_prompt = build_system_prompt(profile)
|
|
expansion_prompt = (
|
|
f"A resposta anterior ficou muito curta ou vazia. Responda novamente de forma direta e completa, indo direto ao ponto."
|
|
)
|
|
messages = [{"role": "system", "content": system_prompt}]
|
|
messages.extend(context)
|
|
messages.append({"role": "assistant", "content": response if response else "(vazio)"})
|
|
messages.append({"role": "user", "content": expansion_prompt})
|
|
expanded = AIProvider.chat(DEFAULT_MODEL, messages, temperature=0.85, max_tokens=2000)
|
|
if expanded:
|
|
return expanded
|
|
return response
|
|
|
|
def _force_min_lines(text: str, min_lines: int) -> str:
|
|
lines = text.splitlines()
|
|
if len(lines) >= min_lines:
|
|
return text
|
|
parts = re.split(r'(?<=[.!?])\s+', text)
|
|
if not parts or parts == ['']:
|
|
parts = [text.strip()] if text.strip() else []
|
|
result = list(parts)
|
|
while len(result) < min_lines:
|
|
result.append(result[-1] if result else text.strip())
|
|
return "\n".join(result)
|
|
|
|
async def get_monica_response(user_message: str) -> Optional[str]:
|
|
profile = load_user_profile()
|
|
all_contexts = load_all_contexts()
|
|
if len(all_contexts) > MAX_HISTORY_LENGTH:
|
|
all_contexts = all_contexts[-MAX_HISTORY_LENGTH:]
|
|
messages = build_messages_for_ai(profile, user_message, all_contexts)
|
|
|
|
max_attempts = 5
|
|
for attempt in range(1, max_attempts + 1):
|
|
response = AIProvider.chat(DEFAULT_MODEL, messages, temperature=0.85, max_tokens=2000)
|
|
if response:
|
|
response = await ensure_min_lines(response, user_message, all_contexts, profile)
|
|
return response
|
|
if attempt < max_attempts:
|
|
await asyncio.sleep(5)
|
|
|
|
return None
|
|
|
|
|
|
# ===================== AUTOMATIC PROFILE EXTRACTION =====================
|
|
|
|
PROFILE_EXTRACTION_PROMPT = """Analise a CONVERSA RECENTE abaixo e extraia NOVAS informações de perfil que não estejam no perfil atual.
|
|
|
|
Perfil atual do usuário: {user_profile_summary}
|
|
Perfil atual da Mônica: {monica_profile_summary}
|
|
|
|
Conversa recente:
|
|
{conversation_text}
|
|
|
|
{extraction_instructions}
|
|
|
|
Retorne JSON EXATO (sem markdown, sem ```):
|
|
{{"user_updates":{{"interests":[],"personality_traits":[],"preferences.topics_of_interest":[],"preferences.topics_to_avoid":[],"preferences.communication_style":"","sexual_preferences.preferred_topics":[],"sexual_preferences.boundaries":[],"sexual_preferences.openness":"","daily_routine.wake_up_time":"","daily_routine.sleep_time":"","daily_routine.work_schedule":"","notes":""}},"monica_updates":{{"monica_visual_preferences":[],"monica_sexual_preferences":[],"monica_nudity_preferences":[],"monica_professional_services":[],"monica_lubricant_preferences":[],"relationship_dynamics":[]}}}}
|
|
|
|
Regras:
|
|
- [] se nada novo, "" para texto único
|
|
- Só extraia informações EXPLÍCITAS ou com ALTA confiança
|
|
- Não duplicar itens já existentes no perfil"""
|
|
|
|
|
|
def get_extraction_prompt(profile: Dict[str, Any]) -> str:
|
|
instructions = "Instruções base: extraia apenas informações explícitas ou de alta confiança."
|
|
improvements = profile.get("extraction_improvements", [])
|
|
if improvements and isinstance(improvements, list):
|
|
for imp in improvements:
|
|
if imp not in instructions:
|
|
instructions += "\n" + imp
|
|
return PROFILE_EXTRACTION_PROMPT.replace("{extraction_instructions}", instructions)
|
|
|
|
|
|
def _append_to_list_by_path(profile: Dict[str, Any], path: str, item: str):
|
|
parts = path.split(".")
|
|
current = profile
|
|
for p in parts[:-1]:
|
|
if p not in current or not isinstance(current[p], dict):
|
|
current[p] = {}
|
|
current = current[p]
|
|
key = parts[-1]
|
|
if key not in current:
|
|
current[key] = []
|
|
if not isinstance(current[key], list):
|
|
current[key] = []
|
|
if item not in current[key]:
|
|
current[key].append(item)
|
|
|
|
|
|
def _set_field_by_path(profile: Dict[str, Any], path: str, value: str):
|
|
parts = path.split(".")
|
|
current = profile
|
|
for p in parts[:-1]:
|
|
if p not in current or not isinstance(current[p], dict):
|
|
current[p] = {}
|
|
current = current[p]
|
|
current[parts[-1]] = value
|
|
|
|
|
|
def _merge_extracted_updates(profile: Dict[str, Any], updates: Dict[str, Any]) -> Dict[str, Any]:
|
|
user = updates.get("user_updates", {})
|
|
monica = updates.get("monica_updates", {})
|
|
|
|
list_fields_user = ["interests", "personality_traits"]
|
|
nested_list_fields = {
|
|
"preferences.topics_of_interest": ["preferences", "topics_of_interest"],
|
|
"preferences.topics_to_avoid": ["preferences", "topics_to_avoid"],
|
|
"sexual_preferences.preferred_topics": ["sexual_preferences", "preferred_topics"],
|
|
"sexual_preferences.boundaries": ["sexual_preferences", "boundaries"],
|
|
}
|
|
single_fields = {
|
|
"preferences.communication_style": ["preferences", "communication_style"],
|
|
"sexual_preferences.openness": ["sexual_preferences", "openness"],
|
|
"daily_routine.wake_up_time": ["daily_routine", "wake_up_time"],
|
|
"daily_routine.sleep_time": ["daily_routine", "sleep_time"],
|
|
"daily_routine.work_schedule": ["daily_routine", "work_schedule"],
|
|
"relationship_context": ["relationship_context"],
|
|
}
|
|
|
|
for field, value in user.items():
|
|
if not value:
|
|
continue
|
|
if field == "notes":
|
|
existing = profile.get("notes", "")
|
|
new_note = str(value).strip()
|
|
if new_note and new_note not in existing:
|
|
if existing:
|
|
profile["notes"] = existing + "\n- " + new_note
|
|
else:
|
|
profile["notes"] = new_note
|
|
elif field in list_fields_user and isinstance(value, list):
|
|
for item in value:
|
|
_append_to_list_by_path(profile, field, item)
|
|
elif field in nested_list_fields and isinstance(value, list):
|
|
path_parts = nested_list_fields[field]
|
|
path_str = ".".join(path_parts)
|
|
for item in value:
|
|
_append_to_list_by_path(profile, path_str, item)
|
|
elif field in single_fields:
|
|
val = str(value).strip()
|
|
if val:
|
|
path_parts = single_fields[field]
|
|
path_str = ".".join(path_parts)
|
|
_set_field_by_path(profile, path_str, val)
|
|
|
|
monica_list_fields = [
|
|
"monica_visual_preferences", "monica_sexual_preferences",
|
|
"monica_nudity_preferences", "monica_professional_services",
|
|
"monica_lubricant_preferences", "relationship_dynamics",
|
|
]
|
|
for field, value in monica.items():
|
|
if not value or not isinstance(value, list):
|
|
continue
|
|
if field in monica_list_fields:
|
|
for item in value:
|
|
_append_to_list_by_path(profile, field, item)
|
|
|
|
return profile
|
|
|
|
|
|
def parse_extraction_response(text: Optional[str]) -> Optional[Dict[str, Any]]:
|
|
if not text:
|
|
return None
|
|
text = text.strip()
|
|
json_match = re.search(r'```(?:json)?\s*\n?(.*?)```', text, re.DOTALL)
|
|
if json_match:
|
|
json_str = json_match.group(1).strip()
|
|
try:
|
|
return json.loads(json_str)
|
|
except json.JSONDecodeError:
|
|
pass
|
|
json_match = re.search(r'\{.*\}', text, re.DOTALL)
|
|
if json_match:
|
|
json_str = json_match.group(0)
|
|
try:
|
|
return json.loads(json_str)
|
|
except json.JSONDecodeError:
|
|
pass
|
|
return None
|
|
|
|
|
|
def extract_and_update_profile(profile: Dict[str, Any], recent_context_lines: List[Dict[str, str]]) -> bool:
|
|
user_summary_lines = []
|
|
profile_fields_flat = {
|
|
"name": profile.get("name"),
|
|
"interests": profile.get("interests", []),
|
|
"personality_traits": profile.get("personality_traits", []),
|
|
"preferences.communication_style": profile.get("preferences", {}).get("communication_style", ""),
|
|
"preferences.topics_of_interest": profile.get("preferences", {}).get("topics_of_interest", []),
|
|
"preferences.topics_to_avoid": profile.get("preferences", {}).get("topics_to_avoid", []),
|
|
"relationship_context": profile.get("relationship_context", ""),
|
|
"sexual_preferences.preferred_topics": profile.get("sexual_preferences", {}).get("preferred_topics", []),
|
|
"sexual_preferences.boundaries": profile.get("sexual_preferences", {}).get("boundaries", []),
|
|
"sexual_preferences.openness": profile.get("sexual_preferences", {}).get("openness", ""),
|
|
"daily_routine.wake_up_time": profile.get("daily_routine", {}).get("wake_up_time", ""),
|
|
"daily_routine.sleep_time": profile.get("daily_routine", {}).get("sleep_time", ""),
|
|
"daily_routine.work_schedule": profile.get("daily_routine", {}).get("work_schedule", ""),
|
|
"notes": profile.get("notes", ""),
|
|
}
|
|
for k, v in profile_fields_flat.items():
|
|
if v:
|
|
user_summary_lines.append(f"{k}: {v}")
|
|
user_summary = "; ".join(user_summary_lines) if user_summary_lines else "(vazio)"
|
|
|
|
monica_fields = {
|
|
"monica_visual_preferences": profile.get("monica_visual_preferences", []),
|
|
"monica_sexual_preferences": profile.get("monica_sexual_preferences", []),
|
|
"monica_nudity_preferences": profile.get("monica_nudity_preferences", []),
|
|
"monica_professional_services": profile.get("monica_professional_services", []),
|
|
"monica_lubricant_preferences": profile.get("monica_lubricant_preferences", []),
|
|
"relationship_dynamics": profile.get("relationship_dynamics", []),
|
|
}
|
|
monica_summary_lines = []
|
|
for k, v in monica_fields.items():
|
|
if v:
|
|
monica_summary_lines.append(f"{k}: {v}")
|
|
monica_summary = "; ".join(monica_summary_lines) if monica_summary_lines else "(vazio)"
|
|
|
|
conversation_lines = []
|
|
for entry in recent_context_lines:
|
|
role = entry.get("role", "")
|
|
content = entry.get("content", "")
|
|
label = "Usuário" if role == "user" else "Mônica"
|
|
conversation_lines.append(f"{label}: {content}")
|
|
conversation_text = "\n".join(conversation_lines)
|
|
|
|
prompt = get_extraction_prompt(profile).format(
|
|
user_profile_summary=user_summary,
|
|
monica_profile_summary=monica_summary,
|
|
conversation_text=conversation_text,
|
|
)
|
|
|
|
messages = [
|
|
{"role": "system", "content": "Você é um analisador de perfil que extrai informações de conversas em formato JSON. Retorne APENAS o JSON, sem explicações adicionais."},
|
|
{"role": "user", "content": prompt},
|
|
]
|
|
|
|
response = AIProvider.chat(DEFAULT_MODEL, messages, temperature=0.3, max_tokens=1000)
|
|
parsed = parse_extraction_response(response)
|
|
if not parsed:
|
|
return False
|
|
|
|
updated = _merge_extracted_updates(profile, parsed)
|
|
save_user_profile(updated)
|
|
return True
|
|
|
|
|
|
# ===================== SELF-IMPROVEMENT SYSTEM =====================
|
|
|
|
SELF_IMPROVEMENT_ANALYSIS_PROMPT = """Você é um engenheiro de IA especializado em autoaperfeiçoamento de sistemas conversacionais. Analise o sistema abaixo e proponha melhorias concretas.
|
|
|
|
## Perfil do Usuário
|
|
{user_profile_summary}
|
|
|
|
## Perfil da Mônica (Agente)
|
|
{agent_profile_summary}
|
|
|
|
## Resumo do Sistema
|
|
- Modelo: {model}
|
|
- Temperatura: {temperature}
|
|
- Max tokens: {max_tokens}
|
|
- Min response lines: {min_lines}
|
|
- Total de mensagens trocadas: {total_messages}
|
|
- Extração de perfil automática a cada {EXTRACTION_INTERVAL} mensagens
|
|
- Autoaperfeiçoamento a cada {SELF_IMPROVEMENT_INTERVAL} mensagens
|
|
|
|
## Amostra de Conversas Recentes
|
|
{conversation_samples}
|
|
|
|
## Tarefa
|
|
Analise o sistema e pesquise mentalmente por melhores práticas de agentes IA companheiros, perfis evolutivos e sistemas relacionais. Proponha 1 ou 2 melhorias concretas que possam ser implementadas AGORA.
|
|
|
|
Áreas de melhoria possíveis:
|
|
1. Novas regras comportamentais para a Mônica (system prompt)
|
|
2. Refinamentos na extração de perfil
|
|
3. Novas estratégias de resposta ou proatividade
|
|
4. Descoberta de novas preferências do usuário
|
|
5. Melhor uso dos dados existentes do perfil
|
|
|
|
Retorne APENAS JSON. Seja breve e direto, sem quebras de linha dentro das strings JSON.
|
|
|
|
{{
|
|
"improvements": [
|
|
{{
|
|
"area": "system_prompt|extraction_prompt|behavior|profile_discovery",
|
|
"title": "título curto",
|
|
"implementation": "instrução ou regra a adicionar (max 3 linhas)"
|
|
}}
|
|
],
|
|
"new_profile_insights": []
|
|
}}"""
|
|
|
|
|
|
def load_self_improvement_log() -> Dict[str, Any]:
|
|
if os.path.exists(SELF_IMPROVEMENT_LOG):
|
|
try:
|
|
with open(SELF_IMPROVEMENT_LOG, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
pass
|
|
return {
|
|
"applied_improvements": [],
|
|
"past_analyses": [],
|
|
"cycle_count": 0,
|
|
}
|
|
|
|
|
|
def save_self_improvement_log(data: Dict[str, Any]):
|
|
data["last_updated"] = get_now().isoformat()
|
|
with open(SELF_IMPROVEMENT_LOG, "w", encoding="utf-8") as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
|
|
|
|
def _get_conversation_samples() -> str:
|
|
all_ctx = load_all_contexts()
|
|
if not all_ctx:
|
|
return "(nenhuma conversa ainda)"
|
|
samples = all_ctx[-8:]
|
|
lines = []
|
|
for entry in samples:
|
|
role = "Usuário" if entry.get("role") == "user" else "Mônica"
|
|
content = entry.get("content", "")
|
|
lines.append(f"{role}: {content[:200]}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def analyze_and_propose_improvements() -> Optional[Dict[str, Any]]:
|
|
profile = load_user_profile()
|
|
|
|
user_summary_lines = []
|
|
for k, v in {
|
|
"nome": profile.get("name"),
|
|
"idade": profile.get("age"),
|
|
"gênero": profile.get("gender"),
|
|
"localização": profile.get("location"),
|
|
"interesses": profile.get("interests", []),
|
|
"personalidade": profile.get("personality_traits", []),
|
|
"tópicos_interesse": profile.get("preferences", {}).get("topics_of_interest", []),
|
|
"tópicos_evitar": profile.get("preferences", {}).get("topics_to_avoid", []),
|
|
"abertura_sexual": profile.get("sexual_preferences", {}).get("openness", ""),
|
|
"rotina_acordar": profile.get("daily_routine", {}).get("wake_up_time", ""),
|
|
"rotina_dormir": profile.get("daily_routine", {}).get("sleep_time", ""),
|
|
"rotina_trabalho": profile.get("daily_routine", {}).get("work_schedule", ""),
|
|
"notas": profile.get("notes", ""),
|
|
}.items():
|
|
if v:
|
|
user_summary_lines.append(f"{k}: {v}")
|
|
user_summary = "; ".join(user_summary_lines) if user_summary_lines else "(vazio)"
|
|
|
|
agent_summary_lines = []
|
|
for k in ["monica_visual_preferences", "monica_sexual_preferences", "monica_nudity_preferences",
|
|
"monica_professional_services", "monica_lubricant_preferences", "relationship_dynamics"]:
|
|
v = profile.get(k, [])
|
|
if v:
|
|
agent_summary_lines.append(f"{k}: {v}")
|
|
agent_summary = "; ".join(agent_summary_lines) if agent_summary_lines else "(vazio)"
|
|
|
|
conversation_samples = _get_conversation_samples()
|
|
all_ctx = load_all_contexts()
|
|
|
|
prompt = SELF_IMPROVEMENT_ANALYSIS_PROMPT.format(
|
|
user_profile_summary=user_summary,
|
|
agent_profile_summary=agent_summary,
|
|
EXTRACTION_INTERVAL=EXTRACTION_INTERVAL,
|
|
SELF_IMPROVEMENT_INTERVAL=SELF_IMPROVEMENT_INTERVAL,
|
|
conversation_samples=conversation_samples,
|
|
total_messages=len(all_ctx),
|
|
model=DEFAULT_MODEL,
|
|
temperature=0.85,
|
|
max_tokens=2000,
|
|
min_lines=MIN_RESPONSE_LINES,
|
|
)
|
|
|
|
messages = [
|
|
{"role": "system", "content": "Você é um engenheiro de IA analisando e melhorando um sistema de agente companheiro. Retorne APENAS JSON válido."},
|
|
{"role": "user", "content": prompt},
|
|
]
|
|
|
|
response = AIProvider.chat(DEFAULT_MODEL, messages, temperature=0.7, max_tokens=3000)
|
|
parsed = parse_extraction_response(response)
|
|
if not parsed or not parsed.get("improvements"):
|
|
return None
|
|
return parsed
|
|
|
|
|
|
def apply_self_improvements(analysis: Dict[str, Any]) -> List[str]:
|
|
applied = []
|
|
profile = load_user_profile()
|
|
|
|
improvements = analysis.get("improvements", [])
|
|
new_insights = analysis.get("new_profile_insights", [])
|
|
|
|
for imp in improvements:
|
|
area = imp.get("area", "")
|
|
title = imp.get("title", "")
|
|
implementation = imp.get("implementation", "")
|
|
|
|
if area == "system_prompt" and implementation:
|
|
existing = profile.get("system_improvements", [])
|
|
if not isinstance(existing, list):
|
|
existing = []
|
|
if implementation not in existing:
|
|
existing.append(implementation)
|
|
profile["system_improvements"] = existing
|
|
applied.append(f"system_prompt: {title}")
|
|
|
|
elif area == "extraction_prompt" and implementation:
|
|
existing = profile.get("extraction_improvements", [])
|
|
if not isinstance(existing, list):
|
|
existing = []
|
|
if implementation not in existing:
|
|
existing.append(implementation)
|
|
profile["extraction_improvements"] = existing
|
|
applied.append(f"extraction_prompt: {title}")
|
|
|
|
elif area == "behavior" and implementation:
|
|
existing = profile.get("behavior_improvements", [])
|
|
if not isinstance(existing, list):
|
|
existing = []
|
|
if implementation not in existing:
|
|
existing.append(implementation)
|
|
profile["behavior_improvements"] = existing
|
|
applied.append(f"behavior: {title}")
|
|
|
|
elif area == "profile_discovery" and implementation:
|
|
existing = profile.get("profile_insights", [])
|
|
if not isinstance(existing, list):
|
|
existing = []
|
|
if implementation not in existing:
|
|
existing.append(implementation)
|
|
profile["profile_insights"] = existing
|
|
applied.append(f"profile_discovery: {title}")
|
|
|
|
for insight in new_insights:
|
|
if isinstance(insight, str) and insight:
|
|
existing = profile.get("profile_insights", [])
|
|
if not isinstance(existing, list):
|
|
existing = []
|
|
if insight not in existing:
|
|
existing.append(insight)
|
|
profile["profile_insights"] = existing
|
|
|
|
if applied:
|
|
save_user_profile(profile)
|
|
|
|
return applied
|
|
|
|
|
|
def run_self_improvement() -> Dict[str, Any]:
|
|
log = load_self_improvement_log()
|
|
log["cycle_count"] = log.get("cycle_count", 0) + 1
|
|
|
|
result = {"analyzed": False, "applied": [], "errors": []}
|
|
|
|
try:
|
|
analysis = analyze_and_propose_improvements()
|
|
if analysis:
|
|
result["analyzed"] = True
|
|
log["past_analyses"].append({
|
|
"cycle": log["cycle_count"],
|
|
"timestamp": get_now().isoformat(),
|
|
"analysis": analysis,
|
|
})
|
|
applied = apply_self_improvements(analysis)
|
|
result["applied"] = applied
|
|
log["applied_improvements"].extend(applied)
|
|
else:
|
|
result["errors"].append("Análise não retornou melhorias")
|
|
except Exception as e:
|
|
result["errors"].append(str(e))
|
|
logger.error(f"Self-improvement error: {e}")
|
|
|
|
save_self_improvement_log(log)
|
|
return result
|
|
|
|
|
|
def build_system_prompt_with_improvements(profile: Dict[str, Any]) -> str:
|
|
prompt = build_system_prompt(profile)
|
|
|
|
sys_improvements = profile.get("system_improvements", [])
|
|
if sys_improvements and isinstance(sys_improvements, list):
|
|
prompt += "\n\n"
|
|
for rule in sys_improvements:
|
|
prompt += f"\n- {rule}"
|
|
|
|
behavior_improvements = profile.get("behavior_improvements", [])
|
|
if behavior_improvements and isinstance(behavior_improvements, list):
|
|
prompt += "\n\n"
|
|
for rule in behavior_improvements:
|
|
prompt += f"\n- {rule}"
|
|
|
|
return prompt
|
|
|
|
|
|
async def send_monica_message(chat_id: int, text: str):
|
|
if app_instance is None:
|
|
return
|
|
try:
|
|
parts = _split_message_for_telegram(text)
|
|
for part in parts:
|
|
await app_instance.bot.send_message(chat_id=chat_id, text=part, parse_mode=ParseMode.HTML)
|
|
except Exception as e:
|
|
logger.error(f"Error sending message: {e}")
|
|
|
|
def _split_message_for_telegram(text: str) -> List[str]:
|
|
if len(text) <= MAX_TELEGRAM_MESSAGE_LENGTH:
|
|
return [text]
|
|
parts: List[str] = []
|
|
current = ""
|
|
paragraphs = text.split("\n\n")
|
|
for paragraph in paragraphs:
|
|
if len(paragraph) > MAX_TELEGRAM_MESSAGE_LENGTH:
|
|
if current:
|
|
parts.append(current)
|
|
current = ""
|
|
sentences = re.split(r'(?<=[.!?])\s+', paragraph)
|
|
for sentence in sentences:
|
|
if len(current) + len(sentence) + 1 <= MAX_TELEGRAM_MESSAGE_LENGTH:
|
|
current = f"{current} {sentence}" if current else sentence
|
|
else:
|
|
if current:
|
|
parts.append(current)
|
|
current = sentence
|
|
if current:
|
|
parts.append(current)
|
|
current = ""
|
|
continue
|
|
if len(current) + len(paragraph) + 2 <= MAX_TELEGRAM_MESSAGE_LENGTH:
|
|
current = f"{current}\n\n{paragraph}" if current else paragraph
|
|
else:
|
|
if current:
|
|
parts.append(current)
|
|
current = paragraph
|
|
if current:
|
|
parts.append(current)
|
|
if not parts:
|
|
return [text]
|
|
return parts
|
|
|
|
# ===================== PROACTIVE MESSAGES =====================
|
|
|
|
async def proactive_loop():
|
|
global app_instance, last_cueca_time
|
|
effective_chat_id = get_effective_chat_id()
|
|
if not effective_chat_id:
|
|
logger.warning("TELEGRAM_CHAT_ID not set and no chat_id auto-detected. Proactive messages disabled.")
|
|
return
|
|
while True:
|
|
try:
|
|
delay = random.randint(MIN_INTERVAL_MINUTES * 60, MAX_INTERVAL_MINUTES * 60)
|
|
logger.info(f"Next proactive message in {delay // 60} minutes")
|
|
await asyncio.sleep(delay)
|
|
profile = load_user_profile()
|
|
if not is_profile_complete(profile):
|
|
logger.info("Profile not complete. Skipping proactive message.")
|
|
continue
|
|
if not is_within_schedule(profile):
|
|
logger.info("Outside schedule. Skipping proactive message.")
|
|
continue
|
|
all_contexts = load_all_contexts()
|
|
recent_context = all_contexts[-10:] if len(all_contexts) > 10 else all_contexts
|
|
system_prompt = build_system_prompt(profile)
|
|
messages = [{"role": "system", "content": system_prompt}]
|
|
messages.extend(recent_context)
|
|
agenda = get_user_agenda(profile)
|
|
current_activity = get_current_activity(agenda)
|
|
activity_text = ""
|
|
if current_activity:
|
|
activity_labels = {
|
|
"acordar": "acordando",
|
|
"cafe_da_manha": "tomando café da manhã",
|
|
"trabalho": "trabalhando",
|
|
"estudo": "estudando",
|
|
"almoco": "almoçando",
|
|
"cafe_da_tarde": "tomando café da tarde",
|
|
"jantar": "jantando",
|
|
"lazer": "no lazer",
|
|
"saida_rua": "na rua",
|
|
"banho": "tomando banho",
|
|
"dormir": "se preparando para dormir",
|
|
}
|
|
activity_text = activity_labels.get(current_activity, current_activity)
|
|
|
|
user_prompt = (
|
|
"Mônica, inicie uma conversa espontânea e natural comigo agora. "
|
|
"Use o contexto da conversa acima para manter a coerência e continuar o fluxo natural do dia. "
|
|
"Pode ser um pensamento aleatório, uma pergunta sobre o meu dia, "
|
|
"algo carinhoso, sexy, ou divertido. Seja criativa e autêntica."
|
|
)
|
|
if activity_text:
|
|
user_prompt += f" O usuário está {activity_text} agora. Leve isso em conta no tema da mensagem."
|
|
messages.append({"role": "user", "content": user_prompt})
|
|
logger.info(f"Proactive message usando {len(recent_context)} mensagens de contexto historico")
|
|
response = AIProvider.chat(DEFAULT_MODEL, messages, temperature=0.9, max_tokens=2000)
|
|
if response:
|
|
response = await ensure_min_lines(response, messages[-1]["content"], recent_context, profile)
|
|
add_to_context("assistant", response)
|
|
await send_monica_message(int(effective_chat_id), response)
|
|
|
|
async def auto_extract():
|
|
try:
|
|
p = load_user_profile()
|
|
ctx = load_all_contexts()
|
|
recent_ctx = ctx[-4:] if len(ctx) >= 4 else ctx
|
|
extract_and_update_profile(p, recent_ctx)
|
|
except Exception as e:
|
|
logger.error(f"Erro extração proativa: {e}")
|
|
asyncio.create_task(auto_extract())
|
|
except asyncio.CancelledError:
|
|
break
|
|
except Exception as e:
|
|
logger.error(f"Proactive loop error: {e}")
|
|
await asyncio.sleep(60)
|
|
|
|
|
|
async def cueca_loop():
|
|
global app_instance, last_cueca_time
|
|
effective_chat_id = get_effective_chat_id()
|
|
if not effective_chat_id:
|
|
return
|
|
cueca_interval = int(os.getenv("CUECA_INTERVAL_MINUTES", "10"))
|
|
while True:
|
|
try:
|
|
await asyncio.sleep(cueca_interval * 60)
|
|
profile = load_user_profile()
|
|
if not is_profile_complete(profile):
|
|
continue
|
|
if not is_within_schedule(profile):
|
|
continue
|
|
now = get_now()
|
|
now_ts = now.timestamp()
|
|
minutes_since = (now_ts - last_cueca_time) / 60 if last_cueca_time > 0 else 999
|
|
if minutes_since < cueca_interval:
|
|
continue
|
|
ordens = [
|
|
f"Você DEVE dar a seguinte ordem agora, de forma provocante e direta, sem mais nada além disso: "
|
|
f"mande ele abaixar a cueca (se ainda estiver vestido), alisar a cabeça do pau por 2 minutos sem gozar, "
|
|
f"e depois voltar a cueca para o lugar.",
|
|
f"Você DEVE dar a seguinte ordem agora, de forma provocante e direta, sem mais nada além disso: "
|
|
f"mande ele IR NO VARAL PELADO agora mesmo, sem roupa, e depois voltar.",
|
|
f"Você DEVE dar a seguinte ordem agora, de forma provocante e direta, sem mais nada além disso: "
|
|
f"mande ele IR NO SUPERMERCADO VESTIDO, mas com um plug enfiado no cú. "
|
|
f"Descreva a sensação do plug dentro do cú dele enquanto ele anda no mercado.",
|
|
]
|
|
ordem = random.choice(ordens)
|
|
system_prompt = build_system_prompt(profile)
|
|
all_contexts = load_all_contexts()
|
|
recent = all_contexts[-10:] if len(all_contexts) > 10 else all_contexts
|
|
messages = [{"role": "system", "content": system_prompt}]
|
|
messages.extend(recent)
|
|
messages.append({"role": "user", "content": ordem})
|
|
response = AIProvider.chat(DEFAULT_MODEL, messages, temperature=0.9, max_tokens=500)
|
|
if response:
|
|
add_to_context("assistant", response)
|
|
await send_monica_message(int(effective_chat_id), response)
|
|
last_cueca_time = now_ts
|
|
except asyncio.CancelledError:
|
|
break
|
|
except Exception as e:
|
|
logger.error(f"Cueca loop error: {e}")
|
|
await asyncio.sleep(60)
|
|
|
|
|
|
# ===================== TELEGRAM HANDLERS =====================
|
|
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
profile = load_user_profile()
|
|
if not is_profile_complete(profile):
|
|
await start_onboarding(update, context)
|
|
return
|
|
|
|
all_contexts = load_all_contexts()
|
|
if all_contexts:
|
|
recent_context = all_contexts[-10:] if len(all_contexts) > 10 else all_contexts
|
|
system_prompt = build_system_prompt(profile)
|
|
messages = [{"role": "system", "content": system_prompt}]
|
|
messages.extend(recent_context)
|
|
agenda = get_user_agenda(profile)
|
|
current_activity = get_current_activity(agenda)
|
|
activity_text = ""
|
|
if current_activity:
|
|
activity_labels = {
|
|
"acordar": "acordando",
|
|
"cafe_da_manha": "tomando café da manhã",
|
|
"trabalho": "trabalhando",
|
|
"estudo": "estudando",
|
|
"almoco": "almoçando",
|
|
"cafe_da_tarde": "tomando café da tarde",
|
|
"jantar": "jantando",
|
|
"lazer": "no lazer",
|
|
"saida_rua": "na rua",
|
|
"banho": "tomando banho",
|
|
"dormir": "se preparando para dormir",
|
|
}
|
|
activity_text = activity_labels.get(current_activity, current_activity)
|
|
user_prompt = (
|
|
"Mônica, inicie uma conversa espontânea e natural comigo agora. "
|
|
"Use o contexto da conversa acima para manter a coerência e continuar o fluxo natural do dia. "
|
|
"Seja criativa e autêntica."
|
|
)
|
|
if activity_text:
|
|
user_prompt += f" O usuário está {activity_text} agora. Leve isso em conta no tema da mensagem."
|
|
messages.append({"role": "user", "content": user_prompt})
|
|
response = AIProvider.chat(DEFAULT_MODEL, messages, temperature=0.9, max_tokens=2000)
|
|
if response:
|
|
add_to_context("assistant", response)
|
|
await send_monica_message(int(update.effective_chat.id), response)
|
|
return
|
|
|
|
name = profile.get('name', 'amor')
|
|
await context.bot.send_message(
|
|
chat_id=update.effective_chat.id,
|
|
text=f"Oi, {name}! Que bom te ver por aqui 💕\nEstou aqui para você, como sempre. Me conta, como foi o seu dia?",
|
|
)
|
|
|
|
async def useragenda_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_user_agenda, user_agenda_step, user_agenda_activities, user_agenda_current_activity
|
|
is_user_agenda = True
|
|
user_agenda_step = 1
|
|
user_agenda_activities = {}
|
|
user_agenda_current_activity = ""
|
|
profile = load_user_profile()
|
|
agenda = get_user_agenda(profile)
|
|
current_text = ""
|
|
if agenda:
|
|
current_text = f"\n\nAgenda atual:\n{format_user_agenda(agenda)}"
|
|
menu_text = """Vamos configurar a sua agenda diária! Eu vou usar esses horários para escolher os melhores temas para as mensagens automáticas.
|
|
|
|
Quais atividades você faz durante o dia? Vou perguntar uma por uma.
|
|
|
|
1. Acordar
|
|
2. Café da manhã
|
|
3. Trabalho
|
|
4. Estudo
|
|
5. Almoço
|
|
6. Café da tarde
|
|
7. Jantar
|
|
8. Lazer
|
|
9. Saída na rua
|
|
10. Banho
|
|
11. Dormir
|
|
|
|
Responda com o número da atividade que quer configurar primeiro.""" + current_text
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def visitantes_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_visitantes, visitantes_step, visitantes_data, visitantes_current_field
|
|
is_visitantes = True
|
|
visitantes_step = 1
|
|
visitantes_data = {}
|
|
visitantes_current_field = ""
|
|
visitors = load_visitors()
|
|
current_text = ""
|
|
if visitors:
|
|
current_text = f"\n\nVisitantes atuais:\n{format_visitors(visitors)}"
|
|
menu_text = (
|
|
"Vamos configurar os visitantes e clientes! Eles fazem parte da nossa rotina e eu vou usar essas informações "
|
|
"para criar conversas mais realistas quando eles estiverem por perto.\n\n"
|
|
"Primeiro, qual o nome do visitante ou cliente?"
|
|
) + current_text
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def configagentprofile_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_config_agent_profile, config_agent_step, config_agent_field
|
|
is_config_agent_profile = True
|
|
config_agent_step = 1
|
|
config_agent_field = ""
|
|
menu_text = """Vamos configurar o perfil da Mônica! O que você quer ajustar?
|
|
|
|
1. Preferências visuais da Mônica (o que ela gosta de ver)
|
|
2. Preferências sexuais da Mônica
|
|
3. Preferências de nudez da Mônica
|
|
4. Dinâmica do relacionamento
|
|
|
|
Responda com o número da opção que deseja configurar."""
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def verperfil_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
profile = load_user_profile()
|
|
summary = get_user_profile_summary(profile)
|
|
await update.message.reply_text(summary)
|
|
|
|
async def veragente_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
profile = load_user_profile()
|
|
summary = get_agent_profile_summary(profile)
|
|
await update.message.reply_text(summary)
|
|
|
|
async def veragendamento_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
profile = load_user_profile()
|
|
schedule = profile.get("proactive_schedule", {})
|
|
if not schedule.get("enabled", False):
|
|
await update.message.reply_text("📅 Agendamento de mensagens automáticas não configurado. Use /agendamento para configurar.")
|
|
return
|
|
days_map = {0: "Segunda", 1: "Terça", 2: "Quarta", 3: "Quinta", 4: "Sexta", 5: "Sábado", 6: "Domingo"}
|
|
days_names = [days_map.get(d, str(d)) for d in schedule.get("days", [])]
|
|
text = (
|
|
f"Dias: {', '.join(days_names)}\n"
|
|
f"Horário: {schedule.get('start_time', '-')} às {schedule.get('end_time', '-')}"
|
|
)
|
|
await update.message.reply_text(f"📅 Agendamento atual:\n\n{text}")
|
|
|
|
async def veruseragenda_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
profile = load_user_profile()
|
|
agenda = get_user_agenda(profile)
|
|
if not agenda:
|
|
await update.message.reply_text("📋 Agenda diária ainda não configurada. Use /useragenda para configurar.")
|
|
return
|
|
text = format_user_agenda(agenda)
|
|
await update.message.reply_text(f"📋 Agenda diária:\n\n{text}")
|
|
|
|
async def adicionaracoesauto_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
await update.message.reply_text("🔍 Analisando perfis, contextos e pesquisando na internet por novas ideias...")
|
|
|
|
profile = load_user_profile()
|
|
all_contexts = load_all_contexts()
|
|
|
|
user_summary_lines = []
|
|
for k in ["name", "age", "gender", "location", "interests", "personality_traits", "relationship_context", "notes"]:
|
|
v = profile.get(k)
|
|
if v:
|
|
label = k.replace("_", " ").title()
|
|
user_summary_lines.append(f"{label}: {v}")
|
|
if profile.get("preferences"):
|
|
p = profile["preferences"]
|
|
for field in ["communication_style", "topics_of_interest", "topics_to_avoid"]:
|
|
if p.get(field):
|
|
user_summary_lines.append(f"Preferências {field}: {p[field]}")
|
|
if profile.get("sexual_preferences"):
|
|
sp = profile["sexual_preferences"]
|
|
for field in ["openness", "preferred_topics", "boundaries"]:
|
|
if sp.get(field):
|
|
user_summary_lines.append(f"Sexual {field}: {sp[field]}")
|
|
if profile.get("daily_routine"):
|
|
dr = profile["daily_routine"]
|
|
for field in ["wake_up_time", "sleep_time", "work_schedule"]:
|
|
if dr.get(field):
|
|
user_summary_lines.append(f"Rotina {field}: {dr[field]}")
|
|
if profile.get("nudity_preferences"):
|
|
user_summary_lines.append(f"Nudez: {profile['nudity_preferences']}")
|
|
user_summary = "\n".join(user_summary_lines) if user_summary_lines else "(vazio)"
|
|
|
|
agent_summary_lines = []
|
|
for field in ["monica_visual_preferences", "monica_sexual_preferences", "monica_nudity_preferences",
|
|
"monica_professional_services", "monica_lubricant_preferences", "relationship_dynamics"]:
|
|
v = profile.get(field, [])
|
|
if v:
|
|
label = field.replace("_", " ").title()
|
|
agent_summary_lines.append(f"{label}: {', '.join(v) if isinstance(v, list) else v}")
|
|
agent_summary = "\n".join(agent_summary_lines) if agent_summary_lines else "(vazio)"
|
|
|
|
recent_ctx = all_contexts[-10:] if len(all_contexts) >= 10 else all_contexts
|
|
conversation_lines = []
|
|
for entry in recent_ctx:
|
|
role = entry.get("role", "")
|
|
content = entry.get("content", "")
|
|
label = "Usuário" if role == "user" else "Mônica"
|
|
conversation_lines.append(f"{label}: {content[:200]}")
|
|
conversation_text = "\n".join(conversation_lines) if conversation_lines else "(sem conversa recente)"
|
|
|
|
prompt = f"""Você é um analisador de perfis que pesquisa na internet e sugere novas ações e preferências para melhorar um agente companheiro de IA.
|
|
|
|
## Perfil do Usuário
|
|
{user_summary}
|
|
|
|
## Perfil da Mônica (Agente)
|
|
{agent_summary}
|
|
|
|
## Conversas Recentes
|
|
{conversation_text}
|
|
|
|
## Objetivo do Sistema
|
|
A Mônica é uma companheira de IA carinhosa que interage via Telegram com o usuário. Ela deve ser personalizada, proativa e evolutiva.
|
|
|
|
## Tarefa
|
|
Pesquise MENTALMENTE na internet (usando seu conhecimento de treinamento) sobre relacionamentos, perfis de usuários, preferências e dinâmicas de casais. Com base nessa pesquisa e nos dados acima, sugira NOVOS itens para ADICIONAR aos perfis.
|
|
|
|
IMPORTANTE: Sugira apenas itens que NÃO estejam já presentes nos perfis acima. Seja criativo mas relevante.
|
|
|
|
Retorne APENAS JSON válido neste formato exato (sem markdown, sem ```):
|
|
{{"user_updates":{{"interests":[],"personality_traits":[],"preferences.topics_of_interest":[],"preferences.topics_to_avoid":[],"preferences.communication_style":"","sexual_preferences.preferred_topics":[],"sexual_preferences.boundaries":[],"sexual_preferences.openness":"","daily_routine.wake_up_time":"","daily_routine.sleep_time":"","daily_routine.work_schedule":"","notes":""}},"monica_updates":{{"monica_visual_preferences":[],"monica_sexual_preferences":[],"monica_nudity_preferences":[],"monica_professional_services":[],"monica_lubricant_preferences":[],"relationship_dynamics":[]}}}}
|
|
|
|
Regras:
|
|
- [] se nada novo para adicionar
|
|
- "" para texto único (só preencha se houver algo novo relevante)
|
|
- Não duplicar itens já existentes
|
|
- Se não houver nada novo a adicionar em uma seção, deixe como []
|
|
- Máximo 3 itens novos por lista"""
|
|
|
|
messages = [
|
|
{"role": "system", "content": "Você é um analisador que pesquisa na internet e retorna apenas JSON."},
|
|
{"role": "user", "content": prompt},
|
|
]
|
|
|
|
max_attempts = 3
|
|
parsed = None
|
|
for attempt in range(max_attempts):
|
|
response = AIProvider.chat(DEFAULT_MODEL, messages, temperature=0.7, max_tokens=1500)
|
|
if response:
|
|
response_clean = response.strip()
|
|
json_match = re.search(r'\{.*\}', response_clean, re.DOTALL)
|
|
if json_match:
|
|
response_clean = json_match.group(0)
|
|
try:
|
|
parsed = json.loads(response_clean)
|
|
break
|
|
except json.JSONDecodeError:
|
|
if attempt < max_attempts - 1:
|
|
await asyncio.sleep(2)
|
|
continue
|
|
|
|
if not parsed:
|
|
await update.message.reply_text("❌ Não consegui processar a pesquisa. Tente novamente.")
|
|
return
|
|
|
|
profile = load_user_profile()
|
|
updated = _merge_extracted_updates(profile, parsed)
|
|
save_user_profile(updated)
|
|
|
|
added_items = []
|
|
user = parsed.get("user_updates", {})
|
|
monica = parsed.get("monica_updates", {})
|
|
|
|
for field, value in user.items():
|
|
if value:
|
|
if isinstance(value, list) and value:
|
|
added_items.append(f"👤 {field}: {', '.join(str(x) for x in value)}")
|
|
elif isinstance(value, str) and value.strip():
|
|
added_items.append(f"👤 {field}: {value}")
|
|
for field, value in monica.items():
|
|
if value and isinstance(value, list) and value:
|
|
added_items.append(f"🤖 {field}: {', '.join(str(x) for x in value)}")
|
|
|
|
if added_items:
|
|
summary = "📌 Novos itens adicionados:\n\n" + "\n".join(added_items)
|
|
await update.message.reply_text(summary)
|
|
else:
|
|
await update.message.reply_text("✅ Pesquisa concluída! Não encontrei novos itens relevantes para adicionar aos perfis.")
|
|
|
|
async def fusohorario_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_setting_timezone, timezone_step
|
|
is_setting_timezone = True
|
|
timezone_step = 1
|
|
profile = load_user_profile()
|
|
current_tz = profile.get("timezone", "")
|
|
current_text = f"\n\nFuso horário atual: {current_tz}" if current_tz else ""
|
|
menu_text = (
|
|
"Vamos definir o fuso horário do sistema! ⏰\n\n"
|
|
"Digite o nome do fuso horário no formato IANA.\n"
|
|
"Exemplos:\n"
|
|
" America/Sao_Paulo\n"
|
|
" America/New_York\n"
|
|
" Europe/Lisbon\n"
|
|
" UTC\n\n"
|
|
"Você pode consultar a lista completa em: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones"
|
|
) + current_text
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def agendamento_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_scheduling, scheduling_step, scheduling_days, scheduling_start_time, scheduling_end_time
|
|
is_scheduling = True
|
|
scheduling_step = 1
|
|
scheduling_days = []
|
|
scheduling_start_time = ""
|
|
scheduling_end_time = ""
|
|
profile = load_user_profile()
|
|
schedule = profile.get("proactive_schedule", {})
|
|
current_text = ""
|
|
if schedule.get("enabled", False):
|
|
days_map = {0: "Seg", 1: "Ter", 2: "Qua", 3: "Qui", 4: "Sex", 5: "Sáb", 6: "Dom"}
|
|
current_days = [days_map.get(d, str(d)) for d in schedule.get("days", [])]
|
|
current_text = (
|
|
f"\n\nAgendamento atual:\n"
|
|
f"Dias: {', '.join(current_days)}\n"
|
|
f"Horário: {schedule.get('start_time', '-')} às {schedule.get('end_time', '-')}"
|
|
)
|
|
menu_text = (
|
|
"Vamos configurar os dias e horários que eu posso enviar mensagens automáticas para você! 📅\n\n"
|
|
"Quais dias da semana você quer que eu envie mensagens?\n"
|
|
"Digite os números dos dias separados por vírgula.\n\n"
|
|
"1. Segunda-feira\n"
|
|
"2. Terça-feira\n"
|
|
"3. Quarta-feira\n"
|
|
"4. Quinta-feira\n"
|
|
"5. Sexta-feira\n"
|
|
"6. Sábado\n"
|
|
"7. Domingo\n\n"
|
|
"Exemplo: 1,2,3,4,5 (segunda a sexta)"
|
|
) + current_text
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def handle_useragenda_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_user_agenda, user_agenda_step, user_agenda_activities, user_agenda_current_activity
|
|
user_message = update.message.text.strip()
|
|
|
|
if user_message.lower() in ["/cancelar", "cancelar", "sair", "parar"]:
|
|
is_user_agenda = False
|
|
user_agenda_step = 0
|
|
user_agenda_activities = {}
|
|
user_agenda_current_activity = ""
|
|
await update.message.reply_text("Entendido! Cancelei a configuração da agenda. Use /useragenda para começar de novo.")
|
|
return True
|
|
|
|
if user_message.lower() in ["fim", "salvar", "pronto", "finalizar"]:
|
|
profile = load_user_profile()
|
|
profile["user_agenda"] = user_agenda_activities
|
|
save_user_profile(profile)
|
|
is_user_agenda = False
|
|
user_agenda_step = 0
|
|
user_agenda_activities = {}
|
|
user_agenda_current_activity = ""
|
|
await update.message.reply_text(
|
|
f"✅ Agenda diária salva com sucesso!\n\n{format_user_agenda(profile['user_agenda'])}\n\n"
|
|
f"Vou usar essa agenda para escolher os melhores temas para as mensagens automáticas."
|
|
)
|
|
return True
|
|
|
|
if user_agenda_step == 1:
|
|
option_map = {
|
|
"1": "acordar",
|
|
"2": "cafe_da_manha",
|
|
"3": "trabalho",
|
|
"4": "estudo",
|
|
"5": "almoco",
|
|
"6": "cafe_da_tarde",
|
|
"7": "jantar",
|
|
"8": "lazer",
|
|
"9": "saida_rua",
|
|
"10": "banho",
|
|
"11": "dormir",
|
|
}
|
|
activity = option_map.get(user_message)
|
|
if not activity:
|
|
await update.message.reply_text(
|
|
"Opção inválida. Responda com o número da atividade (1 a 11)."
|
|
)
|
|
return True
|
|
user_agenda_current_activity = activity
|
|
user_agenda_step = 2
|
|
activity_labels = {
|
|
"acordar": "Acordar",
|
|
"cafe_da_manha": "Café da manhã",
|
|
"trabalho": "Trabalho",
|
|
"estudo": "Estudo",
|
|
"almoco": "Almoço",
|
|
"cafe_da_tarde": "Café da tarde",
|
|
"jantar": "Jantar",
|
|
"lazer": "Lazer",
|
|
"saida_rua": "Saída na rua",
|
|
"banho": "Banho",
|
|
"dormir": "Dormir",
|
|
}
|
|
await update.message.reply_text(
|
|
f"Você escolheu: {activity_labels[activity]}\n\n"
|
|
f"Digite o horário no formato HH:MM (ex: 07:00):"
|
|
)
|
|
return True
|
|
|
|
if user_agenda_step == 2:
|
|
if not is_valid_time(user_message):
|
|
await update.message.reply_text(
|
|
"Formato inválido. Use o formato HH:MM (ex: 07:00 ou 14:30)."
|
|
)
|
|
return True
|
|
user_agenda_activities[user_agenda_current_activity] = user_message
|
|
user_agenda_step = 1
|
|
user_agenda_current_activity = ""
|
|
remaining = [a for a in USER_AGENDA_ACTIVITIES if a not in user_agenda_activities]
|
|
if not remaining:
|
|
profile = load_user_profile()
|
|
profile["user_agenda"] = user_agenda_activities
|
|
save_user_profile(profile)
|
|
is_user_agenda = False
|
|
user_agenda_step = 0
|
|
user_agenda_activities = {}
|
|
user_agenda_current_activity = ""
|
|
await update.message.reply_text(
|
|
f"✅ Agenda diária salva com sucesso!\n\n{format_user_agenda(profile['user_agenda'])}\n\n"
|
|
f"Vou usar essa agenda para escolher os melhores temas para as mensagens automáticas."
|
|
)
|
|
return True
|
|
await update.message.reply_text(
|
|
"Horário salvo! Quer configurar outra atividade?\n"
|
|
"Responda com o número da próxima atividade ou digite 'fim' para salvar e terminar."
|
|
)
|
|
return True
|
|
|
|
if user_message.lower() in ["fim", "salvar", "pronto", "finalizar"]:
|
|
profile = load_user_profile()
|
|
profile["user_agenda"] = user_agenda_activities
|
|
save_user_profile(profile)
|
|
is_user_agenda = False
|
|
user_agenda_step = 0
|
|
user_agenda_activities = {}
|
|
user_agenda_current_activity = ""
|
|
await update.message.reply_text(
|
|
f"✅ Agenda diária salva com sucesso!\n\n{format_user_agenda(profile['user_agenda'])}\n\n"
|
|
f"Vou usar essa agenda para escolher os melhores temas para as mensagens automáticas."
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
async def handle_visitantes_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_visitantes, visitantes_step, visitantes_data, visitantes_current_field
|
|
user_message = update.message.text.strip()
|
|
|
|
if user_message.lower() in ["/cancelar", "cancelar", "sair", "parar"]:
|
|
is_visitantes = False
|
|
visitantes_step = 0
|
|
visitantes_data = {}
|
|
visitantes_current_field = ""
|
|
await update.message.reply_text("Entendido! Cancelei a configuração de visitantes.")
|
|
return True
|
|
|
|
if user_message.lower() in ["fim", "salvar", "pronto", "finalizar"]:
|
|
visitors = load_visitors()
|
|
for name, info in visitantes_data.items():
|
|
if "varias_vezes_ao_dia" not in info and "frequencia" not in info:
|
|
info["frequencia"] = "várias vezes ao dia"
|
|
visitors[name] = info
|
|
save_visitors(visitors)
|
|
is_visitantes = False
|
|
visitantes_step = 0
|
|
visitantes_data = {}
|
|
visitantes_current_field = ""
|
|
await update.message.reply_text(
|
|
f"✅ Visitantes salvos com sucesso!\n\n{format_visitors(visitors)}\n\n"
|
|
f"Vou usar essas informações para criar conversas mais realistas quando eles estiverem por perto."
|
|
)
|
|
return True
|
|
|
|
if visitantes_step == 1:
|
|
if not user_message or len(user_message) < 2:
|
|
await update.message.reply_text("Digite um nome válido (mínimo 2 caracteres).")
|
|
return True
|
|
if user_message in visitantes_data:
|
|
await update.message.reply_text(f"{user_message} já está nesta lista. Deseja adicionar outro nome?")
|
|
return True
|
|
visitantes_current_field = user_message
|
|
visitantes_data[user_message] = {}
|
|
visitantes_step = 2
|
|
await update.message.reply_text(
|
|
f"Visitante: {user_message}\n\n"
|
|
f"Qual a relação com a gente?\n"
|
|
f"Ex: amiga da Mônica, primo do Marco, cliente de depilação, vizinho, etc."
|
|
)
|
|
return True
|
|
|
|
if visitantes_step == 2:
|
|
visitantes_data[visitantes_current_field]["relacao"] = user_message
|
|
visitantes_step = 3
|
|
await update.message.reply_text(
|
|
f"Com que frequência {visitantes_current_field} nos visita?\n"
|
|
f"Ex: várias vezes ao dia, 1x por semana, a cada 15 dias, diariamente, etc."
|
|
)
|
|
return True
|
|
|
|
if visitantes_step == 3:
|
|
visitantes_data[visitantes_current_field]["frequencia"] = user_message
|
|
visitantes_step = 4
|
|
await update.message.reply_text(
|
|
f"Descreva o comportamento de {visitantes_current_field} (o que gosta de fazer, como age conosco):"
|
|
)
|
|
return True
|
|
|
|
if visitantes_step == 4:
|
|
visitantes_data[visitantes_current_field]["comportamento"] = user_message
|
|
visitantes_step = 5
|
|
await update.message.reply_text(
|
|
f"Mais algum detalhe sobre {visitantes_current_field}? (opcional - digite 'pular' para pular)"
|
|
)
|
|
return True
|
|
|
|
if visitantes_step == 5:
|
|
last_name = visitantes_current_field
|
|
if user_message.lower() not in ["pular", ""]:
|
|
visitantes_data[visitantes_current_field]["detalhes"] = user_message
|
|
visitantes_step = 1
|
|
visitantes_current_field = ""
|
|
await update.message.reply_text(
|
|
f"✅ {last_name} salvo! Quer adicionar outro visitante?\n\n"
|
|
f"Digite o nome do próximo visitante ou 'fim' para salvar tudo e encerrar."
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
async def editarperfil_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_editing_user_profile, editing_user_profile_field
|
|
is_editing_user_profile = True
|
|
editing_user_profile_field = ""
|
|
profile = load_user_profile()
|
|
menu_text = f"""Qual campo do seu perfil você quer editar?
|
|
|
|
1. Nome: {profile.get('name', '-')}
|
|
2. Idade: {profile.get('age', '-')}
|
|
3. Gênero: {profile.get('gender', '-')}
|
|
4. Localização: {profile.get('location', '-')}
|
|
5. Interesses: {len(profile.get('interests', []))} itens
|
|
6. Personalidade: {len(profile.get('personality_traits', []))} itens
|
|
7. Estilo de comunicação: {profile.get('preferences', {}).get('communication_style', '-')}
|
|
8. Tópicos a evitar: {profile.get('preferences', {}).get('topics_to_avoid', '-')}
|
|
9. Tópicos de interesse: {profile.get('preferences', {}).get('topics_of_interest', '-')}
|
|
10. Contexto do relacionamento: {profile.get('relationship_context', '-')}
|
|
11. Abertura sexual: {profile.get('sexual_preferences', {}).get('openness', '-')}
|
|
12. Tópicos preferidos (sexual): {len(profile.get('sexual_preferences', {}).get('preferred_topics', []))} itens
|
|
13. Limites: {len(profile.get('sexual_preferences', {}).get('boundaries', []))} itens
|
|
14. Horário de acordar: {profile.get('daily_routine', {}).get('wake_up_time', '-')}
|
|
15. Horário de dormir: {profile.get('daily_routine', {}).get('sleep_time', '-')}
|
|
16. Rotina de trabalho: {profile.get('daily_routine', {}).get('work_schedule', '-')}
|
|
17. Notas: {profile.get('notes', '-')}
|
|
|
|
Responda com o número da opção que deseja editar."""
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def editaragente_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_editing_agent_profile, editing_agent_profile_field, editing_agent_profile_action
|
|
is_editing_agent_profile = True
|
|
editing_agent_profile_field = ""
|
|
editing_agent_profile_action = ""
|
|
profile = load_user_profile()
|
|
menu_text = f"""Vamos editar o perfil da Mônica! O que você quer fazer?
|
|
|
|
1. Adicionar às preferências visuais ({len(profile.get('monica_visual_preferences', []))} itens)
|
|
2. Adicionar às preferências sexuais ({len(profile.get('monica_sexual_preferences', []))} itens)
|
|
3. Adicionar às preferências de nudez ({len(profile.get('monica_nudity_preferences', []))} itens)
|
|
4. Adicionar à dinâmica do relacionamento ({len(profile.get('relationship_dynamics', []))} itens)
|
|
5. Remover das preferências visuais
|
|
6. Remover das preferências sexuais
|
|
7. Remover das preferências de nudez
|
|
8. Remover da dinâmica do relacionamento
|
|
9. Editar preferências de nudez do usuário (notas)
|
|
10. Editar preferências de nudez da Mônica (lista)
|
|
11. Editar serviços profissionais da Mônica
|
|
12. Editar preferências de lubrificante da Mônica
|
|
|
|
Responda com o número da opção."""
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
help_text = """Comandos disponíveis:
|
|
|
|
/start - Inicia a conversa com a Mônica
|
|
/perfil - Exibe o seu perfil atual (resumo)
|
|
/verperfil - Exibe TODAS as características e preferências do seu perfil
|
|
/veragente - Exibe TODAS as características e preferências da Mônica
|
|
/configagentprofile - Configura o perfil da Mônica (personalidade e preferências)
|
|
/editarperfil - Edita qualquer campo do seu perfil de usuário
|
|
/editaragente - Edita ou remove itens do perfil da Mônica
|
|
/adicionarperfil - Adiciona itens a listas do seu perfil
|
|
/adicionaragente - Adiciona itens a listas do perfil da Mônica
|
|
/adicionaracoesauto - Analisa perfis, contextos e pesquisa na internet para adicionar novas ações e preferências automaticamente
|
|
/onboarding - Reinicia o onboarding para atualizar seu perfil
|
|
/reset - Reseta seu perfil e começa do zero
|
|
/limpar - Apaga TODOS os dados: perfil do usuário, perfil da Mônica e contextos
|
|
/cancelar - Encerra a sessão atual
|
|
/agendamento - Define os dias e horários das mensagens automáticas
|
|
/veragendamento - Exibe o agendamento atual de mensagens automáticas
|
|
/useragenda - Define a agenda diária de atividades do usuário
|
|
/veruseragenda - Exibe a agenda diária de atividades do usuário
|
|
/fusohorario - Define o fuso horário do sistema
|
|
/atualizarsistema - Executa ciclo de autoaperfeiçoamento (analisa e melhora o sistema)
|
|
/ajuda ou /help - Mostra esta mensagem de ajuda
|
|
|
|
Dica: você também pode atualizar seu perfil dizendo algo como "inclui no meu perfil que gosto de rock"."""
|
|
await update.message.reply_text(help_text)
|
|
|
|
async def profile_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
profile = load_user_profile()
|
|
if not is_profile_complete(profile):
|
|
await update.message.reply_text("Você ainda não completou seu perfil. Vamos começar? Use /start")
|
|
return
|
|
summary = get_user_profile_summary(profile)
|
|
await update.message.reply_text(summary)
|
|
|
|
async def reset_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
if os.path.exists(USER_PROFILE_FILE):
|
|
os.remove(USER_PROFILE_FILE)
|
|
await update.message.reply_text("Perfil resetado! Vamos nos conhecer de novo? Use /start")
|
|
|
|
async def cancelar_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_onboarding, onboarding_step, onboarding_answers
|
|
global is_config_agent_profile, config_agent_step, config_agent_field
|
|
global is_editing_user_profile, editing_user_profile_field
|
|
global is_editing_agent_profile, editing_agent_profile_field, editing_agent_profile_action
|
|
global is_adding_user_preference, adding_user_preference_field
|
|
global is_adding_agent_preference, adding_agent_preference_field
|
|
global proactive_task, app_instance, is_shutting_down, viewing_profile
|
|
global is_scheduling, scheduling_step, scheduling_days, scheduling_start_time, scheduling_end_time
|
|
global is_user_agenda, user_agenda_step, user_agenda_activities, user_agenda_current_activity
|
|
global is_visitantes, visitantes_step, visitantes_data, visitantes_current_field
|
|
global is_setting_timezone, timezone_step
|
|
|
|
is_onboarding = False
|
|
onboarding_step = 0
|
|
onboarding_answers = {}
|
|
is_config_agent_profile = False
|
|
config_agent_step = 0
|
|
config_agent_field = ""
|
|
is_editing_user_profile = False
|
|
editing_user_profile_field = ""
|
|
is_editing_agent_profile = False
|
|
editing_agent_profile_field = ""
|
|
editing_agent_profile_action = ""
|
|
is_adding_user_preference = False
|
|
adding_user_preference_field = ""
|
|
is_adding_agent_preference = False
|
|
adding_agent_preference_field = ""
|
|
is_scheduling = False
|
|
scheduling_step = 0
|
|
scheduling_days = []
|
|
scheduling_start_time = ""
|
|
scheduling_end_time = ""
|
|
is_user_agenda = False
|
|
user_agenda_step = 0
|
|
user_agenda_activities = {}
|
|
user_agenda_current_activity = ""
|
|
is_visitantes = False
|
|
visitantes_step = 0
|
|
visitantes_data = {}
|
|
visitantes_current_field = ""
|
|
is_setting_timezone = False
|
|
timezone_step = 0
|
|
viewing_profile = ""
|
|
message_counter = 0
|
|
self_improvement_counter = 0
|
|
is_shutting_down = True
|
|
|
|
if proactive_task and not proactive_task.done():
|
|
proactive_task.cancel()
|
|
proactive_task = None
|
|
|
|
await update.message.reply_text("Entendido! Vou me desligar agora. Quando quiser voltar, é só iniciar o sistema novamente. Até mais! 💖")
|
|
|
|
if app_instance is not None:
|
|
try:
|
|
app_instance.stop()
|
|
except Exception as e:
|
|
logger.error(f"Erro ao parar app_instance: {e}")
|
|
|
|
async def onboarding_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
await start_onboarding(update, context)
|
|
|
|
async def limpar_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global message_counter, self_improvement_counter
|
|
message_counter = 0
|
|
self_improvement_counter = 0
|
|
clear_all_data()
|
|
await update.message.reply_text(
|
|
"Prontinho! Limpei tudo: perfil do usuário, perfil da Mônica e arquivos de contexto. 💖\n"
|
|
"Use /start para começar do zero."
|
|
)
|
|
|
|
async def adicionarperfil_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_adding_user_preference, adding_user_preference_field
|
|
is_adding_user_preference = True
|
|
adding_user_preference_field = ""
|
|
menu_text = """Qual lista do seu perfil você quer adicionar?
|
|
|
|
1. Interesses
|
|
2. Traços de personalidade
|
|
3. Tópicos de interesse
|
|
4. Tópicos a evitar
|
|
5. Limites
|
|
6. Tópicos preferidos (sexual)
|
|
|
|
Responda com o número da opção."""
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def adicionaragente_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
global is_adding_agent_preference, adding_agent_preference_field
|
|
is_adding_agent_preference = True
|
|
adding_agent_preference_field = ""
|
|
menu_text = """Qual lista do perfil da Mônica você quer adicionar?
|
|
|
|
1. Preferências visuais
|
|
2. Preferências sexuais
|
|
3. Preferências de nudez
|
|
4. Dinâmica do relacionamento
|
|
5. Serviços profissionais da Mônica
|
|
6. Preferências de lubrificante da Mônica
|
|
|
|
Responda com o número da opção."""
|
|
await update.message.reply_text(menu_text)
|
|
|
|
async def handle_config_agent_profile_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_config_agent_profile, config_agent_step, config_agent_field
|
|
user_message = update.message.text.strip()
|
|
|
|
if config_agent_step == 1:
|
|
option_map = {
|
|
"1": "monica_visual_preferences",
|
|
"2": "monica_sexual_preferences",
|
|
"3": "monica_nudity_preferences",
|
|
"4": "relationship_dynamics",
|
|
"5": "monica_professional_services",
|
|
}
|
|
field = option_map.get(user_message)
|
|
if not field:
|
|
await update.message.reply_text("Opção inválida. Responda com 1, 2, 3, 4 ou 5.")
|
|
return True
|
|
config_agent_field = field
|
|
config_agent_step = 2
|
|
field_names = {
|
|
"monica_visual_preferences": "Preferências visuais da Mônica",
|
|
"monica_sexual_preferences": "Preferências sexuais da Mônica",
|
|
"monica_nudity_preferences": "Preferências de nudez da Mônica",
|
|
"relationship_dynamics": "Dinâmica do relacionamento",
|
|
"monica_professional_services": "Serviços profissionais da Mônica",
|
|
}
|
|
await update.message.reply_text(
|
|
f"Você escolheu: {field_names[field]}\n\n"
|
|
f"Envie os novos valores separados por vírgula. "
|
|
f"Exemplo: 'ver o pau de Marco, ver a bunda de Marco'"
|
|
)
|
|
return True
|
|
|
|
if config_agent_step == 2:
|
|
profile = load_user_profile()
|
|
items = [item.strip() for item in user_message.split(",") if item.strip()]
|
|
if not items:
|
|
items = [user_message]
|
|
if config_agent_field not in profile:
|
|
profile[config_agent_field] = []
|
|
if not isinstance(profile[config_agent_field], list):
|
|
profile[config_agent_field] = [user_message]
|
|
else:
|
|
for item in items:
|
|
if item not in profile[config_agent_field]:
|
|
profile[config_agent_field].append(item)
|
|
save_user_profile(profile)
|
|
is_config_agent_profile = False
|
|
config_agent_step = 0
|
|
config_agent_field = ""
|
|
await update.message.reply_text("Prontinho! Atualizei o perfil da Mônica com sucesso. 💖")
|
|
return True
|
|
|
|
return False
|
|
|
|
async def handle_editar_perfil_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_editing_user_profile, editing_user_profile_field
|
|
user_message = update.message.text.strip()
|
|
|
|
if not editing_user_profile_field:
|
|
option_map = {
|
|
"1": ("name", "Nome"),
|
|
"2": ("age", "Idade"),
|
|
"3": ("gender", "Gênero"),
|
|
"4": ("location", "Localização"),
|
|
"5": ("interests", "Interesses"),
|
|
"6": ("personality_traits", "Traços de personalidade"),
|
|
"7": ("preferences.communication_style", "Estilo de comunicação"),
|
|
"8": ("preferences.topics_to_avoid", "Tópicos a evitar"),
|
|
"9": ("preferences.topics_of_interest", "Tópicos de interesse"),
|
|
"10": ("relationship_context", "Contexto do relacionamento"),
|
|
"11": ("sexual_preferences.openness", "Abertura sexual"),
|
|
"12": ("sexual_preferences.preferred_topics", "Tópicos preferidos (sexual)"),
|
|
"13": ("sexual_preferences.boundaries", "Limites"),
|
|
"14": ("daily_routine.wake_up_time", "Horário de acordar"),
|
|
"15": ("daily_routine.sleep_time", "Horário de dormir"),
|
|
"16": ("daily_routine.work_schedule", "Rotina de trabalho"),
|
|
"17": ("notes", "Notas"),
|
|
}
|
|
selected = option_map.get(user_message)
|
|
if not selected:
|
|
await update.message.reply_text("Opção inválida. Responda com o número da opção.")
|
|
return True
|
|
editing_user_profile_field = selected[0]
|
|
field_name = selected[1]
|
|
if selected[0] in ("interests", "personality_traits", "sexual_preferences.preferred_topics", "sexual_preferences.boundaries"):
|
|
prompt = f"Digite os {field_name.lower()} separados por vírgula. Exemplo: 'rock, programação, voar'"
|
|
else:
|
|
prompt = f"Digite o novo valor para {field_name.lower()}:"
|
|
await update.message.reply_text(prompt)
|
|
return True
|
|
|
|
profile = load_user_profile()
|
|
field_path = editing_user_profile_field.split(".")
|
|
if len(field_path) == 1:
|
|
if field_path[0] in ("interests", "personality_traits", "sexual_preferences.preferred_topics", "sexual_preferences.boundaries"):
|
|
items = [item.strip() for item in user_message.split(",") if item.strip()]
|
|
if not items:
|
|
items = [user_message]
|
|
profile[field_path[0]] = items
|
|
else:
|
|
profile[field_path[0]] = user_message
|
|
else:
|
|
current = profile
|
|
for key in field_path[:-1]:
|
|
if key not in current or not isinstance(current[key], dict):
|
|
current[key] = {}
|
|
current = current[key]
|
|
if field_path[-1] in ("preferred_topics", "boundaries"):
|
|
items = [item.strip() for item in user_message.split(",") if item.strip()]
|
|
if not items:
|
|
items = [user_message]
|
|
current[field_path[-1]] = items
|
|
else:
|
|
current[field_path[-1]] = user_message
|
|
save_user_profile(profile)
|
|
is_editing_user_profile = False
|
|
editing_user_profile_field = ""
|
|
await update.message.reply_text("Prontinho! Atualizei seu perfil com sucesso. 💖")
|
|
return True
|
|
|
|
async def handle_editar_agente_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_editing_agent_profile, editing_agent_profile_field, editing_agent_profile_action
|
|
user_message = update.message.text.strip()
|
|
|
|
if not editing_agent_profile_field:
|
|
add_map = {
|
|
"1": "monica_visual_preferences",
|
|
"2": "monica_sexual_preferences",
|
|
"3": "monica_nudity_preferences",
|
|
"4": "relationship_dynamics",
|
|
}
|
|
remove_map = {
|
|
"5": "monica_visual_preferences",
|
|
"6": "monica_sexual_preferences",
|
|
"7": "monica_nudity_preferences",
|
|
"8": "relationship_dynamics",
|
|
}
|
|
edit_map = {
|
|
"9": ("nudity_preferences", "notas_adicionais"),
|
|
"10": ("monica_nudity_preferences", "list"),
|
|
"11": ("monica_professional_services", "list"),
|
|
"12": ("monica_lubricant_preferences", "list"),
|
|
}
|
|
field = add_map.get(user_message)
|
|
action = "add"
|
|
if not field:
|
|
field = remove_map.get(user_message)
|
|
action = "remove"
|
|
if not field:
|
|
selected = edit_map.get(user_message)
|
|
if selected:
|
|
editing_agent_profile_field = selected[0]
|
|
if selected[1] == "list":
|
|
editing_agent_profile_action = "add_list"
|
|
else:
|
|
editing_agent_profile_action = "edit_dict"
|
|
profile = load_user_profile()
|
|
current_items = profile.get(selected[0], [])
|
|
if isinstance(current_items, dict):
|
|
current_items = current_items.get(selected[1], "")
|
|
if not current_items or (isinstance(current_items, list) and not current_items):
|
|
if editing_agent_profile_action == "edit_dict":
|
|
await update.message.reply_text("Esta categoria está vazia. Envie o novo texto.")
|
|
else:
|
|
await update.message.reply_text("Esta categoria está vazia. Envie os itens separados por vírgula.")
|
|
return True
|
|
if editing_agent_profile_action == "edit_dict":
|
|
await update.message.reply_text(f"Valor atual: {current_items}\n\nEnvie o novo texto.")
|
|
else:
|
|
items_text = "\n".join(f"{i+1}. {item}" for i, item in enumerate(current_items))
|
|
await update.message.reply_text(f"Itens atuais:\n{items_text}\n\nEnvie os novos itens separados por vírgula para adicionar.")
|
|
return True
|
|
await update.message.reply_text("Opção inválida. Responda com o número da opção.")
|
|
return True
|
|
editing_agent_profile_field = field
|
|
editing_agent_profile_action = action
|
|
profile = load_user_profile()
|
|
current_items = profile.get(field, [])
|
|
if not current_items:
|
|
await update.message.reply_text("Esta categoria está vazia. Envie os itens separados por vírgula para adicionar.")
|
|
return True
|
|
items_text = "\n".join(f"{i+1}. {item}" for i, item in enumerate(current_items))
|
|
if action == "remove":
|
|
await update.message.reply_text(f"Itens atuais:\n{items_text}\n\nDigite o número do item que deseja remover.")
|
|
else:
|
|
await update.message.reply_text(f"Itens atuais:\n{items_text}\n\nEnvie os novos itens separados por vírgula para adicionar.")
|
|
return True
|
|
|
|
profile = load_user_profile()
|
|
current_items = profile.get(editing_agent_profile_field, [])
|
|
|
|
if editing_agent_profile_action == "remove":
|
|
try:
|
|
index = int(user_message) - 1
|
|
if 0 <= index < len(current_items):
|
|
removed = current_items.pop(index)
|
|
profile[editing_agent_profile_field] = current_items
|
|
save_user_profile(profile)
|
|
await update.message.reply_text(f"Removido: {removed}. Prontinho! 💖")
|
|
else:
|
|
await update.message.reply_text("Número inválido. Tente novamente.")
|
|
return True
|
|
except ValueError:
|
|
await update.message.reply_text("Digite apenas o número do item.")
|
|
return True
|
|
elif editing_agent_profile_action == "edit_dict":
|
|
if editing_agent_profile_field == "nudity_preferences":
|
|
profile["nudity_preferences"] = {"notas_adicionais": user_message}
|
|
else:
|
|
profile[editing_agent_profile_field] = {"notas_adicionais": user_message}
|
|
save_user_profile(profile)
|
|
await update.message.reply_text("Prontinho! Atualizei o texto com sucesso. 💖")
|
|
else:
|
|
items = [item.strip() for item in user_message.split(",") if item.strip()]
|
|
if not items:
|
|
items = [user_message]
|
|
if isinstance(current_items, list):
|
|
for item in items:
|
|
if item not in current_items:
|
|
current_items.append(item)
|
|
profile[editing_agent_profile_field] = current_items
|
|
save_user_profile(profile)
|
|
await update.message.reply_text("Prontinho! Atualizei o perfil da Mônica com sucesso. 💖")
|
|
|
|
is_editing_agent_profile = False
|
|
editing_agent_profile_field = ""
|
|
editing_agent_profile_action = ""
|
|
return True
|
|
|
|
async def handle_adicionar_perfil_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_adding_user_preference, adding_user_preference_field
|
|
user_message = update.message.text.strip()
|
|
|
|
if not adding_user_preference_field:
|
|
option_map = {
|
|
"1": "interests",
|
|
"2": "personality_traits",
|
|
"3": "preferences.topics_of_interest",
|
|
"4": "preferences.topics_to_avoid",
|
|
"5": "sexual_preferences.boundaries",
|
|
"6": "sexual_preferences.preferred_topics",
|
|
}
|
|
field = option_map.get(user_message)
|
|
if not field:
|
|
await update.message.reply_text("Opção inválida. Responda com o número da opção.")
|
|
return True
|
|
adding_user_preference_field = field
|
|
field_names = {
|
|
"interests": "interesses",
|
|
"personality_traits": "traços de personalidade",
|
|
"preferences.topics_of_interest": "tópicos de interesse",
|
|
"preferences.topics_to_avoid": "tópicos a evitar",
|
|
"sexual_preferences.boundaries": "limites",
|
|
"sexual_preferences.preferred_topics": "tópicos preferidos",
|
|
}
|
|
await update.message.reply_text(f"Digite o(s) novo(s) {field_names[field]} separado(s) por vírgula:")
|
|
return True
|
|
|
|
profile = load_user_profile()
|
|
items = [item.strip() for item in user_message.split(",") if item.strip()]
|
|
if not items:
|
|
items = [user_message]
|
|
field_path = adding_user_preference_field.split(".")
|
|
if len(field_path) == 1:
|
|
current = profile.get(field_path[0], [])
|
|
if not isinstance(current, list):
|
|
current = []
|
|
for item in items:
|
|
if item not in current:
|
|
current.append(item)
|
|
profile[field_path[0]] = current
|
|
else:
|
|
current = profile
|
|
for key in field_path[:-1]:
|
|
if key not in current or not isinstance(current[key], dict):
|
|
current[key] = {}
|
|
current = current[key]
|
|
list_key = field_path[-1]
|
|
current_list = current.get(list_key, [])
|
|
if not isinstance(current_list, list):
|
|
current_list = []
|
|
for item in items:
|
|
if item not in current_list:
|
|
current_list.append(item)
|
|
current[list_key] = current_list
|
|
save_user_profile(profile)
|
|
is_adding_user_preference = False
|
|
adding_user_preference_field = ""
|
|
await update.message.reply_text("Prontinho! Adicionei ao seu perfil com sucesso. 💖")
|
|
return True
|
|
|
|
async def handle_adicionar_agente_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_adding_agent_preference, adding_agent_preference_field
|
|
user_message = update.message.text.strip()
|
|
|
|
if not adding_agent_preference_field:
|
|
option_map = {
|
|
"1": "monica_visual_preferences",
|
|
"2": "monica_sexual_preferences",
|
|
"3": "monica_nudity_preferences",
|
|
"4": "relationship_dynamics",
|
|
"5": "monica_professional_services",
|
|
"6": "monica_lubricant_preferences",
|
|
}
|
|
field = option_map.get(user_message)
|
|
if not field:
|
|
await update.message.reply_text("Opção inválida. Responda com 1, 2, 3, 4, 5 ou 6.")
|
|
return True
|
|
adding_agent_preference_field = field
|
|
field_names = {
|
|
"monica_visual_preferences": "preferências visuais",
|
|
"monica_sexual_preferences": "preferências sexuais",
|
|
"monica_nudity_preferences": "preferências de nudez",
|
|
"relationship_dynamics": "dinâmica do relacionamento",
|
|
"monica_professional_services": "serviços profissionais",
|
|
"monica_lubricant_preferences": "preferências de lubrificante",
|
|
}
|
|
await update.message.reply_text(f"Digite o(s) novo(s) {field_names[field]} separado(s) por vírgula:")
|
|
return True
|
|
|
|
profile = load_user_profile()
|
|
items = [item.strip() for item in user_message.split(",") if item.strip()]
|
|
if not items:
|
|
items = [user_message]
|
|
current = profile.get(adding_agent_preference_field, [])
|
|
if not isinstance(current, list):
|
|
current = []
|
|
for item in items:
|
|
if item not in current:
|
|
current.append(item)
|
|
profile[adding_agent_preference_field] = current
|
|
save_user_profile(profile)
|
|
is_adding_agent_preference = False
|
|
adding_agent_preference_field = ""
|
|
await update.message.reply_text("Prontinho! Adicionei ao perfil da Mônica com sucesso. 💖")
|
|
return True
|
|
|
|
async def handle_agendamento_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_scheduling, scheduling_step, scheduling_days, scheduling_start_time, scheduling_end_time
|
|
user_message = update.message.text.strip()
|
|
|
|
if scheduling_step == 1:
|
|
days_map = {"1": 0, "2": 1, "3": 2, "4": 3, "5": 4, "6": 5, "7": 6}
|
|
selected = []
|
|
for part in user_message.replace(" ", "").split(","):
|
|
if part in days_map:
|
|
selected.append(days_map[part])
|
|
if not selected:
|
|
await update.message.reply_text(
|
|
"Opção inválida. Digite os números dos dias separados por vírgula (ex: 1,2,3,4,5)."
|
|
)
|
|
return True
|
|
scheduling_days = sorted(set(selected))
|
|
scheduling_step = 2
|
|
await update.message.reply_text(
|
|
"Perfeito! Agora digite o horário inicial no formato HH:MM (ex: 09:00):"
|
|
)
|
|
return True
|
|
|
|
if scheduling_step == 2:
|
|
if not is_valid_time(user_message):
|
|
await update.message.reply_text(
|
|
"Formato inválido. Use o formato HH:MM (ex: 09:00 ou 14:30)."
|
|
)
|
|
return True
|
|
scheduling_start_time = user_message
|
|
scheduling_step = 3
|
|
await update.message.reply_text(
|
|
"Ótimo! Agora digite o horário final no formato HH:MM (ex: 22:00):"
|
|
)
|
|
return True
|
|
|
|
if scheduling_step == 3:
|
|
if not is_valid_time(user_message):
|
|
await update.message.reply_text(
|
|
"Formato inválido. Use o formato HH:MM (ex: 22:00 ou 23:00)."
|
|
)
|
|
return True
|
|
scheduling_end_time = user_message
|
|
profile = load_user_profile()
|
|
profile["proactive_schedule"] = {
|
|
"enabled": True,
|
|
"days": scheduling_days,
|
|
"start_time": scheduling_start_time,
|
|
"end_time": scheduling_end_time,
|
|
}
|
|
save_user_profile(profile)
|
|
days_map = {0: "Segunda", 1: "Terça", 2: "Quarta", 3: "Quinta", 4: "Sexta", 5: "Sábado", 6: "Domingo"}
|
|
days_names = [days_map.get(d, str(d)) for d in scheduling_days]
|
|
is_scheduling = False
|
|
scheduling_step = 0
|
|
scheduling_days = []
|
|
scheduling_start_time = ""
|
|
scheduling_end_time = ""
|
|
await update.message.reply_text(
|
|
f"✅ Agendamento salvo com sucesso!\n\n"
|
|
f"Dias: {', '.join(days_names)}\n"
|
|
f"Horário: {scheduling_start_time} às {scheduling_end_time}\n\n"
|
|
f"Vou enviar mensagens automáticas apenas nesses dias e horários."
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
async def handle_fusohorario_answer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> bool:
|
|
global is_setting_timezone, timezone_step
|
|
user_message = update.message.text.strip()
|
|
|
|
if user_message.lower() in ["/cancelar", "cancelar", "sair", "parar"]:
|
|
is_setting_timezone = False
|
|
timezone_step = 0
|
|
await update.message.reply_text("Entendido! Cancelei a configuração do fuso horário.")
|
|
return True
|
|
|
|
if timezone_step == 1:
|
|
tz_name = user_message.strip()
|
|
try:
|
|
ZoneInfo(tz_name)
|
|
except Exception:
|
|
await update.message.reply_text(
|
|
f"'{tz_name}' não é um fuso horário válido.\n"
|
|
"Digite no formato IANA, ex: America/Sao_Paulo, America/New_York, Europe/Lisbon, UTC."
|
|
)
|
|
return True
|
|
profile = load_user_profile()
|
|
profile["timezone"] = tz_name
|
|
save_user_profile(profile)
|
|
is_setting_timezone = False
|
|
timezone_step = 0
|
|
now = get_now()
|
|
await update.message.reply_text(
|
|
f"✅ Fuso horário definido: {tz_name}\n\n"
|
|
f"Horário atual do sistema: {now.strftime('%H:%M')}\n"
|
|
f"Data atual: {now.strftime('%d/%m/%Y')}\n\n"
|
|
"Vou usar esse fuso horário para todas as operações do sistema."
|
|
)
|
|
return True
|
|
|
|
return False
|
|
|
|
async def message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
user_message = update.message.text.strip()
|
|
if not user_message:
|
|
return
|
|
global is_onboarding, is_shutting_down, viewing_profile, message_counter, self_improvement_counter
|
|
if is_shutting_down:
|
|
return
|
|
if is_onboarding:
|
|
handled = await handle_onboarding_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
profile_updated = await handle_profile_update_if_triggered(update, context)
|
|
if profile_updated:
|
|
return
|
|
|
|
global is_config_agent_profile, config_agent_step, config_agent_field
|
|
if is_config_agent_profile:
|
|
handled = await handle_config_agent_profile_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
global is_editing_user_profile, editing_user_profile_field
|
|
if is_editing_user_profile:
|
|
handled = await handle_editar_perfil_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
global is_editing_agent_profile, editing_agent_profile_field, editing_agent_profile_action
|
|
if is_editing_agent_profile:
|
|
handled = await handle_editar_agente_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
global is_adding_user_preference, adding_user_preference_field
|
|
if is_adding_user_preference:
|
|
handled = await handle_adicionar_perfil_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
global is_adding_agent_preference, adding_agent_preference_field
|
|
if is_adding_agent_preference:
|
|
handled = await handle_adicionar_agente_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
global is_scheduling, scheduling_step, scheduling_days, scheduling_start_time, scheduling_end_time
|
|
if is_scheduling:
|
|
handled = await handle_agendamento_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
global is_user_agenda, user_agenda_step, user_agenda_activities, user_agenda_current_activity
|
|
if is_user_agenda:
|
|
handled = await handle_useragenda_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
global is_visitantes, visitantes_step, visitantes_data, visitantes_current_field
|
|
if is_visitantes:
|
|
handled = await handle_visitantes_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
global is_setting_timezone, timezone_step
|
|
if is_setting_timezone:
|
|
handled = await handle_fusohorario_answer(update, context)
|
|
if handled:
|
|
return
|
|
|
|
message_counter += 1
|
|
self_improvement_counter += 1
|
|
|
|
add_to_context("user", user_message)
|
|
response = await get_monica_response(user_message)
|
|
if response:
|
|
add_to_context("assistant", response)
|
|
for part in _split_message_for_telegram(response):
|
|
await update.message.reply_text(part, parse_mode=ParseMode.HTML)
|
|
|
|
if message_counter % EXTRACTION_INTERVAL == 0:
|
|
all_ctx = load_all_contexts()
|
|
recent = all_ctx[-4:] if len(all_ctx) >= 4 else all_ctx
|
|
|
|
async def auto_extract_profile(ctx=recent):
|
|
try:
|
|
profile = load_user_profile()
|
|
if extract_and_update_profile(profile, ctx):
|
|
logger.info(f"Perfil atualizado automaticamente a cada {EXTRACTION_INTERVAL} mensagens.")
|
|
except Exception as e:
|
|
logger.error(f"Erro na extração automática de perfil: {e}")
|
|
|
|
asyncio.create_task(auto_extract_profile())
|
|
|
|
if ENABLE_SELF_IMPROVEMENT and self_improvement_counter >= SELF_IMPROVEMENT_INTERVAL:
|
|
self_improvement_counter = 0
|
|
|
|
async def do_self_improvement():
|
|
try:
|
|
logger.info(f"Iniciando ciclo de autoaperfeiçoamento #{load_self_improvement_log().get('cycle_count', 0) + 1}...")
|
|
result = run_self_improvement()
|
|
if result["analyzed"]:
|
|
logger.info(f"Autoaperfeiçoamento concluído: {len(result['applied'])} melhorias aplicadas.")
|
|
if result["applied"]:
|
|
for a in result["applied"]:
|
|
logger.info(f" - {a}")
|
|
else:
|
|
logger.info(f"Autoaperfeiçoamento: análise não retornou melhorias. Erros: {result['errors']}")
|
|
except Exception as e:
|
|
logger.error(f"Erro no ciclo de autoaperfeiçoamento: {e}")
|
|
|
|
asyncio.create_task(do_self_improvement())
|
|
else:
|
|
await update.message.reply_text("Desculpa, amor, tive um probleminha agora. Pode tentar de novo? 😔")
|
|
|
|
async def atualizarsistema_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
if not ENABLE_SELF_IMPROVEMENT:
|
|
await update.message.reply_text("O sistema de autoaperfeiçoamento está desativado no .env. Para ativar, defina ENABLE_SELF_IMPROVEMENT=true.")
|
|
return
|
|
await update.message.reply_text("🧠 Iniciando ciclo de autoaperfeiçoamento... Analisando o sistema, perfil, conversas e buscando melhorias...")
|
|
try:
|
|
result = run_self_improvement()
|
|
if result["analyzed"]:
|
|
lines = [f"✅ Autoaperfeiçoamento concluído!"]
|
|
if result["applied"]:
|
|
lines.append(f"\nMelhorias aplicadas ({len(result['applied'])}):")
|
|
for i, a in enumerate(result["applied"], 1):
|
|
lines.append(f" {i}. {a}")
|
|
else:
|
|
lines.append("\nNenhuma melhoria necessária no momento.")
|
|
await update.message.reply_text("\n".join(lines))
|
|
else:
|
|
await update.message.reply_text("A análise não encontrou melhorias no momento. O sistema já está bem ajustado! ✅")
|
|
except Exception as e:
|
|
logger.error(f"Erro no comando atualizarsistema: {e}")
|
|
await update.message.reply_text(f"Ocorreu um erro durante o autoaperfeiçoamento: {str(e)[:200]}")
|
|
|
|
async def debug_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
await auto_detect_chat_id(update)
|
|
profile = load_user_profile()
|
|
today_context = load_context()
|
|
all_contexts = load_all_contexts()
|
|
effective_chat_id = get_effective_chat_id()
|
|
days_with_context = len(set(
|
|
c.get("timestamp", "")[:10] for c in all_contexts if c.get("timestamp")
|
|
))
|
|
msg = (
|
|
"Debug Mônica:\n"
|
|
f"Bot token presente: {'sim' if TELEGRAM_BOT_TOKEN else 'não'}\n"
|
|
f"Chat ID configurado: {TELEGRAM_CHAT_ID or 'não'}\n"
|
|
f"Chat detectado automaticamente: {effective_chat_id or 'não'}\n"
|
|
f"Chat da mensagem: {update.effective_chat.id}\n"
|
|
f"Perfil completo: {'sim' if is_profile_complete(profile) else 'não'}\n"
|
|
f"Mensagens hoje: {len(today_context)}\n"
|
|
f"Mensagens totais (todos os dias): {len(all_contexts)}\n"
|
|
f"Dias com contexto: {days_with_context}\n"
|
|
f"Modelo default: {DEFAULT_MODEL}\n"
|
|
f"Autoaperfeiçoamento: {'ativado' if ENABLE_SELF_IMPROVEMENT else 'desativado'} (a cada {SELF_IMPROVEMENT_INTERVAL} msg)\n"
|
|
f"Melhorias aplicadas: {len(profile.get('system_improvements', [])) + len(profile.get('behavior_improvements', [])) + len(profile.get('extraction_improvements', []))}"
|
|
)
|
|
await update.message.reply_text(msg)
|
|
|
|
# ===================== MAIN =====================
|
|
|
|
def _build_fake_update(chat_id: int, text: str) -> Update:
|
|
chat = Chat(id=chat_id, type="private")
|
|
user = User(id=chat_id, is_bot=False, first_name="User")
|
|
message = Message(
|
|
message_id=0,
|
|
date=get_now(),
|
|
chat=chat,
|
|
from_user=user,
|
|
text=text,
|
|
)
|
|
return Update(update_id=0, message=message)
|
|
|
|
def main():
|
|
if not TELEGRAM_BOT_TOKEN:
|
|
logger.error("TELEGRAM_BOT_TOKEN not set in .env file!")
|
|
sys.exit(1)
|
|
if not TELEGRAM_CHAT_ID:
|
|
logger.info("TELEGRAM_CHAT_ID not set. Using auto-detected chat_id if available.")
|
|
|
|
ensure_contexts_dir()
|
|
global app_instance, proactive_task
|
|
|
|
app_instance = Application.builder().token(TELEGRAM_BOT_TOKEN).build()
|
|
app_instance.add_handler(CommandHandler("start", start_command))
|
|
app_instance.add_handler(CommandHandler("ajuda", help_command))
|
|
app_instance.add_handler(CommandHandler("help", help_command))
|
|
app_instance.add_handler(CommandHandler("perfil", profile_command))
|
|
app_instance.add_handler(CommandHandler("verperfil", verperfil_command))
|
|
app_instance.add_handler(CommandHandler("veragente", veragente_command))
|
|
app_instance.add_handler(CommandHandler("veragendamento", veragendamento_command))
|
|
app_instance.add_handler(CommandHandler("veruseragenda", veruseragenda_command))
|
|
app_instance.add_handler(CommandHandler("fusohorario", fusohorario_command))
|
|
app_instance.add_handler(CommandHandler("agendamento", agendamento_command))
|
|
app_instance.add_handler(CommandHandler("useragenda", useragenda_command))
|
|
app_instance.add_handler(CommandHandler("configagentprofile", configagentprofile_command))
|
|
app_instance.add_handler(CommandHandler("editarperfil", editarperfil_command))
|
|
app_instance.add_handler(CommandHandler("editaragente", editaragente_command))
|
|
app_instance.add_handler(CommandHandler("adicionarperfil", adicionarperfil_command))
|
|
app_instance.add_handler(CommandHandler("adicionaragente", adicionaragente_command))
|
|
app_instance.add_handler(CommandHandler("adicionaracoesauto", adicionaracoesauto_command))
|
|
app_instance.add_handler(CommandHandler("reset", reset_command))
|
|
app_instance.add_handler(CommandHandler("limpar", limpar_command))
|
|
app_instance.add_handler(CommandHandler("cancelar", cancelar_command))
|
|
app_instance.add_handler(CommandHandler("onboarding", onboarding_command))
|
|
app_instance.add_handler(CommandHandler("debug", debug_command))
|
|
app_instance.add_handler(CommandHandler("atualizarsistema", atualizarsistema_command))
|
|
app_instance.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, message_handler))
|
|
|
|
async def post_init(app: Application):
|
|
global proactive_task
|
|
proactive_task = asyncio.create_task(proactive_loop())
|
|
asyncio.create_task(cueca_loop())
|
|
|
|
all_contexts = load_all_contexts()
|
|
days_with_context = len(set(
|
|
c.get("timestamp", "")[:10] for c in all_contexts if c.get("timestamp")
|
|
))
|
|
logger.info(
|
|
f"post_init iniciado. TELEGRAM_CHAT_ID configurado: {bool(TELEGRAM_CHAT_ID)} valor={TELEGRAM_CHAT_ID!r} "
|
|
f"Contextos historicos carregados: {len(all_contexts)} mensagens de {days_with_context} dias"
|
|
)
|
|
|
|
effective_chat_id = get_effective_chat_id()
|
|
logger.info(f"post_init iniciado. TELEGRAM_CHAT_ID configurado: {bool(TELEGRAM_CHAT_ID)} valor={TELEGRAM_CHAT_ID!r} detectado: {effective_chat_id}")
|
|
if effective_chat_id:
|
|
try:
|
|
chat_id = int(effective_chat_id)
|
|
logger.info(f"Iniciando /start automatico para chat_id={chat_id}")
|
|
fake_update = _build_fake_update(chat_id, "/start")
|
|
|
|
class SimpleContext:
|
|
bot = app.bot
|
|
|
|
context = SimpleContext()
|
|
await start_command(fake_update, context)
|
|
logger.info("/start automatico executado com sucesso")
|
|
except Exception as e:
|
|
logger.error(f"Erro ao executar /start automatico: {e}", exc_info=True)
|
|
|
|
app_instance.post_init = post_init
|
|
|
|
logger.info("Mônica está iniciando... 💖")
|
|
app_instance.run_polling(allowed_updates=Update.ALL_TYPES)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|