123 lines
4.1 KiB
Python
123 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from core.models import ChatTaskSource, TaskProject
|
|
from core.tasks.codex_support import channel_variants
|
|
|
|
SAFE_TASK_FLAGS_DEFAULTS = {
|
|
"derive_enabled": True,
|
|
"match_mode": "strict",
|
|
"require_prefix": True,
|
|
"allowed_prefixes": ["task:", "todo:"],
|
|
"completion_enabled": True,
|
|
"ai_title_enabled": True,
|
|
"announce_task_id": False,
|
|
"min_chars": 3,
|
|
}
|
|
|
|
|
|
def normalize_channel_identifier(service: str, identifier: str) -> str:
|
|
service_key = str(service or "").strip().lower()
|
|
value = str(identifier or "").strip()
|
|
if not value:
|
|
return ""
|
|
if service_key == "whatsapp":
|
|
bare = value.split("@", 1)[0].strip()
|
|
if not bare:
|
|
return value
|
|
if value.endswith("@s.whatsapp.net"):
|
|
return f"{bare}@s.whatsapp.net"
|
|
return f"{bare}@g.us"
|
|
return value
|
|
|
|
|
|
def resolve_message_scope(message) -> tuple[str, str]:
|
|
source_service = str(getattr(message, "source_service", "") or "").strip().lower()
|
|
source_channel = str(getattr(message, "source_chat_id", "") or "").strip()
|
|
if source_service != "web":
|
|
return source_service, source_channel
|
|
identifier = getattr(getattr(message, "session", None), "identifier", None)
|
|
fallback_service = str(getattr(identifier, "service", "") or "").strip().lower()
|
|
fallback_identifier = str(getattr(identifier, "identifier", "") or "").strip()
|
|
if fallback_service and fallback_identifier and fallback_service != "web":
|
|
return fallback_service, fallback_identifier
|
|
return source_service, source_channel
|
|
|
|
|
|
def _project_name_candidate(service: str, channel_identifier: str, message=None) -> str:
|
|
person_name = ""
|
|
if message is not None:
|
|
identifier = getattr(getattr(message, "session", None), "identifier", None)
|
|
person = getattr(identifier, "person", None)
|
|
person_name = str(getattr(person, "name", "") or "").strip()
|
|
if person_name:
|
|
return person_name[:255]
|
|
raw = str(channel_identifier or "").strip()
|
|
if str(service or "").strip().lower() == "whatsapp":
|
|
raw = raw.split("@", 1)[0].strip()
|
|
cleaned = re.sub(r"\s+", " ", raw).strip()
|
|
if not cleaned:
|
|
cleaned = "Chat"
|
|
return f"Chat: {cleaned}"[:255]
|
|
|
|
|
|
def _ensure_unique_project_name(user, base_name: str) -> str:
|
|
base = str(base_name or "").strip() or "Chat"
|
|
if not TaskProject.objects.filter(user=user, name=base).exists():
|
|
return base
|
|
idx = 2
|
|
while idx < 10000:
|
|
candidate = f"{base} ({idx})"[:255]
|
|
if not TaskProject.objects.filter(user=user, name=candidate).exists():
|
|
return candidate
|
|
idx += 1
|
|
return f"{base} ({str(user.id)[:8]})"[:255]
|
|
|
|
|
|
def ensure_default_source_for_chat(
|
|
*,
|
|
user,
|
|
service: str,
|
|
channel_identifier: str,
|
|
message=None,
|
|
):
|
|
service_key = str(service or "").strip().lower()
|
|
normalized_identifier = normalize_channel_identifier(service_key, channel_identifier)
|
|
variants = channel_variants(service_key, normalized_identifier)
|
|
if not service_key or not variants:
|
|
return None
|
|
existing = (
|
|
ChatTaskSource.objects.filter(
|
|
user=user,
|
|
service=service_key,
|
|
channel_identifier__in=variants,
|
|
)
|
|
.select_related("project", "epic")
|
|
.order_by("-enabled", "-updated_at", "-created_at")
|
|
.first()
|
|
)
|
|
if existing is not None:
|
|
if not existing.enabled:
|
|
existing.enabled = True
|
|
existing.save(update_fields=["enabled", "updated_at"])
|
|
return existing
|
|
project_name = _ensure_unique_project_name(
|
|
user,
|
|
_project_name_candidate(service_key, normalized_identifier, message=message),
|
|
)
|
|
project = TaskProject.objects.create(
|
|
user=user,
|
|
name=project_name,
|
|
settings=dict(SAFE_TASK_FLAGS_DEFAULTS),
|
|
)
|
|
return ChatTaskSource.objects.create(
|
|
user=user,
|
|
service=service_key,
|
|
channel_identifier=normalized_identifier,
|
|
project=project,
|
|
epic=None,
|
|
enabled=True,
|
|
settings=dict(SAFE_TASK_FLAGS_DEFAULTS),
|
|
)
|